diff options
838 files changed, 25906 insertions, 8986 deletions
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index a320f1e9509c..3daee1fdd01b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5737,7 +5737,7 @@ public class Intent implements Parcelable, Cloneable { * @hide */ public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS = - "android.intent.extra.EXTRA_CHOOSER_CUSTOM_ACTIONS"; + "android.intent.extra.CHOOSER_CUSTOM_ACTIONS"; /** * Optional argument to be used with {@link #ACTION_CHOOSER}. @@ -5748,7 +5748,7 @@ public class Intent implements Parcelable, Cloneable { * @hide */ public static final String EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION = - "android.intent.extra.EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION"; + "android.intent.extra.CHOOSER_PAYLOAD_RESELECTION_ACTION"; /** * An {@code ArrayList} of {@code String} annotations describing content for diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 2e3b5d286138..80cea55111ba 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1187,6 +1187,79 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @Overridable public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L; + // Compat framework that per-app overrides rely on only supports booleans. That's why we have + // multiple OVERRIDE_*_ORIENTATION_* change ids below instead of just one override with + // the integer value. + + /** + * Enables {@link #SCREEN_ORIENTATION_PORTRAIT}. Unless OVERRIDE_ANY_ORIENTATION + * is enabled, this override is used only when no other fixed orientation was specified by the + * activity. + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L; + + /** + * Enables {@link #SCREEN_ORIENTATION_NOSENSOR}. Unless OVERRIDE_ANY_ORIENTATION + * is enabled, this override is used only when no other fixed orientation was specified by the + * activity. + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR = 265451093L; + + /** + * Enables {@link #SCREEN_ORIENTATION_REVERSE_LANDSCAPE}. Unless OVERRIDE_ANY_ORIENTATION + * is enabled, this override is used only when activity specify landscape orientation. + * This can help apps that assume that landscape display orientation corresponds to {@link + * android.view.Surface#ROTATION_90}, while on some devices it can be {@link + * android.view.Surface#ROTATION_270}. + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE = 266124927L; + + /** + * When enabled, allows OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE, + * OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR and OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT + * to override any orientation requested by the activity. + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_ANY_ORIENTATION = 265464455L; + + /** + * This override fixes display orientation to landscape natural orientation when a task is + * fullscreen. While display rotation is fixed to landscape, the orientation requested by the + * activity will be still respected by bounds resolution logic. For instance, if an activity + * requests portrait orientation and this override is set, then activity will appear in the + * letterbox mode for fixed orientation with the display rotated to the lanscape natural + * orientation. + * + * <p>This override is applicable only when natural orientation of the device is + * landscape and display ignores orientation requestes. + * + * <p>Main use case for this override are camera-using activities that are portrait-only and + * assume alignment with natural device orientation. Such activities can automatically be + * rotated with com.android.server.wm.DisplayRotationCompatPolicy but not all of them can + * handle dynamic rotation and thus can benefit from this override. + * + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L; + /** * Compares activity window layout min width/height with require space for multi window to * determine if it can be put into multi window mode. @@ -1405,8 +1478,19 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @hide */ public boolean isFixedOrientation() { - return isFixedOrientationLandscape() || isFixedOrientationPortrait() - || screenOrientation == SCREEN_ORIENTATION_LOCKED; + return isFixedOrientation(screenOrientation); + } + + /** + * Returns true if the passed activity's orientation is fixed. + * @hide + */ + public static boolean isFixedOrientation(@ScreenOrientation int orientation) { + return orientation == SCREEN_ORIENTATION_LOCKED + // Orientation is fixed to natural display orientation + || orientation == SCREEN_ORIENTATION_NOSENSOR + || isFixedOrientationLandscape(orientation) + || isFixedOrientationPortrait(orientation); } /** diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index df1c0d7c63bc..e5243ee3ff21 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -878,8 +878,8 @@ public abstract class CameraDevice implements AutoCloseable { * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code VIDEO_CALL}</td> <td colspan="3" id="rb"></td> <td>Preview with video call</td> </tr> * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code PREVIEW_VIDEO_STILL}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>Multi-purpose stream with JPEG or YUV still capture</td> </tr> * <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>YUV and JPEG concurrent still image capture (for testing)</td> </tr> - * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG or YUV video snapshot</td> </tr> - * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG or YUV still image capture</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG video snapshot</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG still image capture</td> </tr> * </table><br> * </p> * diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index e5b9cdb74d3b..754472f07d47 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -1223,14 +1223,6 @@ public final class MandatoryStreamCombination { new StreamCombinationTemplate(new StreamTemplate [] { new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, STREAM_USE_CASE_PREVIEW), - new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD, - STREAM_USE_CASE_RECORD), - new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD, - STREAM_USE_CASE_STILL_CAPTURE)}, - "Preview, video record and YUV video snapshot"), - new StreamCombinationTemplate(new StreamTemplate [] { - new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, - STREAM_USE_CASE_PREVIEW), new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD, STREAM_USE_CASE_RECORD), new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD, @@ -1239,27 +1231,11 @@ public final class MandatoryStreamCombination { new StreamCombinationTemplate(new StreamTemplate [] { new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, STREAM_USE_CASE_PREVIEW), - new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD, - STREAM_USE_CASE_RECORD), - new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD, - STREAM_USE_CASE_STILL_CAPTURE)}, - "Preview, in-application video processing and YUV video snapshot"), - new StreamCombinationTemplate(new StreamTemplate [] { - new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, - STREAM_USE_CASE_PREVIEW), new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW, STREAM_USE_CASE_PREVIEW), new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM, STREAM_USE_CASE_STILL_CAPTURE)}, "Preview, in-application image processing, and JPEG still image capture"), - new StreamCombinationTemplate(new StreamTemplate [] { - new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, - STREAM_USE_CASE_PREVIEW), - new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW, - STREAM_USE_CASE_PREVIEW), - new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM, - STREAM_USE_CASE_STILL_CAPTURE)}, - "Preview, in-application image processing, and YUV still image capture"), }; private static StreamCombinationTemplate sPreviewStabilizedStreamCombinations[] = { diff --git a/core/java/android/service/chooser/ChooserAction.java b/core/java/android/service/chooser/ChooserAction.java index 3010049633d4..a61b781b6bca 100644 --- a/core/java/android/service/chooser/ChooserAction.java +++ b/core/java/android/service/chooser/ChooserAction.java @@ -27,10 +27,9 @@ import java.util.Objects; /** * A ChooserAction is an app-defined action that can be provided to the Android Sharesheet to - * be shown to the user when {@link android.content.Intent.ACTION_CHOOSER} is invoked. + * be shown to the user when {@link android.content.Intent#ACTION_CHOOSER} is invoked. * - * @see android.content.Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS - * @see android.content.Intent.EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION + * @see android.content.Intent#EXTRA_CHOOSER_CUSTOM_ACTIONS * @hide */ public final class ChooserAction implements Parcelable { @@ -88,6 +87,7 @@ public final class ChooserAction implements Parcelable { return "ChooserAction {" + "label=" + mLabel + ", intent=" + mAction + "}"; } + @NonNull public static final Parcelable.Creator<ChooserAction> CREATOR = new Creator<ChooserAction>() { @Override @@ -137,6 +137,7 @@ public final class ChooserAction implements Parcelable { * object. * @return the built action */ + @NonNull public ChooserAction build() { return new ChooserAction(mIcon, mLabel, mAction); } diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java index bf5b970311d0..6e4535b7218a 100644 --- a/core/java/android/service/dreams/DreamOverlayService.java +++ b/core/java/android/service/dreams/DreamOverlayService.java @@ -36,38 +36,100 @@ import android.view.WindowManager; public abstract class DreamOverlayService extends Service { private static final String TAG = "DreamOverlayService"; private static final boolean DEBUG = false; - private boolean mShowComplications; - private ComponentName mDreamComponent; - private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() { + // The last client that started dreaming and hasn't ended + private OverlayClient mCurrentClient; + + // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding + // requests to the {@link DreamOverlayService} + private static class OverlayClient extends IDreamOverlayClient.Stub { + private final DreamOverlayService mService; + private boolean mShowComplications; + private ComponentName mDreamComponent; + IDreamOverlayCallback mDreamOverlayCallback; + + OverlayClient(DreamOverlayService service) { + mService = service; + } + @Override - public void startDream(WindowManager.LayoutParams layoutParams, - IDreamOverlayCallback callback, String dreamComponent, - boolean shouldShowComplications) { - mDreamOverlayCallback = callback; + public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback, + String dreamComponent, boolean shouldShowComplications) throws RemoteException { mDreamComponent = ComponentName.unflattenFromString(dreamComponent); mShowComplications = shouldShowComplications; - onStartDream(layoutParams); + mDreamOverlayCallback = callback; + mService.startDream(this, params); } - @Override - public void endDream() { - onEndDream(); - } + @Override public void wakeUp() { - onWakeUp(() -> { + mService.wakeUp(this, () -> { try { mDreamOverlayCallback.onWakeUpComplete(); } catch (RemoteException e) { - Log.e(TAG, "Could not notify dream of wakeUp:" + e); + Log.e(TAG, "Could not notify dream of wakeUp", e); } }); } - }; - IDreamOverlayCallback mDreamOverlayCallback; + @Override + public void endDream() { + mService.endDream(this); + } + + private void onExitRequested() { + try { + mDreamOverlayCallback.onExitRequested(); + } catch (RemoteException e) { + Log.e(TAG, "Could not request exit:" + e); + } + } + + private boolean shouldShowComplications() { + return mShowComplications; + } + + private ComponentName getComponent() { + return mDreamComponent; + } + } + + private void startDream(OverlayClient client, WindowManager.LayoutParams params) { + endDream(mCurrentClient); + mCurrentClient = client; + onStartDream(params); + } + + private void endDream(OverlayClient client) { + if (client == null || client != mCurrentClient) { + return; + } + + onEndDream(); + mCurrentClient = null; + } + + private void wakeUp(OverlayClient client, Runnable callback) { + if (mCurrentClient != client) { + return; + } + + onWakeUp(callback); + } + + private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() { + @Override + public void getClient(IDreamOverlayClientCallback callback) { + try { + callback.onDreamOverlayClient( + new OverlayClient(DreamOverlayService.this)); + } catch (RemoteException e) { + Log.e(TAG, "could not send client to callback", e); + } + } + }; public DreamOverlayService() { } @@ -110,18 +172,23 @@ public abstract class DreamOverlayService extends Service { * This method is invoked to request the dream exit. */ public final void requestExit() { - try { - mDreamOverlayCallback.onExitRequested(); - } catch (RemoteException e) { - Log.e(TAG, "Could not request exit:" + e); + if (mCurrentClient == null) { + throw new IllegalStateException("requested exit with no dream present"); } + + mCurrentClient.onExitRequested(); } /** * Returns whether to show complications on the dream overlay. */ public final boolean shouldShowComplications() { - return mShowComplications; + if (mCurrentClient == null) { + throw new IllegalStateException( + "requested if should show complication when no dream active"); + } + + return mCurrentClient.shouldShowComplications(); } /** @@ -129,6 +196,10 @@ public abstract class DreamOverlayService extends Service { * @hide */ public final ComponentName getDreamComponent() { - return mDreamComponent; + if (mCurrentClient == null) { + throw new IllegalStateException("requested dream component when no dream active"); + } + + return mCurrentClient.getComponent(); } } diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index d3788862b6c0..6a4710f9475a 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -248,25 +248,39 @@ public class DreamService extends Service implements Window.Callback { private OverlayConnection mOverlayConnection; private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> { - // Overlay set during onBind. - private IDreamOverlay mOverlay; + // Retrieved Client + private IDreamOverlayClient mClient; + // A list of pending requests to execute on the overlay. - private final ArrayList<Consumer<IDreamOverlay>> mConsumers = new ArrayList<>(); + private final ArrayList<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>(); + + private final IDreamOverlayClientCallback mClientCallback = + new IDreamOverlayClientCallback.Stub() { + @Override + public void onDreamOverlayClient(IDreamOverlayClient client) { + mClient = client; + + for (Consumer<IDreamOverlayClient> consumer : mConsumers) { + consumer.accept(mClient); + } + } + }; private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() { @Override public void onConnected(ObservableServiceConnection<IDreamOverlay> connection, IDreamOverlay service) { - mOverlay = service; - for (Consumer<IDreamOverlay> consumer : mConsumers) { - consumer.accept(mOverlay); + try { + service.getClient(mClientCallback); + } catch (RemoteException e) { + Log.e(TAG, "could not get DreamOverlayClient", e); } } @Override public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection, int reason) { - mOverlay = null; + mClient = null; } }; @@ -296,16 +310,16 @@ public class DreamService extends Service implements Window.Callback { super.unbind(); } - public void addConsumer(Consumer<IDreamOverlay> consumer) { + public void addConsumer(Consumer<IDreamOverlayClient> consumer) { execute(() -> { mConsumers.add(consumer); - if (mOverlay != null) { - consumer.accept(mOverlay); + if (mClient != null) { + consumer.accept(mClient); } }); } - public void removeConsumer(Consumer<IDreamOverlay> consumer) { + public void removeConsumer(Consumer<IDreamOverlayClient> consumer) { execute(() -> mConsumers.remove(consumer)); } @@ -1050,6 +1064,24 @@ public class DreamService extends Service implements Window.Callback { * </p> */ public final void finish() { + // If there is an active overlay connection, signal that the dream is ending before + // continuing. Note that the overlay cannot rely on the unbound state, since another dream + // might have bound to it in the meantime. + if (mOverlayConnection != null) { + mOverlayConnection.addConsumer(overlay -> { + try { + overlay.endDream(); + mOverlayConnection.unbind(); + mOverlayConnection = null; + finish(); + } catch (RemoteException e) { + Log.e(mTag, "could not inform overlay of dream end:" + e); + } + }); + mOverlayConnection.clearConsumers(); + return; + } + if (mDebug) Slog.v(mTag, "finish(): mFinished=" + mFinished); Activity activity = mActivity; @@ -1066,10 +1098,6 @@ public class DreamService extends Service implements Window.Callback { } mFinished = true; - if (mOverlayConnection != null) { - mOverlayConnection.unbind(); - } - if (mDreamToken == null) { if (mDebug) Slog.v(mTag, "finish() called when not attached."); stopSelf(); @@ -1365,7 +1393,7 @@ public class DreamService extends Service implements Window.Callback { mWindow.getDecorView().addOnAttachStateChangeListener( new View.OnAttachStateChangeListener() { - private Consumer<IDreamOverlay> mDreamStartOverlayConsumer; + private Consumer<IDreamOverlayClient> mDreamStartOverlayConsumer; @Override public void onViewAttachedToWindow(View v) { @@ -1389,17 +1417,6 @@ public class DreamService extends Service implements Window.Callback { @Override public void onViewDetachedFromWindow(View v) { - if (mOverlayConnection != null) { - mOverlayConnection.addConsumer(overlay -> { - try { - overlay.endDream(); - } catch (RemoteException e) { - Log.e(mTag, "could not inform overlay of dream end:" + e); - } - }); - mOverlayConnection.clearConsumers(); - } - if (mActivity == null || !mActivity.isChangingConfigurations()) { // Only stop the dream if the view is not detached by relaunching // activity for configuration changes. It is important to also clear @@ -1408,6 +1425,10 @@ public class DreamService extends Service implements Window.Callback { mActivity = null; finish(); } + + if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) { + mOverlayConnection.removeConsumer(mDreamStartOverlayConsumer); + } } }); } diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl index 0e4bd3bd547b..7ec75a50ed00 100644 --- a/core/java/android/service/dreams/IDreamOverlay.aidl +++ b/core/java/android/service/dreams/IDreamOverlay.aidl @@ -16,8 +16,7 @@ package android.service.dreams; -import android.service.dreams.IDreamOverlayCallback; -import android.view.WindowManager.LayoutParams; +import android.service.dreams.IDreamOverlayClientCallback; /** * {@link IDreamOverlay} provides a way for a component to annotate a dream with additional view @@ -28,20 +27,7 @@ import android.view.WindowManager.LayoutParams; */ interface IDreamOverlay { /** - * @param params The {@link LayoutParams} for the associated DreamWindow, including the window - token of the Dream Activity. - * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the - * dream. - * @param dreamComponent The component name of the dream service requesting overlay. - * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock - * and weather. + * Retrieves a client the caller can use to interact with the dream overlay. */ - void startDream(in LayoutParams params, in IDreamOverlayCallback callback, - in String dreamComponent, in boolean shouldShowComplications); - - /** Called when the dream is waking, to do any exit animations */ - void wakeUp(); - - /** Called when the dream has ended. */ - void endDream(); + void getClient(in IDreamOverlayClientCallback callback); } diff --git a/core/java/android/service/dreams/IDreamOverlayClient.aidl b/core/java/android/service/dreams/IDreamOverlayClient.aidl new file mode 100644 index 000000000000..78b7280ae652 --- /dev/null +++ b/core/java/android/service/dreams/IDreamOverlayClient.aidl @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.dreams; + +import android.service.dreams.IDreamOverlayCallback; +import android.view.WindowManager.LayoutParams; + +/** +* {@link IDreamOverlayClient} allows {@link DreamService} instances to act upon the dream overlay. +* +* @hide +*/ +interface IDreamOverlayClient { + /** + * @param params The {@link LayoutParams} for the associated DreamWindow, including the window + token of the Dream Activity. + * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the + * dream. + * @param dreamComponent The component name of the dream service requesting overlay. + * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock + * and weather. + */ + void startDream(in LayoutParams params, in IDreamOverlayCallback callback, + in String dreamComponent, in boolean shouldShowComplications); + + /** Called when the dream is waking, to do any exit animations */ + void wakeUp(); + + /** Called when the dream has ended. */ + void endDream(); +} diff --git a/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl new file mode 100644 index 000000000000..244d999c623b --- /dev/null +++ b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.dreams; + +import android.service.dreams.IDreamOverlayClient; + +/** +* {@link IDreamOverlayClientCallback} allows receiving a requested {@link IDreamOverlayClient}. +* @hide +*/ +interface IDreamOverlayClientCallback { + /** + * Called with a unique {@link IDreamOverlayClient}. + */ + void onDreamOverlayClient(in IDreamOverlayClient client); +} diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java index b09d2e9e7f13..7aacb9b4d11a 100644 --- a/core/java/android/service/quickaccesswallet/WalletCard.java +++ b/core/java/android/service/quickaccesswallet/WalletCard.java @@ -16,6 +16,7 @@ package android.service.quickaccesswallet; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PendingIntent; @@ -24,28 +25,73 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + /** * A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit * card, library card, transit pass, etc. Cards are identified by a String identifier and contain a - * card image, card image content description, and a {@link PendingIntent} to be used if the user - * clicks on the card. Cards may be displayed with an icon and label, though these are optional. + * card type, card image, card image content description, and a {@link PendingIntent} to be used if + * the user clicks on the card. Cards may be displayed with an icon and label, though these are + * optional. Valuable cards will also have a second image that will be displayed when the card is + * tapped. */ + public final class WalletCard implements Parcelable { + /** + * Unknown cards refer to cards whose types are unspecified. + * @hide + */ + public static final int CARD_TYPE_UNKNOWN = 0; + + /** + * Payment cards refer to credit cards, debit cards or any other cards in the wallet used to + * make cash-equivalent payments. + * @hide + */ + public static final int CARD_TYPE_PAYMENT = 1; + + /** + * Valuable cards refer to any cards that are not used for cash-equivalent payment. + * This includes event tickets, flights, offers, loyalty cards, gift cards and transit tickets. + * @hide + */ + public static final int CARD_TYPE_VALUABLE = 2; + private final String mCardId; + private final int mCardType; private final Icon mCardImage; private final CharSequence mContentDescription; private final PendingIntent mPendingIntent; private final Icon mCardIcon; private final CharSequence mCardLabel; + private final Icon mValuableCardSecondaryImage; private WalletCard(Builder builder) { this.mCardId = builder.mCardId; + this.mCardType = builder.mCardType; this.mCardImage = builder.mCardImage; this.mContentDescription = builder.mContentDescription; this.mPendingIntent = builder.mPendingIntent; this.mCardIcon = builder.mCardIcon; this.mCardLabel = builder.mCardLabel; + this.mValuableCardSecondaryImage = builder.mValuableCardSecondaryImage; + } + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"CARD_TYPE_"}, value = { + CARD_TYPE_UNKNOWN, + CARD_TYPE_PAYMENT, + CARD_TYPE_VALUABLE + }) + public @interface CardType { } @Override @@ -56,29 +102,44 @@ public final class WalletCard implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mCardId); + dest.writeInt(mCardType); mCardImage.writeToParcel(dest, flags); TextUtils.writeToParcel(mContentDescription, dest, flags); PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest); - if (mCardIcon == null) { + writeIconIfNonNull(mCardIcon, dest, flags); + TextUtils.writeToParcel(mCardLabel, dest, flags); + writeIconIfNonNull(mValuableCardSecondaryImage, dest, flags); + + } + + /** Utility function called by writeToParcel + */ + private void writeIconIfNonNull(Icon icon, Parcel dest, int flags) { + if (icon == null) { dest.writeByte((byte) 0); } else { dest.writeByte((byte) 1); - mCardIcon.writeToParcel(dest, flags); + icon.writeToParcel(dest, flags); } - TextUtils.writeToParcel(mCardLabel, dest, flags); } private static WalletCard readFromParcel(Parcel source) { String cardId = source.readString(); + int cardType = source.readInt(); Icon cardImage = Icon.CREATOR.createFromParcel(source); CharSequence contentDesc = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source); Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source); CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - return new Builder(cardId, cardImage, contentDesc, pendingIntent) + Icon valuableCardSecondaryImage = source.readByte() == 0 ? null : + Icon.CREATOR.createFromParcel(source); + Builder builder = new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent) .setCardIcon(cardIcon) - .setCardLabel(cardLabel) - .build(); + .setCardLabel(cardLabel); + + return cardType == CARD_TYPE_VALUABLE + ? builder.setValuableCardSecondaryImage(valuableCardSecondaryImage).build() : + builder.build(); } @NonNull @@ -104,6 +165,16 @@ public final class WalletCard implements Parcelable { } /** + * Returns the card type. + * @hide + */ + @NonNull + @CardType + public int getCardType() { + return mCardType; + } + + /** * The visual representation of the card. If the card image Icon is a bitmap, it should have a * width of {@link GetWalletCardsRequest#getCardWidthPx()} and a height of {@link * GetWalletCardsRequest#getCardHeightPx()}. @@ -158,23 +229,37 @@ public final class WalletCard implements Parcelable { } /** - * Builder for {@link WalletCard} objects. You must to provide cardId, cardImage, + * Visual representation of the card when it is tapped. Includes a barcode to scan the card in + * addition to the information in the primary image. + * @hide + */ + @Nullable + public Icon getValuableCardSecondaryImage() { + return mValuableCardSecondaryImage; + } + + /** + * Builder for {@link WalletCard} objects. You must provide cardId, cardImage, * contentDescription, and pendingIntent. If the card is opaque and should be shown with * elevation, set hasShadow to true. cardIcon and cardLabel are optional. */ public static final class Builder { private String mCardId; + private int mCardType; private Icon mCardImage; private CharSequence mContentDescription; private PendingIntent mPendingIntent; private Icon mCardIcon; private CharSequence mCardLabel; + private Icon mValuableCardSecondaryImage; /** * @param cardId The card id must be non-null and unique within the list of * cards returned. <b>Note: * </b> this card ID should <b>not</b> contain PII (Personally * Identifiable Information, such as username or email address). + * @param cardType Integer representing the card type. The card type must be + * non-null. If not provided, it defaults to unknown. * @param cardImage The visual representation of the card. If the card image Icon * is a bitmap, it should have a width of {@link * GetWalletCardsRequest#getCardWidthPx()} and a height of {@link @@ -193,18 +278,33 @@ public final class WalletCard implements Parcelable { * request device unlock before sending the pending intent. It is * recommended that the pending intent be immutable (use {@link * PendingIntent#FLAG_IMMUTABLE}). + * @hide */ public Builder(@NonNull String cardId, + @NonNull @CardType int cardType, @NonNull Icon cardImage, @NonNull CharSequence contentDescription, - @NonNull PendingIntent pendingIntent) { + @NonNull PendingIntent pendingIntent + ) { mCardId = cardId; + mCardType = cardType; mCardImage = cardImage; mContentDescription = contentDescription; mPendingIntent = pendingIntent; } /** + * Called when a card type is not provided. + */ + public Builder(@NonNull String cardId, + @NonNull Icon cardImage, + @NonNull CharSequence contentDescription, + @NonNull PendingIntent pendingIntent) { + this(cardId, WalletCard.CARD_TYPE_UNKNOWN, cardImage, contentDescription, + pendingIntent); + } + + /** * An icon may be shown alongside the card image to convey information about how the card * can be used, or if some other action must be taken before using the card. For example, an * NFC logo could indicate that the card is NFC-enabled and will be provided to an NFC @@ -236,6 +336,19 @@ public final class WalletCard implements Parcelable { } /** + * Visual representation of the card when it is tapped. Includes a barcode to scan the card + * in addition to the information in the primary image. + * @hide + */ + @NonNull + public Builder setValuableCardSecondaryImage(@Nullable Icon valuableCardSecondaryImage) { + Preconditions.checkState(mCardType == CARD_TYPE_VALUABLE, + "This field can only be set on valuable cards"); + mValuableCardSecondaryImage = valuableCardSecondaryImage; + return this; + } + + /** * Builds a new {@link WalletCard} instance. * * @return A built response. diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java index b7873b73cb28..61f29a40ff50 100644 --- a/core/java/android/util/SparseSetArray.java +++ b/core/java/android/util/SparseSetArray.java @@ -139,4 +139,9 @@ public class SparseSetArray<T> { public T valueAt(int intIndex, int valueIndex) { return mData.valueAt(intIndex).valueAt(valueIndex); } + + /** @return The set of values for key at position {@code intIndex}. */ + public ArraySet<T> valuesAt(int intIndex) { + return mData.valueAt(intIndex); + } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index a37c24499aff..f62a487d5ec7 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -814,8 +814,8 @@ public interface WindowManager extends ViewManager { } /** - * Activity level {@link android.content.pm.PackageManager.Property PackageManager - * .Property} for an app to inform the system that the activity can be opted-in or opted-out + * Application level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the app can be opted-in or opted-out * from the compatibility treatment that avoids {@link * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural @@ -833,17 +833,17 @@ public interface WindowManager extends ViewManager { * <li>Camera compatibility force rotation treatment is active for the package. * </ul> * - * <p>Setting this property to {@code false} informs the system that the activity must be + * <p>Setting this property to {@code false} informs the system that the app must be * opted-out from the compatibility treatment even if the device manufacturer has opted the app * into the treatment. * * <p><b>Syntax:</b> * <pre> - * <activity> + * <application> * <property * android:name="android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION" * android:value="true|false"/> - * </activity> + * </application> * </pre> * * @hide @@ -853,8 +853,8 @@ public interface WindowManager extends ViewManager { "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"; /** - * Activity level {@link android.content.pm.PackageManager.Property PackageManager - * .Property} for an app to inform the system that the activity should be excluded from the + * Application level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the app should be excluded from the * camera compatibility force rotation treatment. * * <p>The camera compatibility treatment aligns orientations of portrait app window and natural @@ -879,11 +879,11 @@ public interface WindowManager extends ViewManager { * * <p><b>Syntax:</b> * <pre> - * <activity> + * <application> * <property * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION" * android:value="true|false"/> - * </activity> + * </application> * </pre> * * @hide @@ -893,8 +893,8 @@ public interface WindowManager extends ViewManager { "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"; /** - * Activity level {@link android.content.pm.PackageManager.Property PackageManager - * .Property} for an app to inform the system that the activity should be excluded + * Application level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the app should be excluded * from the activity "refresh" after the camera compatibility force rotation treatment. * * <p>The camera compatibility treatment aligns orientations of portrait app window and natural @@ -926,11 +926,11 @@ public interface WindowManager extends ViewManager { * * <p><b>Syntax:</b> * <pre> - * <activity> + * <application> * <property * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH" * android:value="true|false"/> - * </activity> + * </application> * </pre> * * @hide @@ -940,7 +940,7 @@ public interface WindowManager extends ViewManager { "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"; /** - * Activity level {@link android.content.pm.PackageManager.Property PackageManager + * Application level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for an app to inform the system that the activity should be or shouldn't be * "refreshed" after the camera compatibility force rotation treatment using "paused -> * resumed" cycle rather than "stopped -> resumed". @@ -976,11 +976,11 @@ public interface WindowManager extends ViewManager { * * <p><b>Syntax:</b> * <pre> - * <activity> + * <application> * <property * android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE" * android:value="true|false"/> - * </activity> + * </application> * </pre> * * @hide @@ -990,6 +990,77 @@ public interface WindowManager extends ViewManager { "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"; /** + * Application level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the app should be excluded from the + * compatibility override for orientation set by the device manufacturer. + * + * <p>With this property set to {@code true} or unset, device manufacturers can override + * orientation for the app using their discretion to improve display compatibility. + * + * <p>With this property set to {@code false}, device manufactured per-app override for + * orientation won't be applied. + * + * <p><b>Syntax:</b> + * <pre> + * <application> + * <property + * android:name="android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE" + * android:value="true|false"/> + * </application> + * </pre> + * + * @hide + */ + // TODO(b/263984287): Make this public API. + String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = + "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"; + + /** + * Application level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the app should be opted-out from the + * compatibility override that fixes display orientation to landscape natural orientation when + * an activity is fullscreen. + * + * <p>When this compat override is enabled and while display is fixed to the landscape natural + * orientation, the orientation requested by the activity will be still respected by bounds + * resolution logic. For instance, if an activity requests portrait orientation, then activity + * will appear in the letterbox mode for fixed orientation with the display rotated to the + * lanscape natural orientation. + * + * <p>The treatment is disabled by default but device manufacturers can enable the treatment + * using their discretion to improve display compatibility on the displays that have + * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed + * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a> + * for more details). + * + * <p>With this property set to {@code true} or unset, the system wiil use landscape display + * orientation when the following conditions are met: + * <ul> + * <li>Natural orientation of the display is landscape + * <li>ignoreOrientationRequest display setting is enabled + * <li>Activity is fullscreen. + * <li>Device manufacturer enabled the treatment. + * </ul> + * + * <p>With this property set to {@code false}, device manufactured per-app override for + * display orientation won't be applied. + * + * <p><b>Syntax:</b> + * <pre> + * <application> + * <property + * android:name="android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE" + * android:value="true|false"/> + * </application> + * </pre> + * + * @hide + */ + // TODO(b/263984287): Make this public API. + String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = + "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"; + + /** * @hide */ public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array"; diff --git a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java index e3cc4f12fcc6..d0b581158614 100644 --- a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java +++ b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java @@ -47,7 +47,9 @@ public class ChooserActivityLoggerImpl implements ChooserActivityLogger { /* num_app_provided_app_targets = 6 */ appProvidedApp, /* is_workprofile = 7 */ isWorkprofile, /* previewType = 8 */ typeFromPreviewInt(previewType), - /* intentType = 9 */ typeFromIntentString(intent)); + /* intentType = 9 */ typeFromIntentString(intent), + /* num_provided_custom_actions = 10 */ 0, + /* reselection_action_provided = 11 */ false); } @Override diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 4f7f8ba2b45c..b9373be76b9a 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -567,11 +567,6 @@ public final class SystemUiDeviceConfigFlags { public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification"; /** - * (boolean) Whether the clipboard overlay is enabled. - */ - public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled"; - - /** * (boolean) Whether widget provider info would be saved to / loaded from system persistence * layer as opposed to individual manifests in respective apps. */ diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto index 8e4006aa6861..e029af4f9819 100644 --- a/core/proto/android/service/notification.proto +++ b/core/proto/android/service/notification.proto @@ -110,11 +110,20 @@ message ManagedServicesProto { // All of this type/caption enabled for current profiles. repeated android.content.ComponentNameProto enabled = 3; - repeated ManagedServiceInfoProto live_services = 4; + // Was: repeated ComponentNameProto, when snoozed services were not per-user-id. + reserved 5; + + message SnoozedServices { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 user_id = 1; + repeated android.content.ComponentNameProto snoozed = 2; + } + // Snoozed for current profiles. - repeated android.content.ComponentNameProto snoozed = 5; + repeated SnoozedServices snoozed = 6; } message RankingHelperProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6a80d1cb62a7..31903e25f8fd 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -315,6 +315,7 @@ <protected-broadcast android:name="android.media.MASTER_BALANCE_CHANGED_ACTION" /> <protected-broadcast android:name="android.media.SCO_AUDIO_STATE_CHANGED" /> <protected-broadcast android:name="android.media.ACTION_SCO_AUDIO_STATE_UPDATED" /> + <protected-broadcast android:name="com.android.server.audio.action.CHECK_MUSIC_ACTIVE" /> <protected-broadcast android:name="android.intent.action.MEDIA_REMOVED" /> <protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTED" /> @@ -3893,7 +3894,7 @@ <p>Should only be requested by the System, should be required by TileService declarations.--> <permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" - android:protectionLevel="signature" /> + android:protectionLevel="signature|recents" /> <!-- Allows SystemUI to request third party controls. <p>Should only be requested by the System and required by diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml index 50e6f33f628a..a5ff4706c085 100644 --- a/core/res/res/layout/resolve_grid_item.xml +++ b/core/res/res/layout/resolve_grid_item.xml @@ -17,6 +17,7 @@ */ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/item" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 869d5e1442bb..ef746fab4aef 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -330,11 +330,11 @@ <string name="permgroupdesc_notifications" msgid="4608679556801506580">"wys kennisgewings"</string> <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Venster-inhoud ophaal"</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Die inhoud ondersoek van \'n venster waarmee jy interaksie het."</string> - <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Verken deur raak aanskakel"</string> + <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Verken-met-raak aanskakel"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Items waarop getik word, sal hardop gesê word en die skerm kan met behulp van gebare verken word."</string> - <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Teks wat jy tik waarneem"</string> + <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Neem teks wat jy tik waar"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Dit sluit persoonlike data soos kredietkaartnommers en wagwoorde in."</string> - <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Vertoonskermvergroting beheer"</string> + <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Beheer vertoonskerm-vergroting"</string> <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Die vertoonskerm se zoemvlak en posisionering beheer."</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Voer gebare uit"</string> <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Kan tik, swiep, knyp en ander gebare uitvoer."</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Bekyk tans volskerm"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Swiep van bo na onder as jy wil uitgaan."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Het dit"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Klaar"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Ure se sirkelglyer"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Minute se sirkelglyer"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index d5d8fe5a38d5..89039a6ff779 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"ሙሉ ገጽ በማሳየት ላይ"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"ለመውጣት፣ ከላይ ወደታች ጠረግ ያድርጉ።"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"ገባኝ"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"ተከናውኗል"</string> <string name="hour_picker_description" msgid="5153757582093524635">"የሰዓታት ክብ ተንሸራታች"</string> <string name="minute_picker_description" msgid="9029797023621927294">"የደቂቃዎች ክብ ተንሸራታች"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index cbf18251287c..b04af913f56a 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1844,6 +1844,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"جارٍ العرض بملء الشاشة"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"للخروج، مرر بسرعة من أعلى إلى أسفل."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"حسنًا"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"تم"</string> <string name="hour_picker_description" msgid="5153757582093524635">"شريط التمرير الدائري للساعات"</string> <string name="minute_picker_description" msgid="9029797023621927294">"شريط التمرير الدائري للدقائق"</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index 5834cb2ae2a8..cd693145222e 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -788,7 +788,7 @@ <string name="policylab_expirePassword" msgid="6015404400532459169">"স্ক্ৰীন লক পাছৱৰ্ডৰ ম্যাদ ওকলাৰ দিন ছেট কৰক"</string> <string name="policydesc_expirePassword" msgid="9136524319325960675">"স্ক্ৰীন লকৰ পাছৱৰ্ড, পিন বা আর্হি কিমান ঘনাই সলনি কৰিব লাগিব তাক সলনি কৰক।"</string> <string name="policylab_encryptedStorage" msgid="9012936958126670110">"ষ্ট’ৰেজৰ এনক্ৰিপশ্বন ছেট কৰক"</string> - <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"সঞ্চয় কৰি ৰখা ডেটাক এনক্ৰিপ্ট কৰাৰ প্ৰয়োজন।"</string> + <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"ষ্ট’ৰ কৰি ৰখা এপৰ ডেটাক এনক্ৰিপ্ট কৰাৰ প্ৰয়োজন।"</string> <string name="policylab_disableCamera" msgid="5749486347810162018">"কেমেৰাবোৰ অক্ষম কৰক"</string> <string name="policydesc_disableCamera" msgid="3204405908799676104">"আটাইবোৰ ডিভাইচৰ কেমেৰা ব্যৱহাৰ কৰাত বাধা দিয়ক।"</string> <string name="policylab_disableKeyguardFeatures" msgid="5071855750149949741">"স্ক্ৰীন লকৰ কিছুমান সুবিধা অক্ষম কৰক"</string> @@ -1046,9 +1046,9 @@ <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"আপোনাৰ ৱেব বুকমার্কবোৰ আৰু ইতিহাস পঢ়ক"</string> <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰে ব্যৱহাৰ কৰা আটাইবোৰ URLৰ ইতিহাস পঢ়িবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string> <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"আপোনাৰ ৱেব বুকমার্কবোৰ আৰু ইতিহাস লিখক"</string> - <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"আপোনাৰ টেবলেটত সঞ্চয় কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string> + <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"আপোনাৰ টেবলেটত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string> <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"এপ্টোক আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ ইতিহাস আৰু বুকমার্কবোৰ সংশোধন কৰিবলৈ অনুমতি দিয়ে। ব্ৰাউজাৰ ডেটা মোহাৰিবলৈ অথবা সংশোধন কৰিবলৈ ই এপ্টোক অনুমতি দিব পাৰে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ অথবা ৱেব ব্ৰাউজিঙৰ ক্ষমতা থকা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ কৰা নহ’বও পাৰে।"</string> - <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"আপোনাৰ ফ\'নত সঞ্চয় কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string> + <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"আপোনাৰ ফ\'নত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string> <string name="permlab_setAlarm" msgid="1158001610254173567">"এলাৰ্ম ছেট কৰক"</string> <string name="permdesc_setAlarm" msgid="2185033720060109640">"এপ্টোক ইনষ্টল হৈ থকা এলাৰ্ম ক্লক এপত এলাৰ্ম ছেট কৰিবলৈ অনুমতি দিয়ে। কিছুমান এলাৰ্ম ক্লক এপত এই সুবিধাটো প্ৰযোজ্য নহ’ব পাৰে।"</string> <string name="permlab_addVoicemail" msgid="4770245808840814471">"ভইচমেইল যোগ কৰক"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"স্ক্ৰীন পূৰ্ণৰূপত চাই আছে"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"বাহিৰ হ\'বলৈ ওপৰৰপৰা তললৈ ছোৱাইপ কৰক।"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"বুজি পালোঁ"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"সম্পন্ন কৰা হ’ল"</string> <string name="hour_picker_description" msgid="5153757582093524635">"ঘড়ীৰ বৃত্তাকাৰ শ্লাইডাৰ"</string> <string name="minute_picker_description" msgid="9029797023621927294">"মিনিটৰ বৃত্তাকাৰ শ্লাইডাৰ"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 9a4e5cef1882..5abd2b64dbb7 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Tam ekrana baxış"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Çıxmaq üçün yuxarıdan aşağı sürüşdürün."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Anladım"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Hazırdır"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Dairəvi saat slayderi"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Dairəvi dəqiqə slayderi"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index c183da8efb83..4a7a235c2fc6 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -1841,6 +1841,8 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Prikazuje se ceo ekran"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Da biste izašli, prevucite nadole odozgo."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Važi"</string> + <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotirajte radi boljeg prikaza"</string> + <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Izađite iz podeljenog ekrana radi boljeg prikaza"</string> <string name="done_label" msgid="7283767013231718521">"Gotovo"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Kružni klizač za sate"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Kružni klizač za minute"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 2d8f21d5c62c..fa0ebc12658b 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1842,6 +1842,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Прагляд у поўнаэкранным рэжыме"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Для выхаду правядзіце зверху ўніз."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Зразумела"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Гатова"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Кругавы паўзунок гадзін"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Кругавы паўзунок хвілін"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index d0e4b4c06259..ba4466fd90f5 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -328,17 +328,17 @@ <string name="permgroupdesc_sensors" msgid="2610631290633747752">"достъп до сензорните данни за жизнените ви показатели"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"Известия"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"показване на известията"</string> - <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Извличане на съдържанието от прозореца"</string> - <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Инспектиране на съдържанието на прозорец, с който взаимодействате."</string> - <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Включване на изследването чрез докосване"</string> + <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Извлича съдържанието от прозореца"</string> + <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Инспектира съдържанието на прозорец, с който взаимодействате."</string> + <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Включи изследването чрез докосване"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Докосваните елементи ще бъдат изговаряни на глас и екранът може да бъде изследван посредством жестове."</string> - <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Наблюдение на въвеждания от вас текст"</string> + <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Наблюдава въвеждания от вас текст"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Включва лични данни, като например номера на кредитни карти и пароли."</string> - <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Управление на увеличението на дисплея"</string> - <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Управление на нивото на мащаба и позиционирането на дисплея."</string> + <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Управлява увеличението на дисплея"</string> + <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Управлява нивото на мащаба и позиционирането на дисплея."</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Извършване на жестове"</string> <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Можете да докосвате, да прекарвате пръст, да събирате пръсти и да извършвате други жестове."</string> - <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Жестове за отпечатък"</string> + <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Улавя жестове за отпечатък"</string> <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Може да улавя жестовете, извършени върху сензора за отпечатъци на устройството."</string> <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Създаване на екранна снимка"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да създава екранни снимки."</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Изглед на цял екран"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"За изход плъзнете пръст надолу от горната част."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Разбрах"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Готово"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Кръгов плъзгач за часовете"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Кръгов плъзгач за минутите"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index d11e3eb5ea9d..ef513843e786 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -328,13 +328,13 @@ <string name="permgroupdesc_sensors" msgid="2610631290633747752">"আপনার অত্যাবশ্যক লক্ষণগুলির সম্পর্কে সেন্সর ডেটা অ্যাক্সেস করে"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"বিজ্ঞপ্তি"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"বিজ্ঞপ্তি দেখুন"</string> - <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"উইন্ডোর কন্টেন্ট পুনরুদ্ধার করে"</string> - <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ব্যবহার করছেন এমন একটি উইন্ডোর কন্টেন্ট নিরীক্ষণ করে৷"</string> + <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"উইন্ডোর কন্টেন্ট ফিরিয়ে আনুন"</string> + <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ব্যবহার করছেন এমন একটি উইন্ডোর কন্টেন্ট পরীক্ষা করে৷"</string> <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"স্পর্শের মাধ্যমে অন্বেষণ করা চালু করুন"</string> - <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"যে আইটেমগুলিতে আলতো চেপেছেন সেগুলি সশব্দে বলবে এবং ইঙ্গিতগুলি ব্যবহার করে স্ক্রিন অন্বেষণ করা যাবে৷"</string> + <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"যে আইটেমগুলিতে ট্যাপ করেছেন সেগুলি জোরে বলবে এবং ইঙ্গিতগুলি ব্যবহার করে স্ক্রিন অন্বেষণ করা যাবে৷"</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"আপনার লেখা পাঠ্যকে নিরীক্ষণ করে"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"ক্রেডিট কার্ডের নম্বর ও পাসওয়ার্ডগুলির মতো ব্যক্তিগত তথ্য অন্তর্ভুক্ত করে৷"</string> - <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"প্রদর্শনের বৃহত্তরীকরণ ব্যবস্থা নিয়ন্ত্রণ করুন"</string> + <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"ডিসপ্লে বড়কার ব্যবস্থা নিয়ন্ত্রণ করুন"</string> <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"প্রদর্শনের জুমের স্তর এবং লোকেশন নির্ধারন নিয়ন্ত্রণ করুন৷"</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"অঙ্গভঙ্গির কাজগুলি সম্পাদন"</string> <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"আলতো চাপ দেওয়া, সোয়াইপ, পিঞ্চ করা এবং অন্যান্য ইঙ্গিতের কাজগুলি সম্পাদন করতে পারবেন৷"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"পূর্ণ স্ক্রিনে দেখা হচ্ছে"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"প্রস্থান করতে উপর থেকে নিচের দিকে সোয়াইপ করুন"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"বুঝেছি"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"সম্পন্ন হয়েছে"</string> <string name="hour_picker_description" msgid="5153757582093524635">"বৃত্তাকার ঘণ্টা নির্বাচকের স্লাইডার"</string> <string name="minute_picker_description" msgid="9029797023621927294">"বৃত্তাকার মিনিট নির্বাচকের স্লাইডার"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 35f08cc26ea2..fd5cd642e757 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -329,20 +329,20 @@ <string name="permgroupdesc_sensors" msgid="2610631290633747752">"pristupa podacima senzora o vašim vitalnim funkcijama"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"Obavještenja"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"prikaz obavještenja"</string> - <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"preuzima sadržaj prozora"</string> + <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"preuzimati sadržaj prozora"</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Pregleda sadržaj prozora koji trenutno koristite."</string> - <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"uključi opciju Istraživanje dodirom"</string> - <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Stavke koje dodirnete bit će izgovorene naglas, a ekran možete istraživati koristeći pokrete."</string> - <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"prati tekst koji unosite"</string> + <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"uključiti Istraživanje dodirom"</string> + <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Stavke koje dodirnete će se izgovarati naglas i moći ćete istraživati ekran pomoću pokreta."</string> + <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"pratiti tekst koji unosite"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Obuhvata lične podatke kao što su brojevi kreditnih kartica i lozinke."</string> - <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"kontrolira uvećavanje prikaza na ekranu"</string> - <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Kontrolira stepen uvećanja prikaza na ekranu i podešavanje položaja."</string> - <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"izvodi pokrete"</string> + <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"kontrolirati uvećavanje prikaza na ekranu"</string> + <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Kontrolira nivo i položaj zumiranja na ekranu."</string> + <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"izvoditi pokrete"</string> <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Može dodirivati, prevlačiti, hvatati prstima i praviti druge pokrete."</string> - <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"prepoznaje pokrete za otisak prsta"</string> - <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Moguće je zabilježiti pokrete na senzoru za otisak prsta uređaja."</string> - <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"pravi snimke ekrana"</string> - <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Moguće je snimiti ekran."</string> + <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"prepoznavati pokrete otiska prsta"</string> + <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Može zabilježiti pokrete na senzoru za otisak prsta uređaja."</string> + <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"praviti snimke ekrana"</string> + <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Može napraviti snimak ekrana."</string> <string name="permlab_statusBar" msgid="8798267849526214017">"onemogućavanje ili mijenjanje statusne trake"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Dozvoljava aplikaciji onemogućavanje statusne trake ili dodavanje i uklanjanje sistemskih ikona."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"funkcioniranje u vidu statusne trake"</string> @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Prikazuje se cijeli ekran"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Da izađete, prevucite odozgo nadolje."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Razumijem"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Gotovo"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Kružni klizač za odabir sata"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Kružni klizač za minute"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 1934d6e8208e..153f4305a0f8 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -330,19 +330,19 @@ <string name="permgrouplab_notifications" msgid="5472972361980668884">"Notificacions"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"mostra notificacions"</string> <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Recuperar el contingut de la finestra"</string> - <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspecciona el contingut d\'una finestra amb què estàs interaccionant."</string> + <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspeccionar el contingut d\'una finestra amb què estàs interaccionant."</string> <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Activar Exploració tàctil"</string> - <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Els elements que toquis es diran en veu alta, i podràs explorar la pantalla amb gestos."</string> + <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Els elements que toquis s\'enunciaran en veu alta, i la pantalla es pot explorar amb gestos."</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Observar el text que escrius"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Inclou dades personals com ara números de targetes de crèdit i contrasenyes."</string> <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Controlar l\'ampliació de la pantalla"</string> - <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Controla el nivell i la posició del zoom de la pantalla."</string> + <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Controlar el nivell i la posició del zoom de la pantalla."</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Fer gestos"</string> <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Permet tocar, lliscar, pinçar i fer altres gestos."</string> <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos d\'empremtes digitals"</string> - <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Captura gestos realitzats en el sensor d\'empremtes digitals del dispositiu."</string> - <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fes una captura de pantalla"</string> - <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pots fer una captura de la pantalla."</string> + <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Pot capturar els gestos fets en el sensor d\'empremtes digitals del dispositiu."</string> + <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fer una captura de pantalla"</string> + <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pot fer una captura de la pantalla."</string> <string name="permlab_statusBar" msgid="8798267849526214017">"desactivar o modificar la barra d\'estat"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Permet que l\'aplicació desactivi la barra d\'estat o afegeixi i elimini icones del sistema."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"aparèixer a la barra d\'estat"</string> @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Mode de pantalla completa"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Per sortir, llisca cap avall des de la part superior."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Entesos"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Fet"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Control circular de les hores"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Control circular dels minuts"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index ea61feaab93a..24617d08e249 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1842,6 +1842,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Zobrazení celé obrazovky"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Režim ukončíte přejetím prstem shora dolů."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Rozumím"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Hotovo"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Kruhový posuvník hodin"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Kruhový posuvník minut"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index ca619b30a700..bcc7d50344bc 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Visning i fuld skærm"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Stryg ned fra toppen for at afslutte."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK, det er forstået"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Udfør"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Cirkulær timevælger"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Cirkulær minutvælger"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 876d3a17aae3..0df4fc6de74b 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Vollbildmodus wird aktiviert"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Zum Beenden von oben nach unten wischen"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Ok"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Fertig"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Kreisförmiger Schieberegler für Stunden"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Kreisförmiger Schieberegler für Minuten"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 172365f825b2..8c354fb8fafa 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Προβολή σε πλήρη οθόνη"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Για έξοδο, σύρετε προς τα κάτω από το επάνω μέρος."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Το κατάλαβα"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Τέλος"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Κυκλικό ρυθμιστικό ωρών"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Κυκλικό ρυθμιστικό λεπτών"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index f42a577009c1..fc37786d66dc 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Done"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 76ca7cf957c5..7d455c7a31e4 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -1840,6 +1840,8 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string> + <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotate for a better view"</string> + <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Exit split screen for a better view"</string> <string name="done_label" msgid="7283767013231718521">"Done"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index e6277647d586..7bc59d77954a 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Done"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 565cbd9a5137..997929db4543 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Done"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 2718d425c4e6..b8767b7bcca3 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -1840,6 +1840,8 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string> + <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotate for a better view"</string> + <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Exit split screen for a better view"</string> <string name="done_label" msgid="7283767013231718521">"Done"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 4e5c8f72b0fe..884b767374c2 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Visualización en pantalla completa"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Para salir, desliza el dedo hacia abajo desde la parte superior."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Entendido"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Listo"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Control deslizante circular de horas"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Control deslizante circular de minutos"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 54e22d37d09c..b26ee6a46202 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Modo de pantalla completa"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Para salir, desliza el dedo de arriba abajo."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Entendido"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Hecho"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Control deslizante circular de horas"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Control deslizante circular de minutos"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index c72713713bba..0c9559183840 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Kuvamine täisekraanil"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Väljumiseks pühkige ülevalt alla."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Selge"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Valmis"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Ringikujuline tunniliugur"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Ringikujuline minutiliugur"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index ce02af221d0f..0dffb903ae2d 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -328,19 +328,19 @@ <string name="permgroupdesc_sensors" msgid="2610631290633747752">"atzitu bizi-konstanteei buruzko sentsorearen datuak"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"Jakinarazpenak"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"jakinarazpenak erakutsi"</string> - <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Eskuratu leihoko edukia"</string> + <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Leihoko edukia eskuratu."</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Arakatu irekita daukazun leihoko edukia."</string> - <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Aktibatu \"Arakatu ukituta\""</string> + <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"\"Arakatu ukituta\" aktibatu."</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Sakatutako elementuak ozen irakurriko dira eta pantaila keinu bidez arakatu ahal izango da."</string> - <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Behatu idazten duzun testua"</string> + <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Idazten duzun testua behatu."</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Ez da salbuespenik egiten datu pertsonalekin, hala nola kreditu-txartelen zenbakiekin eta pasahitzekin."</string> - <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Kontrolatu pantailaren zoom-maila"</string> - <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Kontrolatu pantailaren zoom-maila eta posizioa."</string> - <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Egin keinuak"</string> + <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Pantailaren zoom-maila kontrolatu."</string> + <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Pantailaren zoom-maila eta posizioa kontrolatu."</string> + <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Keinuak egin."</string> <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Sakatu, lerratu, atximurkatu eta beste hainbat keinu egin ditzake."</string> <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Hatz-marken keinuak"</string> <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Gailuaren hatz-marken sentsorean egindako keinuak atzeman ditzake."</string> - <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Atera pantaila-argazki bat"</string> + <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Pantaila-argazkiak atera."</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pantaila-argazkiak atera ditzake."</string> <string name="permlab_statusBar" msgid="8798267849526214017">"desgaitu edo aldatu egoera-barra"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Egoera-barra desgaitzea edo sistema-ikonoak gehitzea edo kentzea baimentzen die aplikazioei."</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Pantaila osoko ikuspegia"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Irteteko, pasatu hatza goitik behera."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Ados"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Eginda"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Ordua aukeratzeko ikuspegi zirkularra"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Minutuak aukeratzeko ikuspegi zirkularra"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index f9fa48d386ee..20b67450682f 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -575,7 +575,7 @@ <string name="permdesc_mediaLocation" msgid="597912899423578138">"به برنامه اجازه میدهد مکانها را از مجموعه رسانهتان بخواند."</string> <string name="biometric_app_setting_name" msgid="3339209978734534457">"استفاده از زیستسنجشی"</string> <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"استفاده از زیستسنجشی یا قفل صفحه"</string> - <string name="biometric_dialog_default_title" msgid="55026799173208210">"تأیید کنید این شما هستید"</string> + <string name="biometric_dialog_default_title" msgid="55026799173208210">"تأیید کنید این شمایید"</string> <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"برای ادامه، از زیستسنجشی استفاده کنید"</string> <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"برای ادامه، از زیستسنجشی یا قفل صفحه استفاده کنید"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"سختافزار زیستسنجی دردسترس نیست"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"مشاهده در حالت تمام صفحه"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"برای خروج، انگشتتان را از بالای صفحه به پایین بکشید."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"متوجه شدم"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"تمام"</string> <string name="hour_picker_description" msgid="5153757582093524635">"لغزنده دایرهای ساعت"</string> <string name="minute_picker_description" msgid="9029797023621927294">"لغزنده دایرهای دقیقه"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index c81bf0034a60..5f27e36d7e18 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1840,6 +1840,8 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Koko ruudun tilassa"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Sulje palkki pyyhkäisemällä alas ruudun ylälaidasta."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Selvä"</string> + <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Kierrä, niin saat paremman näkymän"</string> + <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Poistu jaetulta näytöltä, niin saat paremman näkymän"</string> <string name="done_label" msgid="7283767013231718521">"Valmis"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Tuntien ympyränmuotoinen liukusäädin"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Minuuttien ympyränmuotoinen liukusäädin"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 40b265af02db..eb8a77874ee2 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Affichage plein écran"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Pour quitter, balayez vers le bas à partir du haut."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Terminé"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Curseur circulaire des heures"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Curseur circulaire des minutes"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index c2a84607706d..480a7b50030c 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Affichage en plein écran"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Pour quitter, balayez l\'écran du haut vers le bas."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"OK"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Curseur circulaire des heures"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Curseur circulaire des minutes"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 9353cf8d90e1..1e8063a0fb36 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Vendo pantalla completa"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Para saír, pasa o dedo cara abaixo desde a parte superior."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Entendido"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Feito"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Control desprazable circular das horas"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Control desprazable circular dos minutos"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index ad006c6b42a5..45f12d7b4dbd 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"પૂર્ણ સ્ક્રીન પર જુઓ"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"બહાર નીકળવા માટે, ટોચ પરથી નીચે સ્વાઇપ કરો."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"સમજાઈ ગયું"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"થઈ ગયું"</string> <string name="hour_picker_description" msgid="5153757582093524635">"કલાકનું વર્તુળાકાર સ્લાઇડર"</string> <string name="minute_picker_description" msgid="9029797023621927294">"મિનિટનું વર્તુળાકાર સ્લાઇડર"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 2a472f803ade..f145a4cc1674 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"आप पूरे स्क्रीन पर देख रहे हैं"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"बाहर निकलने के लिए, ऊपर से नीचे स्वाइप करें."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"ठीक है"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"हो गया"</string> <string name="hour_picker_description" msgid="5153757582093524635">"घंटो का चक्राकार स्लाइडर"</string> <string name="minute_picker_description" msgid="9029797023621927294">"मिनटों का चक्राकार स्लाइडर"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 72480a4054c5..e12694147e87 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Gledanje preko cijelog zaslona"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Za izlaz prijeđite prstom od vrha prema dolje."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Shvaćam"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Gotovo"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Kružni klizač sati"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Kružni klizač minuta"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index f9a93a8fbf0f..a529352a3038 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Megtekintése teljes képernyőn"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Kilépéshez csúsztassa ujját fentről lefelé."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Értem"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Kész"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Óra kör alakú csúszkája"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Perc kör alakú csúszkája"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 138d810e7ad5..825924c158ef 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Լիաէկրան դիտում"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Դուրս գալու համար վերևից սահահարվածեք դեպի ներքև:"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Պարզ է"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Պատրաստ է"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Ժամերի ընտրություն թվատախտակից"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Րոպեների ընտրություն թվատախտակից"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index b02345ac9945..8768e4d6ff97 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Melihat layar penuh"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Untuk keluar, geser layar ke bawah dari atas."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Mengerti"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Selesai"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Penggeser putar jam"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Penggeser putar menit"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 77df3312fc8d..9a27d8dc5c7d 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Notar allan skjáinn"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Strjúktu niður frá efri brún til að hætta."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Ég skil"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Lokið"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Valskífa fyrir klukkustundir"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Valskífa fyrir mínútur"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 13f265e56770..8a6ef85975a5 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Visualizzazione a schermo intero"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Per uscire, scorri dall\'alto verso il basso."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Fine"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Dispositivo di scorrimento circolare per le ore"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Dispositivo di scorrimento circolare per i minuti"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 30426c9b34f2..4ad7c2c2ad1b 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"צפייה במסך מלא"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"כדי לצאת, פשוט מחליקים אצבע מלמעלה למטה."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"הבנתי"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"סיום"</string> <string name="hour_picker_description" msgid="5153757582093524635">"מחוון שעות מעגלי"</string> <string name="minute_picker_description" msgid="9029797023621927294">"מחוון דקות מעגלי"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 2700f02278f1..917f91146fa8 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"全画面表示"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"終了するには、上から下にスワイプします。"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"完了"</string> <string name="hour_picker_description" msgid="5153757582093524635">"円形スライダー(時)"</string> <string name="minute_picker_description" msgid="9029797023621927294">"円形スライダー(分)"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 760819989871..e8b84e24cd73 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"სრულ ეკრანზე ნახვა"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"გამოსვლისათვის, გაასრიალეთ ზემოდან ქვემოთ."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"გასაგებია"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"დასრულდა"</string> <string name="hour_picker_description" msgid="5153757582093524635">"საათების წრიული სლაიდერი"</string> <string name="minute_picker_description" msgid="9029797023621927294">"წუთების წრიული სლაიდერი"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 8b27a9ef6cb9..306af3ac1ccb 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -333,7 +333,7 @@ <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Түртілген элементтерді дыбыстау функциясын қосу"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Түртілген элементтер дауыстап айтылады және экранды қимылдар арқылы зерттеуге болады."</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Терілген мәтінді тексеру"</string> - <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Несиелік карта нөмірі және құпия сөздер сияқты жеке деректі қоса."</string> + <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Несиелік карта нөмірлері және құпия сөздер сияқты жеке деректерді қамтиды."</string> <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Дисплей ұлғайтуды басқару"</string> <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Дисплейдің масштабтау деңгейін және орналастыруды басқару."</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Қимылдарды орындау"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Толық экранда көру"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Шығу үшін жоғарыдан төмен қарай сырғытыңыз."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Түсінікті"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Дайын"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Сағаттар айналымының қозғалтқышы"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Минут айналымын қозғалтқыш"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index b115d8f9ed0b..2f4b516e41f9 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -329,7 +329,7 @@ <string name="permgrouplab_notifications" msgid="5472972361980668884">"ការជូនដំណឹង"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"បង្ហាញការជូនដំណឹង"</string> <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"ទាញយកខ្លឹមសារវិនដូ"</string> - <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ពិនិត្យខ្លឹមសារវិនដូដែលអ្នកកំពុងទាក់ទងជាមួយ។"</string> + <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ពិនិត្យខ្លឹមសារវិនដូដែលអ្នកកំពុងធ្វើអន្តរកម្មជាមួយ។"</string> <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"បើកការរកមើលដោយប៉ះ"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"ធាតុដែលបានប៉ះនឹងត្រូវបានអានឮៗ ហើយអេក្រង់នោះអាចត្រូវបានស្វែងរកដោយប្រើកាយវិការ។"</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"មើលអត្ថបទដែលវាយ"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"កំពុងមើលពេញអេក្រង់"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"ដើម្បីចាកចេញ សូមអូសពីលើចុះក្រោម។"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"យល់ហើយ"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"រួចរាល់"</string> <string name="hour_picker_description" msgid="5153757582093524635">"គ្រាប់រំកិលរង្វង់ម៉ោង"</string> <string name="minute_picker_description" msgid="9029797023621927294">"គ្រាប់រំកិលរង្វង់នាទី"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index d9239373ad34..a04faa5148e3 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"ಪೂರ್ಣ ಪರದೆಯನ್ನು ವೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"ನಿರ್ಗಮಿಸಲು, ಮೇಲಿನಿಂದ ಕೆಳಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"ತಿಳಿಯಿತು"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"ಮುಗಿದಿದೆ"</string> <string name="hour_picker_description" msgid="5153757582093524635">"ಗಂಟೆಗಳ ವೃತ್ತಾಕಾರ ಸ್ಲೈಡರ್"</string> <string name="minute_picker_description" msgid="9029797023621927294">"ನಿಮಿಷಗಳ ವೃತ್ತಾಕಾರ ಸ್ಲೈಡರ್"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 4df03228c9b2..4974f9546ff1 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"전체 화면 모드"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"종료하려면 위에서 아래로 스와이프합니다."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"확인"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"완료"</string> <string name="hour_picker_description" msgid="5153757582093524635">"시간 원형 슬라이더"</string> <string name="minute_picker_description" msgid="9029797023621927294">"분 원형 슬라이더"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 27d932aac8fa..cbc29dc04fb2 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -35,7 +35,7 @@ <string name="mmiError" msgid="2862759606579822246">"Туташууда көйгөй чыкты же MMI коду жараксыз."</string> <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функция колдоого алынбайт."</string> <string name="mmiFdnError" msgid="3975490266767565852">"Иш-аракет туруктуу терүү номерлери менен гана чектелет."</string> - <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Роуминг учурунда чалууну башка номерге багыттоонун жөндөөлөрүн телефонуңуздан өзгөртүү мүмкүн эмес."</string> + <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Роуминг учурунда чалууну башка номерге багыттоонун параметрлерин телефонуңуздан өзгөртүү мүмкүн эмес."</string> <string name="serviceEnabled" msgid="7549025003394765639">"Кызмат иштетилди."</string> <string name="serviceEnabledFor" msgid="1463104778656711613">"Кызмат төмөнкү үчүн иштетилди:"</string> <string name="serviceDisabled" msgid="641878791205871379">"Кызмат өчүрүлдү."</string> @@ -77,7 +77,7 @@ <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелген"</string> <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелбейт"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Кызмат камсыздалган эмес."</string> - <string name="CLIRPermanent" msgid="166443681876381118">"Чалуучунун далдаштырма дайындары жөндөөлөрүн өзгөртө албайсыз."</string> + <string name="CLIRPermanent" msgid="166443681876381118">"Чалуучунун далдаштырма дайындары параметрлерин өзгөртө албайсыз."</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобилдик Интернет кызматы жок"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Шашылыш чалуу бөгөттөлгөн"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Аудио чалуу кызматы бөгөттөлгөн"</string> @@ -328,7 +328,7 @@ <string name="permgroupdesc_sensors" msgid="2610631290633747752">"организмдин абалына көз салган сенсордун дайындарына мүмкүнчүлүк алуу"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"Билдирмелер"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"билдирмелерди көрсөтүү"</string> - <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Терезедеги мазмунду алып турат"</string> + <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Терезедеги нерселерди алып туруу"</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Учурда ачылып турган терезедеги маалыматты талдайт."</string> <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"\"Сыйпалап изилдөө\" мүмкүнчүлүгүн иштетет"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Басылып жаткан элементтерди айтып турат жана түзмөктү жаңсоолор менен башкаруу мүмкүнчүлүгүн иштетет."</string> @@ -341,7 +341,7 @@ <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Манжа изинин жаңсоолору"</string> <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Түзмөктөгү манжа изинин сенсорунда жасалган жаңсоолорду жаздырып алат."</string> <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Скриншот тартып алуу"</string> - <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Дисплейдин скриншотун тартып алууга болот."</string> + <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Дисплейдин скриншотун тартып алсаңыз болот."</string> <string name="permlab_statusBar" msgid="8798267849526214017">"абал тилкесин өчүрүү же өзгөртүү"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Колдонмого абал тилкесин өчүрүү же тутум сүрөтчөлөрүн кошуу же алып салуу мүмкүнчүлүгүн берет."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"абал тилкесинин милдетин аткаруу"</string> @@ -403,7 +403,7 @@ <string name="permlab_getPackageSize" msgid="375391550792886641">"колдонмо сактагычынын мейкиндигин өлчөө"</string> <string name="permdesc_getPackageSize" msgid="742743530909966782">"Колдонмого өз кодун, дайындарын жана кэш өлчөмдөрүн түшүрүп алуу мүмкүнчүлүгүн берет"</string> <string name="permlab_writeSettings" msgid="8057285063719277394">"система тууралоолорун өзгөртүү"</string> - <string name="permdesc_writeSettings" msgid="8293047411196067188">"Колдонмого системанын коопсуздук жөндөөлөрүнүн дайындарын өзгөртүү мүмкүнчүлүгүн берет. Кесепттүү колдонмолор тутумуңуздун конфигурациясын бузуп салышы мүмкүн."</string> + <string name="permdesc_writeSettings" msgid="8293047411196067188">"Колдонмого системанын коопсуздук параметрлеринин дайындарын өзгөртүү мүмкүнчүлүгүн берет. Кесепттүү колдонмолор тутумуңуздун конфигурациясын бузуп салышы мүмкүн."</string> <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"түзмөктү жандырганда иштеп баштоо"</string> <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"Колдонмого тутум жүктөлүп бүтөөрү менен өзүн-өзү иштетүү мүмкүнчүлүгүн берет. Бул планшеттин ишке киргизилишин кыйла создуктуруп, планшеттин үзгүлтүксүз иштешин жайлатып салышы мүмкүн."</string> <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"Тутум күйгүзүлөрү менен колдонмого өз алдынча иштеп баштоого уруксат берет. Ага байланыштуу Android TV түзмөгүңүз кечирээк күйгүзүлүп, ошондой эле колдонмо такай иштеп тургандыктан, түзмөк жайыраак иштеп калышы мүмкүн."</string> @@ -446,7 +446,7 @@ <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Колдонмо кайда жүргөнүңүздү активдүү режимде гана болжолдуу аныктай алат. Ал үчүн түзмөгүңүздө жайгашкан жерди аныктоо кызматын иштетишиңиз керек."</string> <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"жайгашкан жерди фондо аныктоо"</string> <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Колдонмо кайда жүргөнүңүздү активдүү режимде гана эмес, фондук режимде да аныктай алат."</string> - <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"аудио жөндөөлөрүңүздү өзгөртүңүз"</string> + <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"аудио параметрлериңизди өзгөртүңүз"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Колдонмого үн деңгээли жана кайсы динамик аркылуу үн чыгарылышы керек сыяктуу түзмөктүн аудио тууралоолорун өзгөртүүгө уруксат берет."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"аудио жаздыруу"</string> <string name="permdesc_recordAudio" msgid="5857246765327514062">"Бул колдонмо иштеп жатканда микрофон менен аудио файлдарды жаздыра алат."</string> @@ -687,7 +687,7 @@ </string-array> <string name="face_error_vendor_unknown" msgid="7387005932083302070">"Бир жерден ката кетти. Кайра аракет кылыңыз."</string> <string name="face_icon_content_description" msgid="465030547475916280">"Жүздүн сүрөтчөсү"</string> - <string name="permlab_readSyncSettings" msgid="6250532864893156277">"шайкештирүү жөндөөлөрүн окуу"</string> + <string name="permlab_readSyncSettings" msgid="6250532864893156277">"шайкештирүү параметрлерин окуу"</string> <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Колдонмого эсеп менен синхрондошуу тууралоолорун окуганга уруксат берет. Мисалы, Кишилер колдонмосу эсеп менен синхрондошкондугун аныктай алат."</string> <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"синхрондоштурууну өчүрүү/жандыруу"</string> <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Колдонмого эсеп менен синхрондошуу тууралоолорун өзгөртүү уруксатын берет. Мисалы, бул Кишилер колдонмосун эсеп менен синхрондошуусун иштете алат."</string> @@ -1229,7 +1229,7 @@ <string name="launch_warning_original" msgid="3332206576800169626">"Башында <xliff:g id="APP_NAME">%1$s</xliff:g> жүргүзүлгөн."</string> <string name="screen_compat_mode_scale" msgid="8627359598437527726">"Шкала"</string> <string name="screen_compat_mode_show" msgid="5080361367584709857">"Ар дайым көрүнсүн"</string> - <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Муну тутум жөндөөлөрүнөн кайра иштетүү > Колдонмолор > Жүктөлүп алынган."</string> + <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Муну тутум параметрлеринен кайра иштетүү > Колдонмолор > Жүктөлүп алынган."</string> <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу көрүнүштүн тандалган өлчөмүн экранда көрсөтө албайт жана туура эмес иштеши мүмкүн."</string> <string name="unsupported_display_size_show" msgid="980129850974919375">"Ар дайым көрүнсүн"</string> <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> Android OS тутуму менен иштеген түзмөктүн шайкеш келбеген версиясы үчүн орнотулган колдонмо жана туура эмес иштеши мүмкүн. Колдонмонун жаңырган версиясы жеткиликтүү болушу мүмкүн."</string> @@ -1498,7 +1498,7 @@ <string name="vpn_lockdown_connected" msgid="2853127976590658469">"Туташты"</string> <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"Ар дайым иштеген VPN\'ден ажыратуу"</string> <string name="vpn_lockdown_error" msgid="4453048646854247947">"Ар дайым күйүк VPN\'ге туташпай калды"</string> - <string name="vpn_lockdown_config" msgid="8331697329868252169">"Тармакты же VPN жөндөөлөрүн өзгөртүү"</string> + <string name="vpn_lockdown_config" msgid="8331697329868252169">"Тармакты же VPN параметрлерин өзгөртүү"</string> <string name="upload_file" msgid="8651942222301634271">"Файл тандоо"</string> <string name="no_file_chosen" msgid="4146295695162318057">"Эч файл тандалган жок"</string> <string name="reset" msgid="3865826612628171429">"Баштапкы абалга келтирүү"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Толук экран режими"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Чыгуу үчүн экранды ылдый сүрүп коюңуз."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Түшүндүм"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Даяр"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Саат жебеси"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Мүнөт жебеси"</string> @@ -2064,7 +2068,7 @@ <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Көбүрөөк маалымат алып, өзгөртүү үчүн таптаңыз."</string> <string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\"Тынчымды алба\" режими өзгөрдү"</string> <string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Бөгөттөлгөн нерселерди көрүү үчүн таптаңыз."</string> - <string name="review_notification_settings_title" msgid="5102557424459810820">"Билдирмелердин жөндөөлөрүн карап чыгуу"</string> + <string name="review_notification_settings_title" msgid="5102557424459810820">"Билдирмелердин параметрлерин карап чыгуу"</string> <string name="review_notification_settings_text" msgid="5916244866751849279">"Android 13 версиясынан баштап билдирмелерди жөнөтүү үчүн орноткон колдонмолоруңузга уруксат берүү керек. Учурдагы колдонмолор үчүн бул уруксатты өзгөртүү үчүн таптап коюңуз."</string> <string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Кийинчерээк эскертүү"</string> <string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Жабуу"</string> @@ -2270,7 +2274,7 @@ <string name="config_pdp_reject_user_authentication_failed" msgid="4531693033885744689"></string> <string name="config_pdp_reject_service_not_subscribed" msgid="8190338397128671588"></string> <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="6024904218067254186"></string> - <string name="window_magnification_prompt_title" msgid="2876703640772778215">"Чоңойтуу функциясынын жаңы жөндөөлөрү"</string> + <string name="window_magnification_prompt_title" msgid="2876703640772778215">"Чоңойтуу функциясынын жаңы параметрлери"</string> <string name="window_magnification_prompt_content" msgid="8159173903032344891">"Эми экрандын бир бөлүгүн чоңойто аласыз"</string> <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Жөндөөлөрдөн күйгүзүү"</string> <string name="dismiss_action" msgid="1728820550388704784">"Жабуу"</string> @@ -2281,7 +2285,7 @@ <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Сенсордун купуялыгы"</string> <string name="splash_screen_view_icon_description" msgid="180638751260598187">"Колдонмонун сүрөтчөсү"</string> <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Колдонмонун брендинин сүрөтү"</string> - <string name="view_and_control_notification_title" msgid="4300765399209912240">"Кирүү мүмкүнчүлүгүнүн жөндөөлөрүн текшериңиз"</string> + <string name="view_and_control_notification_title" msgid="4300765399209912240">"Кирүү мүмкүнчүлүгүнүн параметрлерин текшериңиз"</string> <string name="view_and_control_notification_content" msgid="8003766498562604034">"<xliff:g id="SERVICE_NAME">%s</xliff:g> экраныңызды көрүп, көзөмөлдөй алат. Көрүү үчүн таптап коюңуз."</string> <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Билдирүү (<xliff:g id="MESSAGE">%1$s</xliff:g>) которулду."</string> <string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Билдирүү <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> тилинен <xliff:g id="TO_LANGUAGE">%2$s</xliff:g> тилине которулду."</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index a172576284b5..09b8bfc99827 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"ການເບິ່ງເຕັມໜ້າຈໍ"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"ຫາກຕ້ອງການອອກ, ໃຫ້ຮູດຈາກທາງເທິງລົງມາທາງລຸ່ມ."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"ໄດ້ແລ້ວ"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"ແລ້ວໆ"</string> <string name="hour_picker_description" msgid="5153757582093524635">"ໂຕໝຸນປັບຊົ່ວໂມງ"</string> <string name="minute_picker_description" msgid="9029797023621927294">"ໂຕໝຸນປັບນາທີ"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 6da22edba445..8f5e7fba720a 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1842,6 +1842,8 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Peržiūrima viso ekrano režimu"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Jei norite išeiti, perbraukite žemyn iš viršaus."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Supratau"</string> + <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Pasukite, kad geriau matytumėte vaizdą"</string> + <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Išeikite iš išskaidyto ekrano režimo, kad geriau matytumėte vaizdą"</string> <string name="done_label" msgid="7283767013231718521">"Atlikta"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Apskritas valandų šliaužiklis"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Apskritas minučių šliaužiklis"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index aeb89a29884a..4edf9682a230 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Skatīšanās pilnekrāna režīmā"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Lai izietu, no augšdaļas velciet lejup."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Labi"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Gatavs"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Stundu apļveida slīdnis"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Minūšu apļveida slīdnis"</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index b0a2bbd953b4..dbbd58554401 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -328,20 +328,20 @@ <string name="permgroupdesc_sensors" msgid="2610631290633747752">"пристапува до податоците од сензорите за виталните знаци"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"Известувања"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"да прикажува известувања"</string> - <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Преземе содржина на прозорец"</string> - <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Ја следи содржината на прозорецот со кој се комуницира."</string> - <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Вклучи „Истражувај со допир“"</string> - <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Допрените ставки ќе се изговорат на глас и екранот може да се истражува со движења."</string> - <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Го следи напишаниот текст"</string> - <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Опфаќа лични податоци како што се броеви на кредитни картички и лозинки."</string> - <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Го контролира зголемувањето на екранот"</string> - <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Го контролира нивото на зумирање и позиционирање на екранот."</string> + <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"да ги вчитува содржините од прозорците"</string> + <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"да ги проверува содржините од прозорецот што го користите"</string> + <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"да вклучи „Истражувај со допир“"</string> + <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"допрените ставки ќе се изговараат наглас и екранот ќе може да се истражува со движења"</string> + <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"да го следи текстот што го пишувате"</string> + <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"вклучително и лични податоци како што се броеви на кредитни картички и лозинки"</string> + <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"да го контролира зголемувањето на екранот"</string> + <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"да го контролира нивото на зумирање и позиционирањето на екранот"</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Користете движења"</string> <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Може да допрете, повлечете, штипнете и да користите други движења."</string> <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Движења за отпечатоци"</string> <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Може да сними движења што се направени на сензорот за отпечатоци на уредот."</string> <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Зачувување слика од екранот"</string> - <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да направи слика од екранот."</string> + <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да зачува слика од екранот."</string> <string name="permlab_statusBar" msgid="8798267849526214017">"оневозможи или измени статусна лента"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Дозволува апликацијата да ја оневозможи статусната лента или да додава или отстранува системски икони."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"да стане статусна лента"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Се прикажува на цел екран"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"За да излезете, повлечете одозгора надолу."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Сфатив"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Готово"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Приказ на часови во кружно движење"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Приказ на минути во кружно движење"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 1b7daec70c33..b59c32200d61 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"പൂർണ്ണ സ്ക്രീനിൽ കാണുന്നു"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"അവസാനിപ്പിക്കാൻ, മുകളിൽ നിന്ന് താഴോട്ട് സ്വൈപ്പ് ചെയ്യുക."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"മനസ്സിലായി"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"പൂർത്തിയാക്കി"</string> <string name="hour_picker_description" msgid="5153757582093524635">"ചാക്രികമായി മണിക്കൂറുകൾ ദൃശ്യമാകുന്ന സ്ലൈഡർ"</string> <string name="minute_picker_description" msgid="9029797023621927294">"ചാക്രികമായി മിനിറ്റുകൾ ദൃശ്യമാകുന്ന സ്ലൈഡർ"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index dd6b40d1da38..029358b14d13 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Бүтэн дэлгэцээр үзэж байна"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Гарахаар бол дээрээс нь доош нь чирнэ үү."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Ойлголоо"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Дууссан"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Цаг гүйлгэгч"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Минут гүйлгэгч"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 6be015aaa3c6..ad224693220a 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"पूर्ण स्क्रीनवर पाहत आहात"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"बाहेर पडण्यासाठी, वरून खाली स्वाइप करा."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"समजले"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"पूर्ण झाले"</string> <string name="hour_picker_description" msgid="5153757582093524635">"तास परिपत्रक स्लायडर"</string> <string name="minute_picker_description" msgid="9029797023621927294">"मिनिटे परिपत्रक स्लायडर"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 8ac73734752c..c038505f61b1 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Melihat skrin penuh"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Untuk keluar, leret dari atas ke bawah."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Faham"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Selesai"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Penggelangsar bulatan jam"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Penggelangsar bulatan minit"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 8603b280e058..badd048647c4 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"မျက်နှာပြင်အပြည့် ကြည့်နေသည်"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"ထွက်ရန် အပေါ်မှ အောက်သို့ ဆွဲချပါ။"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"ရပါပြီ"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"ပြီးပါပြီ"</string> <string name="hour_picker_description" msgid="5153757582093524635">"နာရီရွေးချက်စရာ"</string> <string name="minute_picker_description" msgid="9029797023621927294">"မိနစ်လှည့်သော ရွေ့လျားတန်"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index fe9f9155688a..023e48f753c3 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Visning i fullskjerm"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Sveip ned fra toppen for å avslutte."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Skjønner"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Ferdig"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Sirkulær glidebryter for timer"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Sirkulær glidebryter for minutter"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index e21110e0b0c4..f789ed616eba 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -334,8 +334,8 @@ <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"ट्याप गरिएका वस्तुहरू चर्को स्वरमा बोलिने छन् र इसाराहरूको प्रयोग गरेर स्क्रिनमा अन्वेषण गर्न सकिन्छ।"</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"आफुले टाइप गरेको पाठको निरीक्षण गर्नुहोस्"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"व्यक्तिगत डेटा जस्तै क्रेडिट कार्ड नम्बरहरू र पासवर्डहरू समावेश गर्दछ।"</string> - <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"प्रदर्शन आवर्धन नियन्त्रण गर्नुहोस्"</string> - <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"प्रदर्शनको जुम स्तर र स्थिति नियन्त्रण गर्नुहोस्।"</string> + <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"डिस्प्ले म्याग्निफिकेसन नियन्त्रण गर्नुहोस्"</string> + <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"डिस्प्लेको जुम लेबल र स्थिति नियन्त्रण गर्नुहोस्।"</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"इसाराहरू सम्बन्धी कार्य गर्नुहोस्"</string> <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"ट्याप, स्वाइप गर्न, थिच्न र अन्य इसाराहरू सम्बन्धी कार्य गर्न सक्छ"</string> <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"फिंगरप्रिन्टका इसाराहरू"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"पूरा पर्दा हेर्दै"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"बाहिर निस्कन, माथिबाट तल स्वाइप गर्नुहोस्।"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"बुझेँ"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"भयो"</string> <string name="hour_picker_description" msgid="5153757582093524635">"घन्टा गोलाकार स्लाइडर"</string> <string name="minute_picker_description" msgid="9029797023621927294">"मिनेट गोलाकार स्लाइडर"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 1f380e18c255..7cc02fed175a 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -231,7 +231,7 @@ <string name="shutdown_progress" msgid="5017145516412657345">"Uitzetten…"</string> <string name="shutdown_confirm" product="tablet" msgid="2872769463279602432">"Je tablet wordt uitgezet."</string> <string name="shutdown_confirm" product="tv" msgid="7975942887313518330">"Je Android TV-apparaat wordt uitgezet."</string> - <string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"Je horloge wordt uitgezet."</string> + <string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"Je smartwatch wordt uitgezet."</string> <string name="shutdown_confirm" product="default" msgid="136816458966692315">"Je telefoon wordt uitgezet."</string> <string name="shutdown_confirm_question" msgid="796151167261608447">"Wil je afsluiten?"</string> <string name="reboot_safemode_title" msgid="5853949122655346734">"Opnieuw opstarten in veilige modus"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Volledig scherm wordt getoond"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Swipe omlaag vanaf de bovenkant van het scherm om af te sluiten."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Ik snap het"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Klaar"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Ronde schuifregelaar voor uren"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Ronde schuifregelaar voor minuten"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 83cb868fb17f..b8f017ed465c 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -1077,13 +1077,13 @@ <string name="menu_space_shortcut_label" msgid="5949311515646872071">"ସ୍ପେସ୍"</string> <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"ଏଣ୍ଟର୍"</string> <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"ଡିଲିଟ୍ କରନ୍ତୁ"</string> - <string name="search_go" msgid="2141477624421347086">"ସନ୍ଧାନ କରନ୍ତୁ"</string> - <string name="search_hint" msgid="455364685740251925">"ସନ୍ଧାନ…"</string> - <string name="searchview_description_search" msgid="1045552007537359343">"ସନ୍ଧାନ କରନ୍ତୁ"</string> + <string name="search_go" msgid="2141477624421347086">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string> + <string name="search_hint" msgid="455364685740251925">"ସର୍ଚ୍ଚ କରନ୍ତୁ…"</string> + <string name="searchview_description_search" msgid="1045552007537359343">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string> <string name="searchview_description_query" msgid="7430242366971716338">"କ୍ୱେରୀ ସର୍ଚ୍ଚ କରନ୍ତୁ"</string> <string name="searchview_description_clear" msgid="1989371719192982900">"କ୍ୱେରୀ ଖାଲି କରନ୍ତୁ"</string> <string name="searchview_description_submit" msgid="6771060386117334686">"କ୍ୱେରୀ ଦାଖଲ କରନ୍ତୁ"</string> - <string name="searchview_description_voice" msgid="42360159504884679">"ଭଏସ୍ ସର୍ଚ୍ଚ"</string> + <string name="searchview_description_voice" msgid="42360159504884679">"ଭଏସ ସର୍ଚ୍ଚ"</string> <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ସକ୍ଷମ କରିବେ?"</string> <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ସକ୍ଷମ କରିବାକୁ ଚାହେଁ। ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ଅନ୍ ଥିବାବେଳେ, ଆପଣଙ୍କ ଆଙ୍ଗୁଠି ତଳେ କ’ଣ ଅଛି, ତାହାର ବ୍ୟାଖ୍ୟା ଦେଖିପାରିବେ କିମ୍ବା ଟାବ୍ଲେଟ୍ ସହ କଥାବାର୍ତ୍ତା କରିବାକୁ ଜେଶ୍ଚର୍ କରିପାରିବେ।"</string> <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ସକ୍ଷମ କରିବାକୁ ଚାହେଁ। ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ଅନ୍ ଥିବାବେଳେ, ଆପଣଙ୍କ ଆଙ୍ଗୁଠି ତଳେ କ’ଣ ଅଛି, ତାହାର ବ୍ୟାଖ୍ୟା ଦେଖିପାରିବେ କିମ୍ବା ଫୋନ୍ ସହ କଥାବାର୍ତ୍ତା କରିବାକୁ ଜେଶ୍ଚର୍ କରିପାରିବେ।"</string> @@ -1463,7 +1463,7 @@ <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ଜୁମ୍ ନିୟନ୍ତ୍ରଣ ପାଇଁ ଦୁଇଥର ଟାପ୍ କରନ୍ତୁ"</string> <string name="gadget_host_error_inflating" msgid="2449961590495198720">"ୱିଜେଟ୍ ଯୋଡ଼ିପାରିବ ନାହିଁ।"</string> <string name="ime_action_go" msgid="5536744546326495436">"ଯାଆନ୍ତୁ"</string> - <string name="ime_action_search" msgid="4501435960587287668">"ସନ୍ଧାନ କରନ୍ତୁ"</string> + <string name="ime_action_search" msgid="4501435960587287668">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string> <string name="ime_action_send" msgid="8456843745664334138">"ପଠାନ୍ତୁ"</string> <string name="ime_action_next" msgid="4169702997635728543">"ପରବର୍ତ୍ତୀ"</string> <string name="ime_action_done" msgid="6299921014822891569">"ହୋଇଗଲା"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ଦେଖାଯାଉଛି"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"ବାହାରିବା ପାଇଁ, ଉପରୁ ତଳକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ।"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"ବୁଝିଗଲି"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"ହୋଇଗଲା"</string> <string name="hour_picker_description" msgid="5153757582093524635">"ଘଣ୍ଟା ସର୍କୁଲାର୍ ସ୍ଲାଇଡର୍"</string> <string name="minute_picker_description" msgid="9029797023621927294">"ମିନିଟ୍ସ ସର୍କୁଲାର୍ ସ୍ଲାଇଡର୍"</string> @@ -1931,7 +1935,7 @@ <string name="language_picker_regions_section_suggested" msgid="6080131515268225316">"ପ୍ରସ୍ତାବିତ"</string> <string name="language_picker_section_all" msgid="1985809075777564284">"ସମସ୍ତ ଭାଷା"</string> <string name="region_picker_section_all" msgid="756441309928774155">"ସମସ୍ତ ଅଞ୍ଚଳ"</string> - <string name="locale_search_menu" msgid="6258090710176422934">"ସନ୍ଧାନ କରନ୍ତୁ"</string> + <string name="locale_search_menu" msgid="6258090710176422934">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string> <string name="app_suspended_title" msgid="888873445010322650">"ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ"</string> <string name="app_suspended_default_message" msgid="6451215678552004172">"ବର୍ତ୍ତମାନ <xliff:g id="APP_NAME_0">%1$s</xliff:g> ଉପଲବ୍ଧ ନାହିଁ। ଏହା <xliff:g id="APP_NAME_1">%2$s</xliff:g> ଦ୍ଵାରା ପରିଚାଳିତ ହେଉଛି।"</string> <string name="app_suspended_more_details" msgid="211260942831587014">"ଅଧିକ ଜାଣନ୍ତୁ"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 1cb12c276bf6..66aca4fd8836 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -328,7 +328,7 @@ <string name="permgroupdesc_sensors" msgid="2610631290633747752">"ਆਪਣੇ ਸਰੀਰ ਦੇ ਅਹਿਮ ਚਿੰਨ੍ਹਾਂ ਬਾਰੇ ਸੰਵੇਦਕ ਡਾਟਾ ਤੱਕ ਪਹੁੰਚ ਕਰਨ"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"ਸੂਚਨਾਵਾਂ"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"ਸੂਚਨਾਵਾਂ ਦਿਖਾਓ"</string> - <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"ਵਿੰਡੋ ਸਮੱਗਰੀ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਨਾ"</string> + <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"ਵਿੰਡੋ ਸਮੱਗਰੀ ਮੁੜ-ਪ੍ਰਾਪਤ ਕਰਨਾ"</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ਉਸ ਵਿੰਡੋ ਸਮੱਗਰੀ ਦੀ ਜਾਂਚ ਕਰੋ, ਜਿਸ ਨਾਲ ਤੁਸੀਂ ਅੰਤਰਕਿਰਿਆ ਕਰ ਰਹੇ ਹੋ"</string> <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"\'ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ\' ਚਾਲੂ ਕਰਨਾ"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"ਟੈਪ ਕੀਤੀਆਂ ਆਈਟਮਾਂ ਨੂੰ ਉੱਚੀ ਆਵਾਜ਼ ਵਿੱਚ ਬੋਲਿਆ ਜਾਵੇਗਾ ਅਤੇ ਸਕ੍ਰੀਨ ਦੀ ਸੰਕੇਤਾਂ ਦੀ ਵਰਤੋਂ ਨਾਲ ਪੜਚੋਲ ਕੀਤੀ ਜਾ ਸਕਦੀ ਹੈ।"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"ਪੂਰੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦੇਖੋ"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"ਬਾਹਰ ਜਾਣ ਲਈ, ਉਪਰੋਂ ਹੇਠਾਂ ਸਵਾਈਪ ਕਰੋ।"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"ਸਮਝ ਲਿਆ"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"ਹੋ ਗਿਆ"</string> <string name="hour_picker_description" msgid="5153757582093524635">"ਘੰਟੇ ਸਰਕੁਲਰ ਸਲਾਈਡਰ"</string> <string name="minute_picker_description" msgid="9029797023621927294">"ਮਿੰਟ ਸਰਕੁਲਰ ਸਲਾਈਡਰ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index a2c06b787bfb..61e3217f2669 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1842,6 +1842,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Włączony pełny ekran"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Aby wyjść, przesuń palcem z góry na dół."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Gotowe"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Kołowy suwak godzin"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Kołowy suwak minut"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 9768208e5171..91f0a0720f3a 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Visualização em tela cheia"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize de cima para baixo."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Entendi"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Concluído"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Controle deslizante circular das horas"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Controle deslizante circular dos minutos"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 6d7f3915a7a1..03bc7057fa93 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Visualização de ecrã inteiro"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Concluído"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Controlo de deslize circular das horas"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Controlo de deslize circular dos minutos"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 9768208e5171..91f0a0720f3a 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Visualização em tela cheia"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize de cima para baixo."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Entendi"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Concluído"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Controle deslizante circular das horas"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Controle deslizante circular dos minutos"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 83fba3f85538..38ba48dd62c5 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1841,6 +1841,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Vizualizare pe ecran complet"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Pentru a ieși, glisează de sus în jos."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Am înțeles"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Terminat"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Selector circular pentru ore"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Selector circular pentru minute"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 713e67da475a..93cbae2f7fcd 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1842,6 +1842,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Полноэкранный режим"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Чтобы выйти, проведите по экрану сверху вниз."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"ОК"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Готово"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Выбор часов на циферблате"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Выбор минут на циферблате"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 8605c679d55b..d79d0b8fbe2a 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"මුළු තිරය බලමින්"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"ඉවත් වීමට, ඉහළ සිට පහළට ස්වයිප් කරන්න"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"වැටහුණි"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"අවසන්"</string> <string name="hour_picker_description" msgid="5153757582093524635">"පැය කවාකාර සර්පනය"</string> <string name="minute_picker_description" msgid="9029797023621927294">"මිනිත්තු කවාකාර සර්පනය"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 01e57e130420..092827f30fa8 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -335,7 +335,7 @@ <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Zapnúť funkciu Preskúmanie dotykom"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Po klepnutí na položku sa vysloví jej názov a obrazovku je možné preskúmať pomocou gest."</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Sledovať zadávaný text"</string> - <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Sledovanie zahŕňa osobné údaje ako sú čísla kreditných kariet a heslá."</string> + <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Sledovanie zahŕňa osobné údaje, ako sú čísla kreditných kariet a heslá."</string> <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Ovládať priblíženie obrazovky"</string> <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Ovládajte umiestnenie a úroveň priblíženia obrazovky."</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Gestá"</string> @@ -1842,6 +1842,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Zobrazenie na celú obrazovku"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Ukončíte potiahnutím zhora nadol."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Dobre"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Hotovo"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Kruhový posúvač hodín"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Kruhový posúvač minút"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index e0026a689768..f5578237ab41 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1842,6 +1842,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Vklopljen je celozaslonski način"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Zaprete ga tako, da z vrha s prstom povlečete navzdol."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Razumem"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Dokončano"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Okrogli drsnik za ure"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Okrogli drsnik za minute"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 7a9f8a3b89a0..7875ea25d26a 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -328,20 +328,20 @@ <string name="permgroupdesc_sensors" msgid="2610631290633747752">"qasu tek të dhënat e sensorëve rreth shenjave të tua jetësore"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"Njoftimet"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"shfaq njoftimet"</string> - <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Nxjerrë përmbajtjen e dritares"</string> + <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Të nxjerrë përmbajtjen e dritares"</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspekton përmbajtjen e dritares me të cilën po ndërvepron."</string> - <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Aktivizojë funksionin \"Eksploro me prekje\""</string> + <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Të aktivizojë veçorinë \"Eksploro me prekje\""</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Artikujt e trokitur do të lexohen me zë të lartë dhe ekrani mund të eksplorohet duke përdorur gjestet."</string> - <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Vëzhgojë tekstin që shkruan"</string> + <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Të vëzhgojë tekstin që shkruan"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Përfshin të dhëna personale, si numrat e kartave të kreditit dhe fjalëkalimet."</string> - <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Kontrollo zmadhimin e ekranit"</string> - <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Kontrollo nivelin dhe pozicionimin e zmadhimit të ekranit."</string> + <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Të kontrollojë zmadhimin e ekranit"</string> + <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Kontrollon nivelin dhe pozicionimin e zmadhimit të ekranit."</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Kryen gjeste"</string> <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Mund të trokasë, rrëshqasë, bashkojë gishtat dhe kryejë gjeste të tjera."</string> <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gjestet e gjurmës së gishtit"</string> <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Mund të regjistrojë gjestet e kryera në sensorin e gjurmës së gishtit të pajisjes."</string> <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Nxirr një pamje të ekranit"</string> - <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Mund të nxirret një pamje e ekranit."</string> + <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Mund të nxjerrë një pamje e ekranit."</string> <string name="permlab_statusBar" msgid="8798267849526214017">"çaktivizo ose modifiko shiritin e statusit"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Lejon aplikacionin të çaktivizojë shiritin e statusit dhe të heqë ikonat e sistemit."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"të bëhet shiriti i statusit"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Po shikon ekranin e plotë"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Për të dalë, rrëshqit nga lart poshtë."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"E kuptova"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"U krye"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Rrëshqitësi rrethor i orëve"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Rrëshqitësi rrethor i minutave"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index f606c8f2c907..bf8c0f76d5ee 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1841,6 +1841,8 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Приказује се цео екран"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Да бисте изашли, превуците надоле одозго."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Важи"</string> + <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Ротирајте ради бољег приказа"</string> + <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Изађите из подељеног екрана ради бољег приказа"</string> <string name="done_label" msgid="7283767013231718521">"Готово"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Кружни клизач за сате"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Кружни клизач за минуте"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index fb9a3132debe..7e920d8b3500 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -341,7 +341,7 @@ <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingeravtrycksrörelser"</string> <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan registrera rörelser som utförs med hjälp av enhetens fingeravtryckssensor."</string> <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ta skärmbild"</string> - <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Du kan ta en skärmbild av skärmen."</string> + <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Kan ta en skärmbild av skärmen."</string> <string name="permlab_statusBar" msgid="8798267849526214017">"inaktivera eller ändra statusfält"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Tillåter att appen inaktiverar statusfältet eller lägger till och tar bort systemikoner."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"visas i statusfältet"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Visar på fullskärm"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Svep nedåt från skärmens överkant för att avsluta."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Klart"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Cirkelreglage för timmar"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Cirkelreglage för minuter"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 180293f84809..d380436066ef 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Unatazama skrini nzima"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Ili kuondoka, telezesha kidole kutoka juu hadi chini."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Nimeelewa"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Imekamilika"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Kitelezi cha mviringo wa saa"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Kitelezi cha mviringo wa dakika"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 186dab33c791..aee945de527a 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -332,7 +332,7 @@ <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"நீங்கள் பணியாற்றிக் கொண்டிருக்கும் சாளரத்தின் உள்ளடக்கத்தைப் பார்க்கலாம்."</string> <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"தொடுவதன் மூலம் அறிவதை இயக்கும்"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"தட்டிய உள்ளடக்கம் சத்தமாகப் படிக்கப்படும், சைகைகளைப் பயன்படுத்தி திரையில் உலாவலாம்."</string> - <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"நீங்கள் தட்டச்சு செய்யும் உரையைக் கவனிக்கும்"</string> + <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"நீங்கள் டைப் செய்யும் வார்த்தையைக் கவனிக்கும்."</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"கிரெடிட் கார்டு எண்கள் மற்றும் கடவுச்சொற்கள் போன்ற தனிப்பட்ட தகவலும் உள்ளடங்கும்."</string> <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"திரை பெரிதாவதைக் கட்டுப்படுத்தும்"</string> <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"திரையின் ஜூம் அளவையும் நிலையையும் கட்டுப்படுத்தலாம்."</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"முழுத் திரையில் காட்டுகிறது"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"வெளியேற, மேலிருந்து கீழே ஸ்வைப் செய்யவும்"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"புரிந்தது"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"முடிந்தது"</string> <string name="hour_picker_description" msgid="5153757582093524635">"மணிநேர வட்ட வடிவ ஸ்லைடர்"</string> <string name="minute_picker_description" msgid="9029797023621927294">"நிமிடங்களுக்கான வட்டவடிவ ஸ்லைடர்"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 7cebe2729adb..7d6b3cd221f7 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -330,7 +330,7 @@ <string name="permgroupdesc_notifications" msgid="4608679556801506580">"నోటిఫికేషన్లను చూపండి"</string> <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"విండో కంటెంట్ను తిరిగి పొందుతుంది"</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"మీరు పరస్పర చర్య చేస్తున్న విండో కంటెంట్ను పరిశీలిస్తుంది."</string> - <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"తాకడం ద్వారా విశ్లేషణను ప్రారంభిస్తుంది"</string> + <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"తాకడం ద్వారా విశ్లేషణను ఆన్ చేయండి"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"నొక్కిన అంశాలు బిగ్గరగా చదివి వినిపించబడతాయి మరియు సంజ్ఞలను ఉపయోగించి స్క్రీన్ను విశ్లేషించవచ్చు."</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"మీరు టైప్ చేస్తున్న వచనాన్ని పరిశీలిస్తుంది"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"క్రెడిట్ కార్డు నంబర్లు మరియు పాస్వర్డ్ల వంటి వ్యక్తిగత డేటాను కలిగి ఉంటుంది."</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"ఫుల్-స్క్రీన్లో వీక్షిస్తున్నారు"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"నిష్క్రమించడానికి, పై నుండి క్రిందికి స్వైప్ చేయండి."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"అర్థమైంది"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"పూర్తయింది"</string> <string name="hour_picker_description" msgid="5153757582093524635">"గంటల వృత్తాకార స్లయిడర్"</string> <string name="minute_picker_description" msgid="9029797023621927294">"నిమిషాల వృత్తాకార స్లయిడర్"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index a14b5742481d..852aef06b2dd 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"กำลังดูแบบเต็มหน้าจอ"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"หากต้องการออก ให้เลื่อนลงจากด้านบน"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"รับทราบ"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"เสร็จสิ้น"</string> <string name="hour_picker_description" msgid="5153757582093524635">"ตัวเลื่อนหมุนระบุชั่วโมง"</string> <string name="minute_picker_description" msgid="9029797023621927294">"ตัวเลื่อนหมุนระบุนาที"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 349a39b7f662..7540fcdbb0ce 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Panonood sa full screen"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Upang lumabas, mag-swipe mula sa itaas pababa."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Nakuha ko"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Tapos na"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Pabilog na slider ng mga oras"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Pabilog na slider ng mga minuto"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 4e8c5ef7b5dd..10501d661581 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Tam ekran olarak görüntüleme"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Çıkmak için yukarıdan aşağıya doğru hızlıca kaydırın."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Anladım"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Bitti"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Saat kaydırma çemberi"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Dakika kaydırma çemberi"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 6f63a2c9e37d..eecc6e28535d 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -333,7 +333,7 @@ <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Отримувати вміст вікна"</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Перевіряти вміст вікна, з яким ви взаємодієте."</string> <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Увімкнути функцію дослідження дотиком"</string> - <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Активувати голосові підказки для елементів, яких торкаються, і користуватися інтерфейсом за допомогою жестів."</string> + <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Озвучувати елементи, яких торкаються, і здійснювати навігацію екраном за допомогою жестів."</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Переглядати текст, який ви вводите"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Включає особисті дані, як-от номери кредитних карток і паролі."</string> <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Контролювати збільшення екрана"</string> @@ -1842,6 +1842,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Перегляд на весь екран"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Щоб вийти, проведіть пальцем зверху вниз."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Готово"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Вибір годин на циферблаті"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Вибір хвилин на циферблаті"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index e3783c32af4a..feea70c2a2dd 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"پوری اسکرین میں دیکھ رہے ہیں"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"خارج ہونے کیلئے اوپر سے نیچے سوائپ کریں۔"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"سمجھ آ گئی"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"ہو گیا"</string> <string name="hour_picker_description" msgid="5153757582093524635">"گھنٹوں کا سرکلر سلائیڈر"</string> <string name="minute_picker_description" msgid="9029797023621927294">"منٹس سرکلر سلائیڈر"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 4a23c25827f9..8d3bedf9e860 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -1840,6 +1840,8 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Butun ekranli rejim"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Chiqish uchun tepadan pastga torting."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string> + <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Yaxshiroq koʻrish uchun kamerani buring"</string> + <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Yaxshiroq koʻrish uchun ajratilgan ekran rejimidan chiqing"</string> <string name="done_label" msgid="7283767013231718521">"Tayyor"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Doiradan soatni tanlang"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Doiradan daqiqani tanlang"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 3b3fa06fd93c..05acca683c89 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -330,7 +330,7 @@ <string name="permgroupdesc_notifications" msgid="4608679556801506580">"hiển thị thông báo"</string> <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Truy xuất nội dung cửa sổ"</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Kiểm tra nội dung của cửa sổ bạn đang tương tác."</string> - <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Bật Khám phá bằng cách chạm"</string> + <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Bật tính năng Khám phá bằng cách chạm"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Đọc to các mục được nhấn và cho phép khám phá màn hình bằng cử chỉ."</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Quan sát nội dung bạn nhập"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Bao gồm dữ liệu cá nhân chẳng hạn như số thẻ tín dụng và mật khẩu."</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Xem toàn màn hình"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Để thoát, hãy vuốt từ trên cùng xuống dưới."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Xong"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Thanh trượt giờ hình tròn"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Thanh trượt phút hình tròn"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 1c419fc9c0bf..604e09a226da 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"目前处于全屏模式"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"要退出,请从顶部向下滑动。"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"知道了"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"完成"</string> <string name="hour_picker_description" msgid="5153757582093524635">"小时转盘"</string> <string name="minute_picker_description" msgid="9029797023621927294">"分钟转盘"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index cc4d15e339dd..85124a4a93be 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"開啟全螢幕"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"由頂部向下滑動即可退出。"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"知道了"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"完成"</string> <string name="hour_picker_description" msgid="5153757582093524635">"小時環形滑桿"</string> <string name="minute_picker_description" msgid="9029797023621927294">"分鐘環形滑桿"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 810ddc684b2a..9207ce2c84ed 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -284,7 +284,7 @@ <string name="notification_channel_vpn" msgid="1628529026203808999">"VPN 狀態"</string> <string name="notification_channel_device_admin" msgid="6384932669406095506">"來自 IT 管理員的快訊"</string> <string name="notification_channel_alerts" msgid="5070241039583668427">"快訊"</string> - <string name="notification_channel_retail_mode" msgid="3732239154256431213">"零售商示範模式"</string> + <string name="notification_channel_retail_mode" msgid="3732239154256431213">"零售商展示模式"</string> <string name="notification_channel_usb" msgid="1528280969406244896">"USB 連線"</string> <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"應用程式執行中"</string> <string name="notification_channel_foreground_service" msgid="7102189948158885178">"正在耗用電量的應用程式"</string> @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"以全螢幕檢視"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"如要退出,請從畫面頂端向下滑動。"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"知道了"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"完成"</string> <string name="hour_picker_description" msgid="5153757582093524635">"小時數環狀滑桿"</string> <string name="minute_picker_description" msgid="9029797023621927294">"分鐘數環狀滑桿"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index d40fd0a19787..5d5d14c15bb7 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1840,6 +1840,10 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"Ukubuka isikrini esigcwele"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"Ukuze uphume, swayiphela phansi kusuka phezulu."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"Ngiyitholile"</string> + <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) --> + <skip /> + <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) --> + <skip /> <string name="done_label" msgid="7283767013231718521">"Kwenziwe"</string> <string name="hour_picker_description" msgid="5153757582093524635">"Amahora weslayidi esiyindingilizi"</string> <string name="minute_picker_description" msgid="9029797023621927294">"Amaminithi weslayidi esiyindingilizi"</string> diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml index 4b27bf2849fb..fe296c704095 100644 --- a/core/res/res/values/bools.xml +++ b/core/res/res/values/bools.xml @@ -18,7 +18,6 @@ <bool name="kg_enable_camera_default_widget">true</bool> <bool name="kg_center_small_widgets_vertically">false</bool> <bool name="kg_top_align_page_shrink_on_bouncer_visible">true</bool> - <bool name="kg_wake_on_acquire_start">false</bool> <bool name="action_bar_embed_tabs">true</bool> <bool name="split_action_bar_is_narrow">true</bool> <bool name="preferences_prefer_dual_pane">false</bool> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index a6be07d0b780..87ece556a0ee 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -652,6 +652,16 @@ The default is false. --> <bool name="config_lidControlsSleep">false</bool> + <!-- The device states (supplied by DeviceStateManager) that should be treated as open by the + device fold controller. Default is empty. --> + <integer-array name="config_openDeviceStates"> + <!-- Example: + <item>0</item> + <item>1</item> + <item>2</item> + --> + </integer-array> + <!-- The device states (supplied by DeviceStateManager) that should be treated as folded by the display fold controller. Default is empty. --> <integer-array name="config_foldedDeviceStates"> @@ -672,6 +682,16 @@ --> </integer-array> + <!-- The device states (supplied by DeviceStateManager) that should be treated as a rear display + state. Default is empty. --> + <integer-array name="config_rearDisplayDeviceStates"> + <!-- Example: + <item>0</item> + <item>1</item> + <item>2</item> + --> + </integer-array> + <!-- Indicates whether the window manager reacts to half-fold device states by overriding rotation. --> <bool name="config_windowManagerHalfFoldAutoRotateOverride">false</bool> @@ -964,6 +984,15 @@ <!-- Boolean indicating whether light mode is allowed when DWB is turned on. --> <bool name="config_displayWhiteBalanceLightModeAllowed">true</bool> + <!-- Duration, in milliseconds, of the display white balance animated transitions. --> + <integer name="config_displayWhiteBalanceTransitionTime">3000</integer> + + <!-- Device states where the sensor based rotation values should be reversed around the Z axis + for the default display. + TODO(b/265312193): Remove this workaround when this bug is fixed.--> + <integer-array name="config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis"> + </integer-array> + <!-- Indicate available ColorDisplayManager.COLOR_MODE_xxx. --> <integer-array name="config_availableColorModes"> <!-- Example: diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 9410e0682106..ba541839ee0c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5154,6 +5154,14 @@ <!-- Cling help message confirmation button when hiding the navigation bar entering immersive mode [CHAR LIMIT=30] --> <string name="immersive_cling_positive">Got it</string> + <!-- Text on a toast shown after the system rotates the screen for camera app + compatibility. [CHAR LIMIT=NONE] --> + <string name="display_rotation_camera_compat_toast_after_rotation">Rotate for a better view</string> + + <!-- Text on a toast shown when a camera view is started within the app that may not be able + to display the camera preview correctly while in split screen. [CHAR LIMIT=NONE] --> + <string name="display_rotation_camera_compat_toast_in_split_screen">Exit split screen for a better view</string> + <!-- Label for button to confirm chosen date or time [CHAR LIMIT=30] --> <string name="done_label">Done</string> <!-- diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index be9f6d2448ba..a74c787fdab1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2841,7 +2841,6 @@ <java-symbol type="dimen" name="fast_scroller_minimum_touch_target" /> <java-symbol type="array" name="config_cdma_international_roaming_indicators" /> <java-symbol type="string" name="kg_text_message_separator" /> - <java-symbol type="bool" name="kg_wake_on_acquire_start" /> <java-symbol type="bool" name="config_use_sim_language_file" /> <java-symbol type="bool" name="config_LTE_eri_for_network_name" /> @@ -3436,6 +3435,12 @@ <java-symbol type="array" name="config_displayWhiteBalanceDisplayPrimaries" /> <java-symbol type="array" name="config_displayWhiteBalanceDisplayNominalWhite" /> <java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" /> + <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTime" /> + + <!-- Device states where the sensor based rotation values should be reversed around the Z axis + for the default display. + TODO(b/265312193): Remove this workaround when this bug is fixed.--> + <java-symbol type="array" name="config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis" /> <!-- Default first user restrictions --> <java-symbol type="array" name="config_defaultFirstUserRestrictions" /> @@ -4015,8 +4020,10 @@ <java-symbol type="integer" name="config_maxScanTasksForHomeVisibility" /> <!-- For Foldables --> + <java-symbol type="array" name="config_openDeviceStates" /> <java-symbol type="array" name="config_foldedDeviceStates" /> <java-symbol type="array" name="config_halfFoldedDeviceStates" /> + <java-symbol type="array" name="config_rearDisplayDeviceStates" /> <java-symbol type="bool" name="config_windowManagerHalfFoldAutoRotateOverride" /> <java-symbol type="array" name="config_deviceStatesOnWhichToWakeUp" /> <java-symbol type="array" name="config_deviceStatesOnWhichToSleep" /> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index f47d9c6e0c2d..1cf819af7a24 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -3331,6 +3331,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, + "1015746067": { + "message": "Display id=%d is ignoring orientation request for %d, return %d following a per-app override for %s", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "1022095595": { "message": "TaskFragment info changed name=%s", "level": "VERBOSE", diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index f615ad6e671b..0f4521951e3d 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -47,6 +47,7 @@ filegroup { "src/com/android/wm/shell/sysui/ShellSharedConstants.java", "src/com/android/wm/shell/common/TransactionPool.java", "src/com/android/wm/shell/animation/Interpolators.java", + "src/com/android/wm/shell/pip/PipContentOverlay.java", "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java", ], path: "src", diff --git a/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml b/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml new file mode 100644 index 000000000000..a3ca74fac4e6 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_900" android:alpha="0.6" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml new file mode 100644 index 000000000000..a3ca74fac4e6 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_900" android:alpha="0.6" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml index 416287d2cbb3..53a8bb18537c 100644 --- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml @@ -17,5 +17,4 @@ <shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@android:color/white" /> - <corners android:radius="20dp" /> </shape> diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml new file mode 100644 index 000000000000..60f3cfe6dde6 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <solid android:color="?androidprv:attr/colorAccentPrimaryVariant"/> + <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/> +</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml new file mode 100644 index 000000000000..ef97ea19e993 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/letterbox_restart_button_background_ripple"> + <item android:drawable="@drawable/letterbox_restart_button_background"/> +</ripple>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml new file mode 100644 index 000000000000..c247c6e4c8cf --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android" > + <item android:state_checked="true" + android:drawable="@drawable/letterbox_restart_checkbox_checked" /> + <item android:state_pressed="true" + android:drawable="@drawable/letterbox_restart_checkbox_checked" /> + <item android:state_pressed="false" + android:drawable="@drawable/letterbox_restart_checkbox_unchecked" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml new file mode 100644 index 000000000000..4f97e2c7ea0d --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20dp" + android:height="20dp" + android:viewportWidth="20" + android:viewportHeight="20" + android:tint="?android:attr/textColorSecondary"> + <group + android:scaleX="0.83333333333" + android:scaleY="0.83333333333" + android:translateX="0" + android:translateY="0"> + <path + android:fillColor="?android:attr/textColorSecondary" + android:pathData="M10.6,16.2 L17.65,9.15 16.25,7.75 10.6,13.4 7.75,10.55 6.35,11.95ZM5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21Z"/> + </group> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml new file mode 100644 index 000000000000..bb14d1961e81 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20dp" + android:height="20dp" + android:viewportWidth="20" + android:viewportHeight="20" + android:tint="?android:attr/textColorSecondary"> + <group + android:scaleX="0.83333333333" + android:scaleY="0.83333333333" + android:translateX="0" + android:translateY="0"> + <path + android:fillColor="?android:attr/textColorSecondary" + android:pathData="M5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21ZM5,19H19Q19,19 19,19Q19,19 19,19V5Q19,5 19,5Q19,5 19,5H5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19Z"/> + </group> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml new file mode 100644 index 000000000000..e3c18a2db66f --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <solid android:color="?androidprv:attr/colorSurface"/> + <corners android:radius="@dimen/letterbox_restart_dialog_corner_radius"/> +</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml new file mode 100644 index 000000000000..af89d41ee6b5 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" android:width="1dp"/> + <solid android:color="?androidprv:attr/colorSurface" /> + <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/> +</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml new file mode 100644 index 000000000000..e32aefca78ac --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/letterbox_restart_dismiss_button_background_ripple"> + <item android:drawable="@drawable/letterbox_restart_dismiss_button_background"/> +</ripple>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml new file mode 100644 index 000000000000..5053971a17d3 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:width="@dimen/letterbox_restart_dialog_title_icon_width" + android:height="@dimen/letterbox_restart_dialog_title_icon_height" + android:viewportWidth="45" + android:viewportHeight="44"> + <group + android:scaleX="0.8" + android:scaleY="0.8" + android:translateX="8" + android:translateY="8"> + <path + android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z" + android:fillColor="?androidprv:attr/colorAccentPrimaryVariant"/> + </group> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml new file mode 100644 index 000000000000..b6e0172af1df --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="@dimen/letterbox_restart_dialog_title_icon_width" + android:height="@dimen/letterbox_restart_dialog_title_icon_height" + android:viewportWidth="45" + android:viewportHeight="44"> + <group + android:scaleX="0.8" + android:scaleY="0.8" + android:translateX="8" + android:translateY="8"> + <path + android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z" + android:fillColor="@color/compat_controls_text"/> + </group> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml index 2a4cc02f0925..da31a46713df 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml @@ -17,26 +17,14 @@ <com.android.wm.shell.windowdecor.WindowDecorLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/desktop_mode_caption" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" + android:gravity="center_horizontal" android:background="@drawable/desktop_mode_decor_title"> <Button - style="@style/CaptionButtonStyle" - android:id="@+id/back_button" - android:contentDescription="@string/back_button_text" - android:background="@drawable/decor_back_button_dark" - /> - <Button android:id="@+id/caption_handle" android:layout_width="128dp" android:layout_height="32dp" - android:layout_margin="5dp" - android:padding="4dp" android:contentDescription="@string/handle_text" android:background="@drawable/decor_handle_dark"/> - <Button - style="@style/CaptionButtonStyle" - android:id="@+id/close_window" - android:contentDescription="@string/close_button_text" - android:background="@drawable/decor_close_button_dark"/> </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml new file mode 100644 index 000000000000..ba9852c4dd6b --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml @@ -0,0 +1,142 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.wm.shell.compatui.RestartDialogLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/system_neutral1_900"> + + <!-- The background of the top-level layout acts as the background dim. --> + + <!--TODO (b/266288912): Resolve overdraw warning --> + + <!-- Vertical margin will be set dynamically since it depends on task bounds. + Setting the alpha of the dialog container to 0, since it shouldn't be visible until the + enter animation starts. --> + <FrameLayout + android:id="@+id/letterbox_restart_dialog_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/letterbox_restart_dialog_margin" + android:background="@drawable/letterbox_restart_dialog_background" + android:alpha="0" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintWidth_max="@dimen/letterbox_restart_dialog_width"> + + <!-- The ScrollView should only wrap the content of the dialog, otherwise the background + corner radius will be cut off when scrolling to the top/bottom. --> + + <ScrollView android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:padding="24dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical"> + + <ImageView + android:importantForAccessibility="no" + android:layout_width="@dimen/letterbox_restart_dialog_title_icon_width" + android:layout_height="@dimen/letterbox_restart_dialog_title_icon_height" + android:src="@drawable/letterbox_restart_header_ic_arrows"/> + + <TextView + android:layout_marginVertical="16dp" + android:id="@+id/letterbox_restart_dialog_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/letterbox_restart_dialog_title" + android:textAlignment="center" + android:textAppearance="@style/RestartDialogTitleText"/> + + <TextView + android:textAppearance="@style/RestartDialogBodyText" + android:id="@+id/letterbox_restart_dialog_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/letterbox_restart_dialog_description" + android:textAlignment="center"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_gravity="center_vertical" + android:layout_marginVertical="32dp"> + + <CheckBox + android:id="@+id/letterbox_restart_dialog_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:button="@drawable/letterbox_restart_checkbox_button"/> + + <TextView + android:textAppearance="@style/RestartDialogCheckboxText" + android:layout_marginStart="12dp" + android:id="@+id/letterbox_restart_dialog_checkbox_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/letterbox_restart_dialog_checkbox_title" + android:textAlignment="textStart"/> + + </LinearLayout> + + <FrameLayout + android:minHeight="@dimen/letterbox_restart_dialog_button_height" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button + android:textAppearance="@style/RestartDialogDismissButton" + android:id="@+id/letterbox_restart_dialog_dismiss_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="@dimen/letterbox_restart_dialog_button_width" + android:minHeight="@dimen/letterbox_restart_dialog_button_height" + android:layout_gravity="start" + android:background= + "@drawable/letterbox_restart_dismiss_button_background_ripple" + android:text="@string/letterbox_restart_cancel" + android:contentDescription="@string/letterbox_restart_cancel"/> + + <Button + android:textAppearance="@style/RestartDialogConfirmButton" + android:id="@+id/letterbox_restart_dialog_restart_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="@dimen/letterbox_restart_dialog_button_width" + android:minHeight="@dimen/letterbox_restart_dialog_button_height" + android:layout_gravity="end" + android:background= + "@drawable/letterbox_restart_button_background_ripple" + android:text="@string/letterbox_restart_restart" + android:contentDescription="@string/letterbox_restart_restart"/> + + </FrameLayout> + + </LinearLayout> + + </ScrollView> + + </FrameLayout> + +</com.android.wm.shell.compatui.RestartDialogLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 3d50d2262bb8..33e6aa6e487a 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Herbegin vir ’n beter aansig?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Jy kan die app herbegin sodat dit beter op jou skerm lyk, maar jy sal dalk jou vordering of enige ongestoorde veranderinge verloor"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselleer"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Herbegin"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Moenie weer wys nie"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string> <string name="minimize_button_text" msgid="271592547935841753">"Maak klein"</string> <string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index 70304aa9f562..b6b99e52c660 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጪ ሁለቴ መታ ያድርጉ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string> <string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string> <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index 0f74aab78924..7b2adedd32bc 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"انقر مرّتين خارج تطبيق لتغيير موضعه."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"حسنًا"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"التوسيع للحصول على مزيد من المعلومات"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string> <string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string> <string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index a0213f42b125..11a7e321b16f 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"এপ্টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ বাহিৰত দুবাৰ টিপক"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string> <string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string> <string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index f842bfe13efc..10663ee30553 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tətbiqin yerini dəyişmək üçün kənarına iki dəfə toxunun"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ətraflı məlumat üçün genişləndirin."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Daha yaxşı görünüş üçün yenidən başladılsın?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Tətbiqi yenidən başlada bilərsiniz ki, ekranınızda daha yaxşı görünsün, lakin irəliləyişi və ya yadda saxlanmamış dəyişiklikləri itirə bilərsiniz"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ləğv edin"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yenidən başladın"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Yenidən göstərməyin"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string> <string name="minimize_button_text" msgid="271592547935841753">"Kiçildin"</string> <string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml index 540ae7ce6953..47e661c531a2 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste promenili njenu poziciju"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite li da restartujete radi boljeg prikaza?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, s tim što možete da izgubite ono što ste uradili ili nesačuvane promene, ako ih ima"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartuj"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string> <string name="minimize_button_text" msgid="271592547935841753">"Umanjite"</string> <string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index bea753837b7b..0961f3002042 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двойчы націсніце экран па-за праграмай, каб перамясціць яе"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Зразумела"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгарнуць для дадатковай інфармацыі"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string> <string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string> <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index 59915e6b2a6e..6a0648b87ad6 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Докоснете два пъти извън дадено приложение, за да промените позицията му"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Разбрах"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгъване за още информация."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string> <string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string> <string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index 63c9684070b6..5e801044d835 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"কোনও অ্যাপের স্থান পরিবর্তন করতে তার বাইরে ডবল ট্যাপ করুন"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুঝেছি"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"আরও তথ্যের জন্য বড় করুন।"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string> <string name="minimize_button_text" msgid="271592547935841753">"ছোট করুন"</string> <string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index b725efea6e48..2405b2aa5180 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da promijenite njen položaj"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za više informacija."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Ponovo pokrenuti za bolji prikaz?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete ponovo pokrenuti aplikaciju da bolje izgleda na ekranu, ali možete izgubiti napredak ili izmjene koje nisu sačuvane"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Ponovo pokreni"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimiziranje"</string> <string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index 4383916f6597..3b070d86e241 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Fes doble toc fora d\'una aplicació per canviar-ne la posició"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimitza"</string> <string name="close_button_text" msgid="2913281996024033299">"Tanca"</string> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index e5cb26f3e3b4..3606eaa5c9c6 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikaci změníte její umístění"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovat"</string> <string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index 46f7c6985ec2..487d41225309 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryk to gange uden for en app for at justere dens placering"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string> <string name="close_button_text" msgid="2913281996024033299">"Luk"</string> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index 1269d362903d..0ec59e7c5aa9 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimieren"</string> <string name="close_button_text" msgid="2913281996024033299">"Schließen"</string> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index f8a69ef796b9..4f4265adfc32 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Πατήστε δύο φορές έξω από μια εφαρμογή για να αλλάξετε τη θέση της"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Το κατάλαβα"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ανάπτυξη για περισσότερες πληροφορίες."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Επανεκκίνηση για καλύτερη προβολή;"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Μπορείτε να επανεκκινήσετε την εφαρμογή για να προβάλλεται καλύτερα στην οθόνη σας, αλλά η πρόοδός σας και τυχόν μη αποθηκευμένες αλλαγές ενδέχεται να χαθούν."</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ακύρωση"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Επανεκκίνηση"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Να μην εμφανιστεί ξανά"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string> <string name="minimize_button_text" msgid="271592547935841753">"Ελαχιστοποίηση"</string> <string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml index 8e46c3e0999e..e29b01b392ba 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string> <string name="close_button_text" msgid="2913281996024033299">"Close"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml index 7cbbf64991cd..9228c59320f1 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so it looks better on your screen, but you may lose your progress or any unsaved changes"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don’t show again"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string> <string name="close_button_text" msgid="2913281996024033299">"Close"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml index 8e46c3e0999e..e29b01b392ba 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string> <string name="close_button_text" msgid="2913281996024033299">"Close"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml index 8e46c3e0999e..e29b01b392ba 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string> <string name="close_button_text" msgid="2913281996024033299">"Close"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml index b2720be3d8a0..cf231145f906 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so it looks better on your screen, but you may lose your progress or any unsaved changes"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don’t show again"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string> <string name="close_button_text" msgid="2913281996024033299">"Close"</string> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index 47445a7a0488..a147be3e48a5 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Presiona dos veces fuera de una app para cambiar su ubicación"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expande para obtener más información."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"¿Quieres reiniciar para que se vea mejor?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puedes reiniciar la app para que se vea mejor en la pantalla, pero podrías perder tu progreso o cualquier cambio que no hayas guardado"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index 6c45231c3261..9c5aa926d732 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dos veces fuera de una aplicación para cambiarla de posición"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index a8dc08cbbd27..f7eafbd05c63 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Topeltpuudutage rakendusest väljaspool, et selle asendit muuta"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimeeri"</string> <string name="close_button_text" msgid="2913281996024033299">"Sule"</string> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index 9fbf0a070019..a46095a35216 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren kanpoaldea"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Informazio gehiago lortzeko, zabaldu hau."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Aplikazioa berrabiarazi nahi duzu itxura hobea izan dezan?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Aplikazioa berrabiarazi egin dezakezu itxura hobea izan dezan, baina agian garapena edo gorde gabeko aldaketak galduko dituzu"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Utzi"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Berrabiarazi"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ez erakutsi berriro"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizatu"</string> <string name="close_button_text" msgid="2913281996024033299">"Itxi"</string> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index e7cb5f41a3ac..ea8fafea8780 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابهجا کردن برنامه، بیرون از آن دوضربه بزنید"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجهام"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string> <string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string> <string name="close_button_text" msgid="2913281996024033299">"بستن"</string> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index 86199f3cf092..298de6413f53 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kaksoisnapauta sovelluksen ulkopuolella, jos haluat siirtää sitä"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string> <string name="minimize_button_text" msgid="271592547935841753">"Pienennä"</string> <string name="close_button_text" msgid="2913281996024033299">"Sulje"</string> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 1f3ac9ecbcca..36b40bddd569 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une application pour la repositionner"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string> <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string> <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index f1dbb35dc7a7..7f3f9146ca0d 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Appuyez deux fois en dehors d\'une appli pour la repositionner"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string> <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string> <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index 6e215a1f5b81..3d5265f18d9a 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dúas veces fóra da aplicación para cambiala de posición"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> <string name="close_button_text" msgid="2913281996024033299">"Pechar"</string> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index ad086bb1f712..d2e5a82bb0aa 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બહાર બે વાર ટૅપ કરો"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"સમજાઈ ગયું"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"વધુ માહિતી માટે મોટું કરો."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"બહેતર વ્યૂ માટે ફરીથી શરૂ કરીએ?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"તમે ઍપને ફરીથી શરૂ કરી શકો છો, જેથી તે તમારી સ્ક્રીન પર વધુ સારી રીતે દેખાય, પરંતુ આમ કરવાથી તમે તમારી ઍપ પર કરી હોય એવી કોઈ પ્રક્રિયાની પ્રગતિ અથવા સાચવ્યા ન હોય એવો કોઈપણ ફેરફાર ગુમાવી શકો છો"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"રદ કરો"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"ફરી શરૂ કરો"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ફરીથી બતાવશો નહીં"</string> <string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string> <string name="minimize_button_text" msgid="271592547935841753">"નાનું કરો"</string> <string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index bed39fb77400..b217978a9898 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बाहर दो बार टैप करें"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string> <string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string> <string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index 1446e70ecdfa..bbd95f7073ce 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste je premjestili"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite da biste saznali više."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite li ponovno pokrenuti za bolji pregled?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete ponovno pokrenuti aplikaciju tako da bolje izgleda na zaslonu, no mogli biste izgubiti napredak ili sve nespremljene promjene"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Odustani"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Pokreni ponovno"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovno"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimiziraj"</string> <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index 221c329020a0..1bb221344f4f 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Koppintson duplán az alkalmazáson kívül az áthelyezéséhez"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kibontással további információkhoz juthat."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Újraindítja a jobb megjelenítés érdekében?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Újraindíthatja az alkalmazást a képernyőn való jobb megjelenítés érdekében, de elveszítheti az előrehaladását és az esetleges nem mentett változásokat"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Mégse"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Újraindítás"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne jelenjen meg többé"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string> <string name="minimize_button_text" msgid="271592547935841753">"Kis méret"</string> <string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 7be9941d2a5e..7d556b263bd7 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Եղավ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ծավալեք՝ ավելին իմանալու համար։"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string> <string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string> <string name="close_button_text" msgid="2913281996024033299">"Փակել"</string> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index 4e760ef6450c..b51dc006b2a5 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimalkan"</string> <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index 50d4ee7faed6..d924c47352b2 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ýttu tvisvar utan við forrit til að færa það"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minnka"</string> <string name="close_button_text" msgid="2913281996024033299">"Loka"</string> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index d2595f7682d2..98d1b45c3a82 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tocca due volte fuori da un\'app per riposizionarla"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vuoi riavviare per migliorare la visualizzazione?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puoi riavviare l\'app affinché venga visualizzata meglio sullo schermo, ma potresti perdere i tuoi progressi o eventuali modifiche non salvate"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annulla"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Riavvia"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrare più"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Ingrandisci"</string> <string name="minimize_button_text" msgid="271592547935841753">"Riduci a icona"</string> <string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index 883596ebcd6f..d116257944d6 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string> <string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string> <string name="close_button_text" msgid="2913281996024033299">"סגירה"</string> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index 6bb22a29e79b..548d63c50dc3 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"位置を変えるにはアプリの外側をダブルタップしてください"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"開くと詳細が表示されます。"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"再起動して画面をすっきりさせますか?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"アプリを再起動して画面をすっきりさせることはできますが、進捗状況が失われ、保存されていない変更が消える可能性があります"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"キャンセル"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"再起動"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"次回から表示しない"</string> <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> <string name="close_button_text" msgid="2913281996024033299">"閉じる"</string> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index 6cf7d7827ee9..1b359b39e63a 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ორმაგად შეეხეთ აპის გარშემო სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"გასაგებია"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"დამატებითი ინფორმაციისთვის გააფართოეთ."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"გსურთ გადატვირთვა უკეთესი ხედისთვის?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"შეგიძლიათ გადატვირთოთ აპი იმისათვის, რომ თქვენს ეკრანზე უკეთესად გამოჩნდეს, თუმცა თქვენ მიერ შესრულებული მოქმედებები შეიძლება დაიკარგოს ან ცვლილებების შენახვა ვერ მოხერხდეს"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"გაუქმება"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"გადატვირთვა"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"აღარ გამოჩნდეს"</string> <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string> <string name="minimize_button_text" msgid="271592547935841753">"ჩაკეცვა"</string> <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index 216619a232fc..02560caf1749 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Қолданбаның орнын өзгерту үшін одан тыс жерді екі рет түртіңіз."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түсінікті"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толығырақ ақпарат алу үшін терезені жайыңыз."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string> <string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string> <string name="close_button_text" msgid="2913281996024033299">"Жабу"</string> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 79aca620f348..32ca8e3cf949 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ចុចពីរដងនៅក្រៅកម្មវិធី ដើម្បីប្ដូរទីតាំងកម្មវិធីនោះ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"យល់ហើយ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string> <string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string> <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index 9e9333ea422c..9b47a00dcb4c 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಹೊರಗೆ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ಸರಿ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ವಿಸ್ತೃತಗೊಳಿಸಿ."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬಹುದು, ಇದರಿಂದ ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ಆ್ಯಪ್ ಉತ್ತಮವಾಗಿ ಕಾಣಿಸುತ್ತದೆ, ಆದರೆ ನಿಮ್ಮ ಪ್ರಗತಿಯನ್ನು ಅಥವಾ ಯಾವುದೇ ಉಳಿಸದ ಬದಲಾವಣೆಗಳನ್ನು ನೀವು ಕಳೆದುಕೊಳ್ಳಬಹುದು"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ರದ್ದುಮಾಡಿ"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"ಮರುಪ್ರಾರಂಭಿಸಿ"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string> <string name="minimize_button_text" msgid="271592547935841753">"ಕುಗ್ಗಿಸಿ"</string> <string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index f9b495a38f4f..844a9fac00b4 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"앱 위치를 조정하려면 앱 외부를 두 번 탭합니다."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"확인"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"추가 정보는 펼쳐서 확인하세요."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string> <string name="minimize_button_text" msgid="271592547935841753">"최소화"</string> <string name="close_button_text" msgid="2913281996024033299">"닫기"</string> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 800728b1e1e4..c2368d28d364 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -51,7 +51,7 @@ <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чыгуу үчүн экранды ылдый жагынан өйдө сүрүңүз же колдонмонун өйдө жагын басыңыз"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бир кол режимин баштоо"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Бир кол режиминен чыгуу"</string> - <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> калкып чыкма билдирмелер жөндөөлөрү"</string> + <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> калкып чыкма билдирмелер параметрлери"</string> <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Кошумча меню"</string> <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Кайра топтомго кошуу"</string> <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="APP_NAME">%2$s</xliff:g> колдонмосунан <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> @@ -60,7 +60,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Жогорку оң жакка жылдыруу"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Төмөнкү сол жакка жылдыруу"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төмөнкү оң жакка жылдыруу"</string> - <string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> жөндөөлөрү"</string> + <string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлери"</string> <string name="bubble_dismiss_text" msgid="8816558050659478158">"Калкып чыкма билдирмени жабуу"</string> <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Жазышууда калкып чыкма билдирмелер көрүнбөсүн"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Калкып чыкма билдирмелер аркылуу маектешүү"</string> @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Колдонмону жылдыруу үчүн сырт жагын эки жолу таптаңыз"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түшүндүм"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толук маалымат алуу үчүн жайып көрүңүз."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Жакшыраак көрүү үчүн өчүрүп күйгүзөсүзбү?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Экранда жакшыраак көрүү үчүн колдонмону өчүрүп күйгүзө аласыз, бирок аткарылган иш же сакталбаган өзгөрүүлөр өчүрүлүшү мүмкүн"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Токтотуу"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өчүрүп күйгүзүү"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Экинчи көрүнбөсүн"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string> <string name="minimize_button_text" msgid="271592547935841753">"Кичирейтүү"</string> <string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index 8e42aa346669..f4ff5e52cc60 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ແຕະສອງເທື່ອໃສ່ນອກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ເຂົ້າໃຈແລ້ວ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ຂະຫຍາຍເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ຣີສະຕາດເພື່ອໃຫ້ມີມຸມມອງທີ່ດີຂຶ້ນບໍ?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ທ່ານສາມາດຣີສະຕາດແອັບໄດ້ເພື່ອໃຫ້ມັນເບິ່ງດີຂຶ້ນໃນໜ້າຈໍຂອງທ່ານ ແຕ່ທ່ານອາດຈະສູນເສຍຄວາມຄືບໜ້າ ຫຼື ການປ່ຽນແປງທີ່ບໍ່ໄດ້ບັນທຶກໄວ້"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ຍົກເລີກ"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"ຣີສະຕາດ"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ບໍ່ຕ້ອງສະແດງອີກ"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string> <string name="minimize_button_text" msgid="271592547935841753">"ຫຍໍ້ລົງ"</string> <string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index dc9969095cf5..0d276ecd37a1 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dukart palieskite už programos ribų, kad pakeistumėte jos poziciją"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Išskleiskite, jei reikia daugiau informacijos."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Paleisti iš naujo, kad būtų geresnis vaizdas?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Galite iš naujo paleisti programą, kad ji geriau atrodytų ekrane, bet galite prarasti eigą ir neišsaugotus pakeitimus"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atšaukti"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Paleisti iš naujo"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Daugiau neberodyti"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string> <string name="minimize_button_text" msgid="271592547935841753">"Sumažinti"</string> <string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index bd2eef42690b..c6ccc27a024b 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Lai pārvietotu lietotni, veiciet dubultskārienu ārpus lietotnes"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Izvērsiet, lai iegūtu plašāku informāciju."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vai restartēt, lai uzlabotu skatu?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Varat restartēt lietotni, lai tā labāk izskatītos ekrānā, taču, iespējams, zaudēsiet paveikto vai nesaglabātas izmaiņas (ja tādas ir)."</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atcelt"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartēt"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vairs nerādīt"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizēt"</string> <string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index d133654ba777..526093df490f 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Допрете двапати надвор од некоја апликација за да ја преместите"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Сфатив"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширете за повеќе информации."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Да се рестартира за подобар приказ?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Може да ја рестартирате апликацијата за да изгледа подобро на екранот, но може да го изгубите напредокот или незачуваните промени"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартирај"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не прикажувај повторно"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string> <string name="minimize_button_text" msgid="271592547935841753">"Минимизирај"</string> <string name="close_button_text" msgid="2913281996024033299">"Затвори"</string> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index 16927bf19523..bd3d94ee4c21 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ അതിന് പുറത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"മനസ്സിലായി"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"കൂടുതൽ വിവരങ്ങൾക്ക് വികസിപ്പിക്കുക."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"മെച്ചപ്പെട്ട കാഴ്ചയ്ക്കായി റീസ്റ്റാർട്ട് ചെയ്യണോ?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്യുകയാണെങ്കിൽ ഇത് നിങ്ങളുടെ സ്ക്രീനിൽ മെച്ചപ്പെട്ടതായി കാണും, എന്നാൽ ഇതുവരെയുള്ള പുരോഗതിയും സംരക്ഷിക്കാത്ത മാറ്റങ്ങളും നിങ്ങൾക്ക് നഷ്ടമാകും"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"റദ്ദാക്കുക"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"റീസ്റ്റാർട്ട് ചെയ്യൂ"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"വീണ്ടും കാണിക്കരുത്"</string> <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string> <string name="minimize_button_text" msgid="271592547935841753">"ചെറുതാക്കുക"</string> <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index 264f9a0a4db9..6c1c0b59953e 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Аппыг дахин байрлуулахын тулд гадна талд нь хоёр товшино"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ойлголоо"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Нэмэлт мэдээлэл авах бол дэлгэнэ үү."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string> <string name="minimize_button_text" msgid="271592547935841753">"Багасгах"</string> <string name="close_button_text" msgid="2913281996024033299">"Хаах"</string> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index 7a475edcd964..01bf9494fe39 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या बाहेर दोनदा टॅप करा"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"समजले"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"अधिक माहितीसाठी विस्तार करा."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"आणखी चांगल्या प्रकारे दिसावे यासाठी रीस्टार्ट करायचे आहे का?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"तुम्ही अॅप रीस्टार्ट करू शकता, जेणेकरून ते तुमच्या स्क्रीनवर आणखी चांगल्या प्रकारे दिसेल, पण तुमची प्रगती किंवा कोणतेही सेव्ह न केलेले बदल तुम्ही गमवाल"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द करा"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करा"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"पुन्हा दाखवू नका"</string> <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string> <string name="minimize_button_text" msgid="271592547935841753">"लहान करा"</string> <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index be1dc24ccfcb..80efab83fb94 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketik dua kali di luar apl untuk menempatkan semula apl itu"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimumkan"</string> <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index 7b2b7c5dd01e..be0815ea088c 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"နားလည်ပြီ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string> <string name="minimize_button_text" msgid="271592547935841753">"ချုံ့ရန်"</string> <string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 3e18b4968464..8d150869e406 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dobbelttrykk utenfor en app for å flytte den"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string> <string name="close_button_text" msgid="2913281996024033299">"Lukk"</string> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index 4b10f5a1a886..f1997bee71da 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको बाहिर डबल ट्याप गर्नुहोस्"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"बुझेँ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"थप जानकारी प्राप्त गर्न चाहनुहुन्छ भने एक्स्पान्ड गर्नुहोस्।"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"अझ राम्रोसँग देखिने बनाउन एप रिस्टार्ट गर्ने हो?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"यो एप तपाईंको स्क्रिनमा अझ राम्रोसँग देखियोस् भन्नाका लागि तपाईं सो एप रिस्टार्ट गर्न सक्नुहुन्छ तर तपाईंले अहिलेसम्म गरेका क्रियाकलाप वा सेभ गर्न बाँकी परिवर्तनहरू हट्न सक्छन्"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द गर्नुहोस्"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"रिस्टार्ट गर्नुहोस्"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फेरि नदेखाइयोस्"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string> <string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string> <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index b056483e3b2d..ba37ca745ea6 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik naast een app om deze opnieuw te positioneren"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Uitvouwen voor meer informatie."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Opnieuw opstarten voor een betere weergave?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Je kunt de app opnieuw opstarten zodat deze er beter uitziet op je scherm, maar je kunt je voortgang of niet-opgeslagen wijzigingen kwijtraken"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuleren"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Opnieuw opstarten"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Niet opnieuw tonen"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimaliseren"</string> <string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index 5fd81f44bf0d..0a8882d7749d 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହାର ବାହାରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ବୁଝିଗଲି"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string> <string name="minimize_button_text" msgid="271592547935841753">"ଛୋଟ କରନ୍ତୁ"</string> <string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index ada79d195df2..62f15bf856a6 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਬਾਹਰ ਡਬਲ ਟੈਪ ਕਰੋ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ਸਮਝ ਲਿਆ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string> <string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string> <string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index a97fd5c9ddb0..d82f60f67d6d 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kliknij dwukrotnie poza aplikacją, aby ją przenieść"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimalizuj"</string> <string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index 8edcddff14e2..a94d157f48aa 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Reiniciar para melhorar a visualização?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Você pode reiniciar o app para melhorar a visualização dele, mas talvez perca seu progresso ou mudanças não salvas"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index e0636d42d980..7435faf6a1a7 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de uma app para a reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 8edcddff14e2..a94d157f48aa 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Reiniciar para melhorar a visualização?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Você pode reiniciar o app para melhorar a visualização dele, mas talvez perca seu progresso ou mudanças não salvas"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string> <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index 9227216ffc1f..fa9a07454b4f 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Atinge de două ori lângă o aplicație pentru a o repoziționa"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extinde pentru mai multe informații"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Repornești pentru o vizualizare mai bună?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Poți să repornești aplicația ca să arate mai bine pe ecran, dar este posibil să pierzi progresul sau modificările nesalvate"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulează"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Repornește"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nu mai afișa"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizează"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizează"</string> <string name="close_button_text" msgid="2913281996024033299">"Închide"</string> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index f5fa1a475886..b9c59514b939 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Чтобы переместить приложение, дважды нажмите рядом с ним."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОК"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Развернуть, чтобы узнать больше."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Перезапустить приложение?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Вы можете перезапустить приложение, чтобы оно лучше смотрелось на экране. При этом ваш прогресс или несохраненные изменения могут быть утеряны."</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Отмена"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустить"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больше не показывать"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string> <string name="minimize_button_text" msgid="271592547935841753">"Свернуть"</string> <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index 53e52f28adcf..e408655690c8 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"යෙදුමක් නැවත ස්ථානගත කිරීමට පිටතින් දෙවරක් තට්ටු කරන්න"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"තේරුණා"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"වැඩිදුර තොරතුරු සඳහා දිග හරින්න"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string> <string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string> <string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index f004fd472adf..7bb415dc474c 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikácie zmeníte jej pozíciu"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Po rozbalení sa dozviete viac."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Chcete ju reštartovať, aby mala lepší vzhľad?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Aplikáciu môžete reštartovať, aby mala na obrazovke lepší vzhľad, ale môžete prísť o postup a všetky neuložené zmeny."</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušiť"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reštartovať"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Už nezobrazovať"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovať"</string> <string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index c4808059a0c4..ac9c2be9a1fd 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvakrat se dotaknite zunaj aplikacije, če jo želite prestaviti."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimiraj"</string> <string name="close_button_text" msgid="2913281996024033299">"Zapri"</string> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index b59d4db6413e..f4fc5e75d6a4 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Trokit dy herë jashtë një aplikacioni për ta ripozicionuar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Rinis për një pamje më të mirë?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Mund të rinisësh aplikacionin në mënyrë që të duket më mirë në ekranin tënd, por mund të humbësh progresin ose çdo ndryshim të paruajtur"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulo"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Rinis"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Mos e shfaq përsëri"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizo"</string> <string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index 78d74d74ac3f..743f9a84a89e 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двапут додирните изван апликације да бисте променили њену позицију"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширите за још информација."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Желите ли да рестартујете ради бољег приказа?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартујете апликацију да би изгледала боље на екрану, с тим што можете да изгубите оно што сте урадили или несачуване промене, ако их има"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартуј"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не приказуј поново"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string> <string name="minimize_button_text" msgid="271592547935841753">"Умањите"</string> <string name="close_button_text" msgid="2913281996024033299">"Затворите"</string> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index cda3040dc056..ba5785120b58 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryck snabbt två gånger utanför en app för att flytta den"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimera"</string> <string name="close_button_text" msgid="2913281996024033299">"Stäng"</string> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index fee34eb28725..43b415c38020 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Gusa mara mbili nje ya programu ili uihamishe"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string> <string name="minimize_button_text" msgid="271592547935841753">"Punguza"</string> <string name="close_button_text" msgid="2913281996024033299">"Funga"</string> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index 49f128d4477b..3e65964ad6a2 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ஆப்ஸை இடம் மாற்ற அதன் வெளியில் இருமுறை தட்டலாம்"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"சரி"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string> <string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string> <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index f0c8be5c5957..7d09fc41e27c 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"యాప్ స్థానాన్ని మార్చడానికి దాని వెలుపల డబుల్-ట్యాప్ చేయండి"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"అర్థమైంది"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"మరింత సమాచారం కోసం విస్తరించండి."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string> <string name="minimize_button_text" msgid="271592547935841753">"కుదించండి"</string> <string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index 2437e0377780..a9100e8a9453 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"แตะสองครั้งด้านนอกแอปเพื่อเปลี่ยนตำแหน่ง"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"รับทราบ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ขยายเพื่อดูข้อมูลเพิ่มเติม"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"รีสตาร์ทเพื่อรับมุมมองที่ดียิ่งขึ้นใช่ไหม"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"คุณรีสตาร์ทแอปเพื่อรับมุมมองที่ดียิ่งขึ้นบนหน้าจอได้ แต่ความคืบหน้าและการเปลี่ยนแปลงใดๆ ที่ไม่ได้บันทึกอาจหายไป"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ยกเลิก"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"รีสตาร์ท"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ไม่ต้องแสดงข้อความนี้อีก"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string> <string name="minimize_button_text" msgid="271592547935841753">"ย่อ"</string> <string name="close_button_text" msgid="2913281996024033299">"ปิด"</string> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index 86ef75718b77..7a4232cc15d6 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Mag-double tap sa labas ng app para baguhin ang posisyon nito"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"I-expand para sa higit pang impormasyon."</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"I-restart para sa mas magandang hitsura?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puwede mong i-restart ang app para maging mas maganda ang itsura nito sa iyong screen, pero posibleng mawala ang pag-usad mo o anumang hindi na-save na pagbabago"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselahin"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"I-restart"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Huwag nang ipakita ulit"</string> <string name="maximize_button_text" msgid="1650859196290301963">"I-maximize"</string> <string name="minimize_button_text" msgid="271592547935841753">"I-minimize"</string> <string name="close_button_text" msgid="2913281996024033299">"Isara"</string> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index c4060cc795ab..015cfc83771d 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Yeniden konumlandırmak için uygulamanın dışına iki kez dokunun"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string> <string name="minimize_button_text" msgid="271592547935841753">"Küçült"</string> <string name="close_button_text" msgid="2913281996024033299">"Kapat"</string> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index 166041d6d6d8..1ed9069cae9a 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Щоб перемістити додаток, двічі торкніться області поза ним"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string> <string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string> <string name="close_button_text" msgid="2913281996024033299">"Закрити"</string> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index ca6a93714cdd..8f818cb71d03 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس ایپ کے باہر دو بار تھپتھپائیں"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"سمجھ آ گئی"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"مزید معلومات کے لیے پھیلائیں۔"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"بہتر منظر کے لیے ری سٹارٹ کریں؟"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"آپ ایپ کو ری سٹارٹ کر سکتے ہیں تاکہ یہ آپ کی اسکرین پر بہتر نظر آئے، تاہم آپ اپنی پیشرفت سے یا کسی غیر محفوظ شدہ تبدیلیوں سے محروم ہو سکتے ہیں"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"منسوخ کریں"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"ری اسٹارٹ کریں"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوبارہ نہ دکھائیں"</string> <string name="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string> <string name="minimize_button_text" msgid="271592547935841753">"چھوٹا کریں"</string> <string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index 8f173d5d1e4b..0bbdf4fb90f9 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Qayta joylash uchun ilova tashqarisiga ikki marta bosing"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string> <string name="minimize_button_text" msgid="271592547935841753">"Kichraytirish"</string> <string name="close_button_text" msgid="2913281996024033299">"Yopish"</string> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index 1d5b9d63ee5a..d8e131835881 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Nhấn đúp bên ngoài ứng dụng để đặt lại vị trí"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string> <string name="minimize_button_text" msgid="271592547935841753">"Thu nhỏ"</string> <string name="close_button_text" msgid="2913281996024033299">"Đóng"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index 87f2973aa618..88d6aa66395c 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -82,6 +82,16 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在某个应用外连续点按两次,即可调整它的位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展开即可了解详情。"</string> + <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) --> + <skip /> + <!-- no translation found for letterbox_restart_cancel (1342209132692537805) --> + <skip /> + <!-- no translation found for letterbox_restart_restart (8529976234412442973) --> + <skip /> + <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) --> + <skip /> <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> <string name="close_button_text" msgid="2913281996024033299">"关闭"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index f9b22d226c4f..6bd05c69cfe7 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕按兩下即可調整位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動改善檢視畫面嗎?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"你可以重新啟動應用程式,讓系統更新檢視畫面。不過,系統可能不會儲存目前進度及你所做的任何變更"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string> <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> <string name="close_button_text" msgid="2913281996024033299">"關閉"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index 1438e52ccb4a..082049f7d2f9 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕觸兩下即可調整位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"我知道了"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳細資訊。"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動改善檢視畫面嗎?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"你可以重新啟動應用程式,讓系統更新檢視畫面。不過,系統可能不會儲存目前進度及你所做的任何變更"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string> <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> <string name="close_button_text" msgid="2913281996024033299">"關閉"</string> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index e9238dc0833a..18f722c9f7a1 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -82,6 +82,11 @@ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Thepha kabili ngaphandle kwe-app ukuze uyimise kabusha"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Nweba ukuze uthole ulwazi olwengeziwe"</string> + <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Qala kabusha ukuze uthole ukubuka okungcono?"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Ungakwazi ukuqala kabusha i-app ukuze ibukeke kangcono esikrinini sakho, kodwa ungase ulahlekelwe ukuqhubeka kwakho nanoma yiziphi izinguquko ezingalondoloziwe"</string> + <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Khansela"</string> + <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qala kabusha"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ungabonisi futhi"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string> <string name="minimize_button_text" msgid="271592547935841753">"Nciphisa"</string> <string name="close_button_text" msgid="2913281996024033299">"Vala"</string> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 3ee20ea95ee5..8908959b89fa 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -273,6 +273,39 @@ <!-- The space between two actions in the letterbox education dialog --> <dimen name="letterbox_education_dialog_space_between_actions">24dp</dimen> + <!-- The margin between the dialog container and its parent. --> + <dimen name="letterbox_restart_dialog_margin">24dp</dimen> + + <!-- The corner radius of the restart confirmation dialog. --> + <dimen name="letterbox_restart_dialog_corner_radius">28dp</dimen> + + <!-- The fixed width of the dialog if there is enough space in the parent. --> + <dimen name="letterbox_restart_dialog_width">348dp</dimen> + + <!-- The width of the top icon in the restart confirmation dialog. --> + <dimen name="letterbox_restart_dialog_title_icon_width">32dp</dimen> + + <!-- The height of the top icon in the restart confirmation dialog. --> + <dimen name="letterbox_restart_dialog_title_icon_height">32dp</dimen> + + <!-- The width of an icon in the restart confirmation dialog. --> + <dimen name="letterbox_restart_dialog_icon_width">40dp</dimen> + + <!-- The height of an icon in the restart confirmation dialog. --> + <dimen name="letterbox_restart_dialog_icon_height">32dp</dimen> + + <!-- The space between two actions in the restart confirmation dialog --> + <dimen name="letterbox_restart_dialog_space_between_actions">24dp</dimen> + + <!-- The width of the buttons in the restart dialog --> + <dimen name="letterbox_restart_dialog_button_width">82dp</dimen> + + <!-- The width of the buttons in the restart dialog --> + <dimen name="letterbox_restart_dialog_button_height">36dp</dimen> + + <!-- The corner radius of the buttons in the restart dialog --> + <dimen name="letterbox_restart_dialog_button_radius">18dp</dimen> + <!-- The width of the brand image on staring surface. --> <dimen name="starting_surface_brand_image_width">200dp</dimen> @@ -331,8 +364,8 @@ <!-- Height of button (32dp) + 2 * margin (5dp each). --> <dimen name="freeform_decor_caption_height">42dp</dimen> - <!-- Width of buttons (64dp) + handle (128dp) + padding (24dp total). --> - <dimen name="freeform_decor_caption_width">216dp</dimen> + <!-- Width of buttons (32dp each) + padding (128dp total). --> + <dimen name="freeform_decor_caption_menu_width">256dp</dimen> <dimen name="freeform_resize_handle">30dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 25eddf834f3d..250dac6cbaee 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -188,6 +188,23 @@ <!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] --> <string name="letterbox_education_expand_button_description">Expand for more information.</string> + <!-- The title of the restart confirmation dialog. [CHAR LIMIT=NONE] --> + <string name="letterbox_restart_dialog_title">Restart for a better view?</string> + + <!-- The description of the restart confirmation dialog. [CHAR LIMIT=NONE] --> + <string name="letterbox_restart_dialog_description">You can restart the app so it looks better on + your screen, but you may lose your progress or any unsaved changes + </string> + + <!-- Button text for dismissing the restart confirmation dialog. [CHAR LIMIT=20] --> + <string name="letterbox_restart_cancel">Cancel</string> + + <!-- Button text for dismissing the restart confirmation dialog. [CHAR LIMIT=20] --> + <string name="letterbox_restart_restart">Restart</string> + + <!-- Checkbox text for asking to not show the restart confirmation dialog again. [CHAR LIMIT=NONE] --> + <string name="letterbox_restart_dialog_checkbox_title">Don\u2019t show again</string> + <!-- Freeform window caption strings --> <!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] --> <string name="maximize_button_text">Maximize</string> diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml index a8597210d72e..e8f340cdd066 100644 --- a/libs/WindowManager/Shell/res/values/styles.xml +++ b/libs/WindowManager/Shell/res/values/styles.xml @@ -63,4 +63,42 @@ <item name="android:lineHeight">16sp</item> <item name="android:textColor">@color/tv_pip_edu_text</item> </style> + + <style name="RestartDialogTitleText"> + <item name="android:textSize">24sp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:lineSpacingExtra">2sp</item> + <item name="android:textAppearance"> + @*android:style/TextAppearance.DeviceDefault.Headline + </item> + </style> + + <style name="RestartDialogBodyText"> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.02</item> + <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:lineSpacingExtra">2sp</item> + <item name="android:textAppearance"> + @*android:style/TextAppearance.DeviceDefault.Body2 + </item> + </style> + + <style name="RestartDialogCheckboxText"> + <item name="android:textSize">16sp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:lineSpacingExtra">4sp</item> + <item name="android:textAppearance">@*android:style/TextAppearance.DeviceDefault</item> + </style> + + <style name="RestartDialogDismissButton"> + <item name="android:lineSpacingExtra">2sp</item> + <item name="android:textSize">14sp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="RestartDialogConfirmButton"> + <item name="android:lineSpacingExtra">2sp</item> + <item name="android:textSize">14sp</item> + <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> + </style> </resources> 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 236309207b4f..aaeef196b618 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 @@ -82,7 +82,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont /** Flag for U animation features */ public static boolean IS_U_ANIMATION_ENABLED = SystemProperties.getInt("persist.wm.debug.predictive_back_anim", - SETTING_VALUE_OFF) == SETTING_VALUE_ON; + SETTING_VALUE_ON) == SETTING_VALUE_ON; /** Predictive back animation developer option */ private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); // TODO (b/241808055) Find a appropriate time to remove during refactor 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 09dc68a4ccea..e24c2286013d 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 @@ -21,6 +21,7 @@ import static android.os.AsyncTask.Status.FINISHED; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; import android.annotation.DimenRes; +import android.annotation.Hide; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -125,7 +126,7 @@ public class Bubble implements BubbleViewProvider { private Icon mIcon; private boolean mIsBubble; private boolean mIsTextChanged; - private boolean mIsClearable; + private boolean mIsDismissable; private boolean mShouldSuppressNotificationDot; private boolean mShouldSuppressNotificationList; private boolean mShouldSuppressPeek; @@ -180,7 +181,7 @@ public class Bubble implements BubbleViewProvider { @VisibleForTesting(visibility = PRIVATE) public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, final int desiredHeight, final int desiredHeightResId, @Nullable final String title, - int taskId, @Nullable final String locus, Executor mainExecutor, + int taskId, @Nullable final String locus, boolean isDismissable, Executor mainExecutor, final Bubbles.BubbleMetadataFlagListener listener) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); @@ -189,6 +190,7 @@ public class Bubble implements BubbleViewProvider { mKey = key; mGroupKey = null; mLocusId = locus != null ? new LocusId(locus) : null; + mIsDismissable = isDismissable; mFlags = 0; mUser = shortcutInfo.getUserHandle(); mPackageName = shortcutInfo.getPackage(); @@ -245,6 +247,11 @@ public class Bubble implements BubbleViewProvider { return mKey; } + @Hide + public boolean isDismissable() { + return mIsDismissable; + } + /** * @see StatusBarNotification#getGroupKey() * @return the group key for this bubble, if one exists. @@ -526,7 +533,7 @@ public class Bubble implements BubbleViewProvider { mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent(); } - mIsClearable = entry.isClearable(); + mIsDismissable = entry.isDismissable(); mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot(); mShouldSuppressNotificationList = entry.shouldSuppressNotificationList(); mShouldSuppressPeek = entry.shouldSuppressPeek(); @@ -605,7 +612,7 @@ public class Bubble implements BubbleViewProvider { * Whether this notification should be shown in the shade. */ boolean showInShade() { - return !shouldSuppressNotification() || !mIsClearable; + return !shouldSuppressNotification() || !mIsDismissable; } /** @@ -870,7 +877,7 @@ public class Bubble implements BubbleViewProvider { pw.print(" desiredHeight: "); pw.println(getDesiredHeightString()); pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification()); pw.print(" autoExpand: "); pw.println(shouldAutoExpand()); - pw.print(" isClearable: "); pw.println(mIsClearable); + pw.print(" isDismissable: "); pw.println(mIsDismissable); pw.println(" bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null)); if (mExpandedView != null) { mExpandedView.dump(pw); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt index 3a5961462c87..e37c785f15f5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt @@ -109,7 +109,8 @@ internal class BubbleDataRepository( b.rawDesiredHeightResId, b.title, b.taskId, - b.locusId?.id + b.locusId?.id, + b.isDismissable ) } } @@ -205,6 +206,7 @@ internal class BubbleDataRepository( entity.title, entity.taskId, entity.locus, + entity.isDismissable, mainExecutor, bubbleMetadataFlagListener ) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java index 5f428269fb06..afe19c4b7363 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java @@ -38,18 +38,18 @@ public class BubbleEntry { private StatusBarNotification mSbn; private Ranking mRanking; - private boolean mIsClearable; + private boolean mIsDismissable; private boolean mShouldSuppressNotificationDot; private boolean mShouldSuppressNotificationList; private boolean mShouldSuppressPeek; public BubbleEntry(@NonNull StatusBarNotification sbn, - Ranking ranking, boolean isClearable, boolean shouldSuppressNotificationDot, + Ranking ranking, boolean isDismissable, boolean shouldSuppressNotificationDot, boolean shouldSuppressNotificationList, boolean shouldSuppressPeek) { mSbn = sbn; mRanking = ranking; - mIsClearable = isClearable; + mIsDismissable = isDismissable; mShouldSuppressNotificationDot = shouldSuppressNotificationDot; mShouldSuppressNotificationList = shouldSuppressNotificationList; mShouldSuppressPeek = shouldSuppressPeek; @@ -115,9 +115,9 @@ public class BubbleEntry { return mRanking.canBubble(); } - /** @return true if this notification is clearable. */ - public boolean isClearable() { - return mIsClearable; + /** @return true if this notification can be dismissed. */ + public boolean isDismissable() { + return mIsDismissable; } /** @return true if {@link Policy#SUPPRESSED_EFFECT_BADGE} set for this notification. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt index 186b9b1efa9a..9b2e26394605 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt @@ -27,5 +27,6 @@ data class BubbleEntity( @DimenRes val desiredHeightResId: Int, val title: String? = null, val taskId: Int, - val locus: String? = null + val locus: String? = null, + val isDismissable: Boolean = false ) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt index f4fa1835b7a5..48d8ccf40174 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt @@ -43,6 +43,7 @@ private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid" private const val ATTR_TITLE = "t" private const val ATTR_TASK_ID = "tid" private const val ATTR_LOCUS = "l" +private const val ATTR_DISMISSABLE = "d" /** * Writes the bubbles in xml format into given output stream. @@ -84,6 +85,7 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) } serializer.attribute(null, ATTR_TASK_ID, bubble.taskId.toString()) bubble.locus?.let { serializer.attribute(null, ATTR_LOCUS, it) } + serializer.attribute(null, ATTR_DISMISSABLE, bubble.isDismissable.toString()) serializer.endTag(null, TAG_BUBBLE) } catch (e: IOException) { throw RuntimeException(e) @@ -142,7 +144,8 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? { parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null, parser.getAttributeWithName(ATTR_TITLE), parser.getAttributeWithName(ATTR_TASK_ID)?.toInt() ?: INVALID_TASK_ID, - parser.getAttributeWithName(ATTR_LOCUS) + parser.getAttributeWithName(ATTR_LOCUS), + parser.getAttributeWithName(ATTR_DISMISSABLE)?.toBoolean() ?: false ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java index 4f33a71b80d5..06f0a70d3d0f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java @@ -16,11 +16,12 @@ package com.android.wm.shell.compatui; +import android.annotation.NonNull; +import android.app.TaskInfo; import android.content.Context; +import android.content.SharedPreferences; import android.provider.DeviceConfig; -import androidx.annotation.NonNull; - import com.android.wm.shell.R; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ShellMainThread; @@ -34,11 +35,27 @@ import javax.inject.Inject; @WMSingleton public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener { - static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = "enable_letterbox_restart_dialog"; + private static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = + "enable_letterbox_restart_confirmation_dialog"; - static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = + private static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = "enable_letterbox_reachability_education"; + private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG = true; + + private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = false; + + /** + * The name of the {@link SharedPreferences} that holds which user has seen the Restart + * confirmation dialog. + */ + private static final String DONT_SHOW_RESTART_DIALOG_PREF_NAME = "dont_show_restart_dialog"; + + /** + * The {@link SharedPreferences} instance for {@link #DONT_SHOW_RESTART_DIALOG_PREF_NAME}. + */ + private final SharedPreferences mSharedPreferences; + // Whether the extended restart dialog is enabled private boolean mIsRestartDialogEnabled; @@ -64,12 +81,15 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi mIsReachabilityEducationEnabled = context.getResources().getBoolean( R.bool.config_letterboxIsReachabilityEducationEnabled); mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, false); + DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, + DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG); mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, - false); + DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor, this); + mSharedPreferences = context.getSharedPreferences(DONT_SHOW_RESTART_DIALOG_PREF_NAME, + Context.MODE_PRIVATE); } /** @@ -102,18 +122,37 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi mIsReachabilityEducationOverrideEnabled = enabled; } + boolean getDontShowRestartDialogAgain(TaskInfo taskInfo) { + final int userId = taskInfo.userId; + final String packageName = taskInfo.topActivity.getPackageName(); + return mSharedPreferences.getBoolean( + getDontShowAgainRestartKey(userId, packageName), /* default= */ false); + } + + void setDontShowRestartDialogAgain(TaskInfo taskInfo) { + final int userId = taskInfo.userId; + final String packageName = taskInfo.topActivity.getPackageName(); + mSharedPreferences.edit().putBoolean(getDontShowAgainRestartKey(userId, packageName), + true).apply(); + } + @Override public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { - // TODO(b/263349751): Update flag and default value to true if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_RESTART_DIALOG)) { mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, - false); + DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG); } + // TODO(b/263349751): Update flag and default value to true if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION)) { mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_WINDOW_MANAGER, - KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, false); + KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, + DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION); } } -} + + private String getDontShowAgainRestartKey(int userId, String packageName) { + return packageName + "@" + userId; + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 6627de58cce3..3b2db5127316 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -24,6 +24,7 @@ import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import android.view.Display; import android.view.InsetsSourceControl; @@ -49,6 +50,7 @@ import com.android.wm.shell.transition.Transitions; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -91,6 +93,18 @@ public class CompatUIController implements OnDisplaysChangedListener, private final SparseArray<CompatUIWindowManager> mActiveCompatLayouts = new SparseArray<>(0); /** + * {@link SparseArray} that maps task ids to {@link RestartDialogWindowManager} that are + * currently visible + */ + private final SparseArray<RestartDialogWindowManager> mTaskIdToRestartDialogWindowManagerMap = + new SparseArray<>(0); + + /** + * {@link Set} of task ids for which we need to display a restart confirmation dialog + */ + private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>(); + + /** * The active Letterbox Education layout if there is one (there can be at most one active). * * <p>An active layout is a layout that is eligible to be shown for the associated task but @@ -111,12 +125,15 @@ public class CompatUIController implements OnDisplaysChangedListener, private final ShellExecutor mMainExecutor; private final Lazy<Transitions> mTransitionsLazy; private final DockStateReader mDockStateReader; + private final CompatUIConfiguration mCompatUIConfiguration; private CompatUICallback mCallback; // Only show each hint once automatically in the process life. private final CompatUIHintsState mCompatUIHintsState; + private final CompatUIShellCommandHandler mCompatUIShellCommandHandler; + // Indicates if the keyguard is currently showing, in which case compat UIs shouldn't // be shown. private boolean mKeyguardShowing; @@ -130,7 +147,9 @@ public class CompatUIController implements OnDisplaysChangedListener, SyncTransactionQueue syncQueue, ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy, - DockStateReader dockStateReader) { + DockStateReader dockStateReader, + CompatUIConfiguration compatUIConfiguration, + CompatUIShellCommandHandler compatUIShellCommandHandler) { mContext = context; mShellController = shellController; mDisplayController = displayController; @@ -140,14 +159,17 @@ public class CompatUIController implements OnDisplaysChangedListener, mMainExecutor = mainExecutor; mTransitionsLazy = transitionsLazy; mCompatUIHintsState = new CompatUIHintsState(); - shellInit.addInitCallback(this::onInit, this); mDockStateReader = dockStateReader; + mCompatUIConfiguration = compatUIConfiguration; + mCompatUIShellCommandHandler = compatUIShellCommandHandler; + shellInit.addInitCallback(this::onInit, this); } private void onInit() { mShellController.addKeyguardChangeListener(this); mDisplayController.addDisplayWindowListener(this); mImeController.addPositionProcessor(this); + mCompatUIShellCommandHandler.onInit(); } /** Sets the callback for UI interactions. */ @@ -164,6 +186,9 @@ public class CompatUIController implements OnDisplaysChangedListener, */ public void onCompatInfoChanged(TaskInfo taskInfo, @Nullable ShellTaskOrganizer.TaskListener taskListener) { + if (taskInfo != null && !taskInfo.topActivityInSizeCompat) { + mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); + } if (taskInfo.configuration == null || taskListener == null) { // Null token means the current foreground activity is not in compatibility mode. removeLayouts(taskInfo.taskId); @@ -172,6 +197,7 @@ public class CompatUIController implements OnDisplaysChangedListener, createOrUpdateCompatLayout(taskInfo, taskListener); createOrUpdateLetterboxEduLayout(taskInfo, taskListener); + createOrUpdateRestartDialogLayout(taskInfo, taskListener); } @Override @@ -278,7 +304,21 @@ public class CompatUIController implements OnDisplaysChangedListener, ShellTaskOrganizer.TaskListener taskListener) { return new CompatUIWindowManager(context, taskInfo, mSyncQueue, mCallback, taskListener, - mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState); + mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState, + mCompatUIConfiguration, this::onRestartButtonClicked); + } + + private void onRestartButtonClicked( + Pair<TaskInfo, ShellTaskOrganizer.TaskListener> taskInfoState) { + if (mCompatUIConfiguration.isRestartDialogEnabled() + && !mCompatUIConfiguration.getDontShowRestartDialogAgain( + taskInfoState.first)) { + // We need to show the dialog + mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId); + onCompatInfoChanged(taskInfoState.first, taskInfoState.second); + } else { + mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId); + } } private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo, @@ -327,6 +367,60 @@ public class CompatUIController implements OnDisplaysChangedListener, mActiveLetterboxEduLayout = null; } + private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo, + ShellTaskOrganizer.TaskListener taskListener) { + RestartDialogWindowManager layout = + mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId); + if (layout != null) { + // TODO(b/266262111) Handle theme change when taskListener changes + if (layout.getTaskListener() != taskListener) { + mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); + } + layout.setRequestRestartDialog( + mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId)); + // UI already exists, update the UI layout. + if (!layout.updateCompatInfo(taskInfo, taskListener, + showOnDisplay(layout.getDisplayId()))) { + // The layout is no longer eligible to be shown, remove from active layouts. + mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId); + } + return; + } + // Create a new UI layout. + final Context context = getOrCreateDisplayContext(taskInfo.displayId); + if (context == null) { + return; + } + layout = createRestartDialogWindowManager(context, taskInfo, taskListener); + layout.setRequestRestartDialog( + mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId)); + if (layout.createLayout(showOnDisplay(taskInfo.displayId))) { + // The new layout is eligible to be shown, add it the active layouts. + mTaskIdToRestartDialogWindowManagerMap.put(taskInfo.taskId, layout); + } + } + + @VisibleForTesting + RestartDialogWindowManager createRestartDialogWindowManager(Context context, TaskInfo taskInfo, + ShellTaskOrganizer.TaskListener taskListener) { + return new RestartDialogWindowManager(context, taskInfo, mSyncQueue, taskListener, + mDisplayController.getDisplayLayout(taskInfo.displayId), mTransitionsLazy.get(), + this::onRestartDialogCallback, this::onRestartDialogDismissCallback, + mCompatUIConfiguration); + } + + private void onRestartDialogCallback( + Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { + mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId); + mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId); + } + + private void onRestartDialogDismissCallback( + Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { + mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId); + onCompatInfoChanged(stateInfo.first, stateInfo.second); + } + private void removeLayouts(int taskId) { final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId); if (layout != null) { @@ -338,6 +432,14 @@ public class CompatUIController implements OnDisplaysChangedListener, mActiveLetterboxEduLayout.release(); mActiveLetterboxEduLayout = null; } + + final RestartDialogWindowManager restartLayout = + mTaskIdToRestartDialogWindowManagerMap.get(taskId); + if (restartLayout != null) { + restartLayout.release(); + mTaskIdToRestartDialogWindowManagerMap.remove(taskId); + mSetOfTaskIdsShowingRestartDialog.remove(taskId); + } } private Context getOrCreateDisplayContext(int displayId) { @@ -382,6 +484,14 @@ public class CompatUIController implements OnDisplaysChangedListener, if (mActiveLetterboxEduLayout != null && condition.test(mActiveLetterboxEduLayout)) { callback.accept(mActiveLetterboxEduLayout); } + for (int i = 0; i < mTaskIdToRestartDialogWindowManagerMap.size(); i++) { + final int taskId = mTaskIdToRestartDialogWindowManagerMap.keyAt(i); + final RestartDialogWindowManager layout = + mTaskIdToRestartDialogWindowManagerMap.get(taskId); + if (layout != null && condition.test(layout)) { + callback.accept(layout); + } + } } /** An implementation of {@link OnInsetsChangedListener} for a given display id. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index bce3ec4128e8..fe95d04bad3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -21,12 +21,14 @@ import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; import android.content.Context; import android.graphics.Rect; import android.util.Log; +import android.util.Pair; import android.view.LayoutInflater; import android.view.View; @@ -38,6 +40,8 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController.CompatUICallback; import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager; +import java.util.function.Consumer; + /** * Window manager for the Size Compat restart button and Camera Compat control. */ @@ -50,6 +54,13 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { private final CompatUICallback mCallback; + private final CompatUIConfiguration mCompatUIConfiguration; + + private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; + + @NonNull + private TaskInfo mTaskInfo; + // Remember the last reported states in case visibility changes due to keyguard or IME updates. @VisibleForTesting boolean mHasSizeCompat; @@ -68,12 +79,16 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { CompatUIWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, CompatUICallback callback, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, - CompatUIHintsState compatUIHintsState) { + CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration, + Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) { super(context, taskInfo, syncQueue, taskListener, displayLayout); + mTaskInfo = taskInfo; mCallback = callback; mHasSizeCompat = taskInfo.topActivityInSizeCompat; mCameraCompatControlState = taskInfo.cameraCompatControlState; mCompatUIHintsState = compatUIHintsState; + mCompatUIConfiguration = compatUIConfiguration; + mOnRestartButtonClicked = onRestartButtonClicked; } @Override @@ -119,6 +134,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { @Override public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, boolean canShow) { + mTaskInfo = taskInfo; final boolean prevHasSizeCompat = mHasSizeCompat; final int prevCameraCompatControlState = mCameraCompatControlState; mHasSizeCompat = taskInfo.topActivityInSizeCompat; @@ -138,7 +154,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { /** Called when the restart button is clicked. */ void onRestartButtonClicked() { - mCallback.onSizeCompatRestartButtonClicked(mTaskId); + mOnRestartButtonClicked.accept(Pair.create(mTaskInfo, getTaskListener())); } /** Called when the camera treatment button is clicked. */ @@ -199,8 +215,14 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth(); final int positionY = taskStableBounds.bottom - taskBounds.top - mLayout.getMeasuredHeight(); - + // To secure a proper visualisation, we hide the layout while updating the position of + // the {@link SurfaceControl} it belongs. + final int oldVisibility = mLayout.getVisibility(); + if (oldVisibility == View.VISIBLE) { + mLayout.setVisibility(View.GONE); + } updateSurfacePosition(positionX, positionY); + mLayout.setVisibility(oldVisibility); } private void updateVisibilityOfViews() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java index face24340a4e..db87f657c1b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java @@ -151,6 +151,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana @Override public void setConfiguration(Configuration configuration) { super.setConfiguration(configuration); + // TODO(b/266262111): Investigate loss of theme configuration when switching TaskListener mContext = mContext.createConfigurationContext(configuration); } @@ -169,6 +170,10 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana initSurface(mLeash); } + protected ShellTaskOrganizer.TaskListener getTaskListener() { + return mTaskListener; + } + /** Inits the z-order of the surface. */ private void initSurface(SurfaceControl leash) { final int z = getZOrder(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java new file mode 100644 index 000000000000..c53e6389331a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java @@ -0,0 +1,107 @@ +/* + * 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.compatui; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.CheckBox; +import android.widget.TextView; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.android.wm.shell.R; + +import java.util.function.Consumer; + +/** + * Container for a SCM restart confirmation dialog and background dim. + */ +public class RestartDialogLayout extends ConstraintLayout implements DialogContainerSupplier { + + private View mDialogContainer; + private TextView mDialogTitle; + private Drawable mBackgroundDim; + + public RestartDialogLayout(Context context) { + this(context, null); + } + + public RestartDialogLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public View getDialogContainerView() { + return mDialogContainer; + } + + TextView getDialogTitle() { + return mDialogTitle; + } + + @Override + public Drawable getBackgroundDimDrawable() { + return mBackgroundDim; + } + + /** + * Register a callback for the dismiss button and background dim. + * + * @param callback The callback to register or null if all on click listeners should be removed. + */ + void setDismissOnClickListener(@Nullable Runnable callback) { + final OnClickListener listener = callback == null ? null : view -> callback.run(); + findViewById(R.id.letterbox_restart_dialog_dismiss_button).setOnClickListener(listener); + } + + /** + * Register a callback for the restart button + * + * @param callback The callback to register or null if all on click listeners should be removed. + */ + void setRestartOnClickListener(@Nullable Consumer<Boolean> callback) { + final CheckBox dontShowAgainCheckbox = findViewById(R.id.letterbox_restart_dialog_checkbox); + final OnClickListener listener = callback == null ? null : view -> callback.accept( + dontShowAgainCheckbox.isChecked()); + findViewById(R.id.letterbox_restart_dialog_restart_button).setOnClickListener(listener); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mDialogContainer = findViewById(R.id.letterbox_restart_dialog_container); + mDialogTitle = findViewById(R.id.letterbox_restart_dialog_title); + mBackgroundDim = getBackground().mutate(); + // Set the alpha of the background dim to 0 for enter animation. + mBackgroundDim.setAlpha(0); + // We add a no-op on-click listener to the dialog container so that clicks on it won't + // propagate to the listener of the layout (which represents the background dim). + mDialogContainer.setOnClickListener(view -> {}); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java new file mode 100644 index 000000000000..10f25d0eef11 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java @@ -0,0 +1,259 @@ +/* + * 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.compatui; + +import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.TaskInfo; +import android.content.Context; +import android.graphics.Rect; +import android.provider.Settings; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.transition.Transitions; + +import java.util.function.Consumer; + +/** + * Window manager for the Restart Dialog. + * + * TODO(b/263484314): Create abstraction of RestartDialogWindowManager and LetterboxEduWindowManager + */ +class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { + + /** + * The restart dialog should be the topmost child of the Task in case there can be more + * than one child. + */ + private static final int Z_ORDER = Integer.MAX_VALUE; + + private final DialogAnimationController<RestartDialogLayout> mAnimationController; + + private final Transitions mTransitions; + + // Remember the last reported state in case visibility changes due to keyguard or IME updates. + private boolean mRequestRestartDialog; + + private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback; + + private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartCallback; + + private final CompatUIConfiguration mCompatUIConfiguration; + + /** + * The vertical margin between the dialog container and the task stable bounds (excluding + * insets). + */ + private final int mDialogVerticalMargin; + + @NonNull + private TaskInfo mTaskInfo; + + @Nullable + @VisibleForTesting + RestartDialogLayout mLayout; + + RestartDialogWindowManager(Context context, TaskInfo taskInfo, + SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, + DisplayLayout displayLayout, Transitions transitions, + Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback, + Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback, + CompatUIConfiguration compatUIConfiguration) { + this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions, + onRestartCallback, onDismissCallback, + new DialogAnimationController<>(context, "RestartDialogWindowManager"), + compatUIConfiguration); + } + + @VisibleForTesting + RestartDialogWindowManager(Context context, TaskInfo taskInfo, + SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, + DisplayLayout displayLayout, Transitions transitions, + Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback, + Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback, + DialogAnimationController<RestartDialogLayout> animationController, + CompatUIConfiguration compatUIConfiguration) { + super(context, taskInfo, syncQueue, taskListener, displayLayout); + mTaskInfo = taskInfo; + mTransitions = transitions; + mOnDismissCallback = onDismissCallback; + mOnRestartCallback = onRestartCallback; + mAnimationController = animationController; + mDialogVerticalMargin = (int) mContext.getResources().getDimension( + R.dimen.letterbox_restart_dialog_margin); + mCompatUIConfiguration = compatUIConfiguration; + } + + @Override + protected int getZOrder() { + return Z_ORDER; + } + + @Override + @Nullable + protected View getLayout() { + return mLayout; + } + + @Override + protected void removeLayout() { + mLayout = null; + } + + @Override + protected boolean eligibleToShowLayout() { + // We don't show this dialog if the user has explicitly selected so clicking on a checkbox. + return mRequestRestartDialog && !isTaskbarEduShowing() && (mLayout != null + || !mCompatUIConfiguration.getDontShowRestartDialogAgain(mTaskInfo)); + } + + @Override + protected View createLayout() { + mLayout = inflateLayout(); + updateDialogMargins(); + + // startEnterAnimation will be called immediately if shell-transitions are disabled. + mTransitions.runOnIdle(this::startEnterAnimation); + + return mLayout; + } + + void setRequestRestartDialog(boolean enabled) { + mRequestRestartDialog = enabled; + } + + @Override + public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, + boolean canShow) { + mTaskInfo = taskInfo; + return super.updateCompatInfo(taskInfo, taskListener, canShow); + } + + private void updateDialogMargins() { + if (mLayout == null) { + return; + } + final View dialogContainer = mLayout.getDialogContainerView(); + ViewGroup.MarginLayoutParams marginParams = + (ViewGroup.MarginLayoutParams) dialogContainer.getLayoutParams(); + + final Rect taskBounds = getTaskBounds(); + final Rect taskStableBounds = getTaskStableBounds(); + + marginParams.topMargin = taskStableBounds.top - taskBounds.top + mDialogVerticalMargin; + marginParams.bottomMargin = + taskBounds.bottom - taskStableBounds.bottom + mDialogVerticalMargin; + dialogContainer.setLayoutParams(marginParams); + } + + private RestartDialogLayout inflateLayout() { + return (RestartDialogLayout) LayoutInflater.from(mContext).inflate( + R.layout.letterbox_restart_dialog_layout, null); + } + + private void startEnterAnimation() { + if (mLayout == null) { + // Dialog has already been released. + return; + } + mAnimationController.startEnterAnimation(mLayout, /* endCallback= */ + this::onDialogEnterAnimationEnded); + } + + private void onDialogEnterAnimationEnded() { + if (mLayout == null) { + // Dialog has already been released. + return; + } + mLayout.setDismissOnClickListener(this::onDismiss); + mLayout.setRestartOnClickListener(dontShowAgain -> { + if (mLayout != null) { + mLayout.setDismissOnClickListener(null); + mAnimationController.startExitAnimation(mLayout, () -> { + release(); + }); + } + if (dontShowAgain) { + mCompatUIConfiguration.setDontShowRestartDialogAgain(mTaskInfo); + } + mOnRestartCallback.accept(Pair.create(mTaskInfo, getTaskListener())); + }); + // Focus on the dialog title for accessibility. + mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } + + private void onDismiss() { + if (mLayout == null) { + return; + } + + mLayout.setDismissOnClickListener(null); + mAnimationController.startExitAnimation(mLayout, () -> { + release(); + mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener())); + }); + } + + @Override + public void release() { + mAnimationController.cancelAnimation(); + super.release(); + } + + @Override + protected void onParentBoundsChanged() { + if (mLayout == null) { + return; + } + // Both the layout dimensions and dialog margins depend on the parent bounds. + WindowManager.LayoutParams windowLayoutParams = getWindowLayoutParams(); + mLayout.setLayoutParams(windowLayoutParams); + updateDialogMargins(); + relayout(windowLayoutParams); + } + + @Override + protected void updateSurfacePosition() { + // Nothing to do, since the position of the surface is fixed to the top left corner (0,0) + // of the task (parent surface), which is the default position of a surface. + } + + @Override + protected WindowManager.LayoutParams getWindowLayoutParams() { + final Rect taskBounds = getTaskBounds(); + return getWindowLayoutParams(/* width= */ taskBounds.width(), /* height= */ + taskBounds.height()); + } + + @VisibleForTesting + boolean isTaskbarEduShowing() { + return Settings.Secure.getInt(mContext.getContentResolver(), + LAUNCHER_TASKBAR_EDUCATION_SHOWING, /* def= */ 0) == 1; + } +} 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 09f5cf1d31e4..25c430c27457 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 @@ -56,7 +56,9 @@ import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; +import com.android.wm.shell.compatui.CompatUIConfiguration; import com.android.wm.shell.compatui.CompatUIController; +import com.android.wm.shell.compatui.CompatUIShellCommandHandler; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; @@ -196,10 +198,11 @@ public abstract class WMShellBaseModule { DisplayController displayController, DisplayInsetsController displayInsetsController, DisplayImeController imeController, SyncTransactionQueue syncQueue, @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy, - DockStateReader dockStateReader) { + DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration, + CompatUIShellCommandHandler compatUIShellCommandHandler) { return new CompatUIController(context, shellInit, shellController, displayController, displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy, - dockStateReader); + dockStateReader, compatUIConfiguration, compatUIShellCommandHandler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java index 7096a645ef85..283b1ec0f752 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip; +import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; @@ -41,6 +42,11 @@ public abstract class PipContentOverlay { } } + @Nullable + public SurfaceControl getLeash() { + return mLeash; + } + /** * Animates the internal {@link #mLeash} by a given fraction. * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 129924ad5d05..476a7ec0fdc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -143,8 +143,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM || (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD - && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode() - == WINDOWING_MODE_FREEFORM); + && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode() + == WINDOWING_MODE_FREEFORM); } private void createWindowDecoration( @@ -174,26 +174,29 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { final CaptionTouchEventListener touchEventListener = new CaptionTouchEventListener(taskInfo, taskPositioner); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); - windowDecoration.setDragResizeCallback(taskPositioner); + windowDecoration.setDragPositioningCallback(taskPositioner); + windowDecoration.setDragDetector(touchEventListener.mDragDetector); windowDecoration.relayout(taskInfo, startT, finishT); setupCaptionColor(taskInfo, windowDecoration); } private class CaptionTouchEventListener implements - View.OnClickListener, View.OnTouchListener { + View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { private final int mTaskId; private final WindowContainerToken mTaskToken; - private final DragResizeCallback mDragResizeCallback; + private final DragPositioningCallback mDragPositioningCallback; + private final DragDetector mDragDetector; private int mDragPointerId = -1; private CaptionTouchEventListener( RunningTaskInfo taskInfo, - DragResizeCallback dragResizeCallback) { + DragPositioningCallback dragPositioningCallback) { mTaskId = taskInfo.taskId; mTaskToken = taskInfo.token; - mDragResizeCallback = dragResizeCallback; + mDragPositioningCallback = dragPositioningCallback; + mDragDetector = new DragDetector(this); } @Override @@ -216,7 +219,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (v.getId() != R.id.caption) { return false; } - handleEventForMove(e); + mDragDetector.onMotionEvent(e); if (e.getAction() != MotionEvent.ACTION_DOWN) { return false; @@ -235,32 +238,34 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { * @param e {@link MotionEvent} to process * @return {@code true} if a drag is happening; or {@code false} if it is not */ - private void handleEventForMove(MotionEvent e) { + @Override + public boolean handleMotionEvent(MotionEvent e) { final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { - return; + return false; } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mDragPointerId = e.getPointerId(0); - mDragResizeCallback.onDragResizeStart( + mDragPositioningCallback.onDragPositioningStart( 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); break; } case MotionEvent.ACTION_MOVE: { int dragPointerIdx = e.findPointerIndex(mDragPointerId); - mDragResizeCallback.onDragResizeMove( + mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); break; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { int dragPointerIdx = e.findPointerIndex(mDragPointerId); - mDragResizeCallback.onDragResizeEnd( + mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); break; } } + return true; } } -} +}
\ No newline at end of file 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 d26f1fc8ef1b..060dc4e05b46 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 @@ -47,9 +47,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL private View.OnClickListener mOnCaptionButtonClickListener; private View.OnTouchListener mOnCaptionTouchListener; - private DragResizeCallback mDragResizeCallback; + private DragPositioningCallback mDragPositioningCallback; private DragResizeInputListener mDragResizeListener; - private final DragDetector mDragDetector; + private DragDetector mDragDetector; private RelayoutParams mRelayoutParams = new RelayoutParams(); private final RelayoutResult<WindowDecorLinearLayout> mResult = @@ -69,7 +69,6 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mHandler = handler; mChoreographer = choreographer; mSyncQueue = syncQueue; - mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop()); } void setCaptionListeners( @@ -79,8 +78,13 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mOnCaptionTouchListener = onCaptionTouchListener; } - void setDragResizeCallback(DragResizeCallback dragResizeCallback) { - mDragResizeCallback = dragResizeCallback; + void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) { + mDragPositioningCallback = dragPositioningCallback; + } + + void setDragDetector(DragDetector dragDetector) { + mDragDetector = dragDetector; + mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop()); } @Override @@ -147,7 +151,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mChoreographer, mDisplay.getDisplayId(), mDecorationContainerSurface, - mDragResizeCallback); + mDragPositioningCallback); } final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 2863adcc8f5e..606cf2854802 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -205,34 +205,29 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } private class DesktopModeTouchEventListener implements - View.OnClickListener, View.OnTouchListener { + View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { private final int mTaskId; private final WindowContainerToken mTaskToken; - private final DragResizeCallback mDragResizeCallback; + private final DragPositioningCallback mDragPositioningCallback; private final DragDetector mDragDetector; private int mDragPointerId = -1; private DesktopModeTouchEventListener( RunningTaskInfo taskInfo, - DragResizeCallback dragResizeCallback, - DragDetector dragDetector) { + DragPositioningCallback dragPositioningCallback) { mTaskId = taskInfo.taskId; mTaskToken = taskInfo.token; - mDragResizeCallback = dragResizeCallback; - mDragDetector = dragDetector; + mDragPositioningCallback = dragPositioningCallback; + mDragDetector = new DragDetector(this); } @Override public void onClick(View v) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final int id = v.getId(); - if (id == R.id.close_window) { - mTaskOperations.closeTask(mTaskToken); - } else if (id == R.id.back_button) { - mTaskOperations.injectBackKey(); - } else if (id == R.id.caption_handle) { + if (id == R.id.caption_handle) { decoration.createHandleMenu(); } else if (id == R.id.desktop_button) { mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); @@ -254,8 +249,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return false; } if (id == R.id.caption_handle) { - isDrag = mDragDetector.detectDragEvent(e); - handleEventForMove(e); + isDrag = mDragDetector.onMotionEvent(e); } if (e.getAction() != MotionEvent.ACTION_DOWN) { return isDrag; @@ -272,30 +266,30 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { /** * @param e {@link MotionEvent} to process - * @return {@code true} if a drag is happening; or {@code false} if it is not + * @return {@code true} if the motion event is handled. */ - private void handleEventForMove(MotionEvent e) { + @Override + public boolean handleMotionEvent(MotionEvent e) { final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (DesktopModeStatus.isProto2Enabled() && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { - return; + return false; } if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent() - && mDesktopModeController.get().getDisplayAreaWindowingMode( - taskInfo.displayId) + && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId) == WINDOWING_MODE_FULLSCREEN) { - return; + return false; } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mDragPointerId = e.getPointerId(0); - mDragResizeCallback.onDragResizeStart( + mDragPositioningCallback.onDragPositioningStart( 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); break; } case MotionEvent.ACTION_MOVE: { final int dragPointerIdx = e.findPointerIndex(mDragPointerId); - mDragResizeCallback.onDragResizeMove( + mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); break; } @@ -304,7 +298,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int dragPointerIdx = e.findPointerIndex(mDragPointerId); final int statusBarHeight = mDisplayController .getDisplayLayout(taskInfo.displayId).stableInsets().top; - mDragResizeCallback.onDragResizeEnd( + mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); if (e.getRawY(dragPointerIdx) <= statusBarHeight) { if (DesktopModeStatus.isProto2Enabled()) { @@ -324,6 +318,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { break; } } + return true; } } @@ -560,10 +555,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener); final DesktopModeTouchEventListener touchEventListener = - new DesktopModeTouchEventListener( - taskInfo, taskPositioner, windowDecoration.getDragDetector()); + new DesktopModeTouchEventListener(taskInfo, taskPositioner); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); - windowDecoration.setDragResizeCallback(taskPositioner); + windowDecoration.setDragPositioningCallback(taskPositioner); + windowDecoration.setDragDetector(touchEventListener.mDragDetector); windowDecoration.relayout(taskInfo, startT, finishT); incrementEventReceiverTasks(taskInfo.displayId); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 1a38d24a4ab1..744c18fe7adb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -25,7 +25,6 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Point; import android.graphics.PointF; -import android.graphics.Rect; import android.graphics.drawable.VectorDrawable; import android.os.Handler; import android.view.Choreographer; @@ -55,11 +54,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private View.OnClickListener mOnCaptionButtonClickListener; private View.OnTouchListener mOnCaptionTouchListener; - private DragResizeCallback mDragResizeCallback; + private DragPositioningCallback mDragPositioningCallback; private DragResizeInputListener mDragResizeListener; - private final DragDetector mDragDetector; + private DragDetector mDragDetector; private RelayoutParams mRelayoutParams = new RelayoutParams(); + private final int mCaptionMenuWidthId = R.dimen.freeform_decor_caption_menu_width; private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult = new WindowDecoration.RelayoutResult<>(); @@ -81,7 +81,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mChoreographer = choreographer; mSyncQueue = syncQueue; mDesktopActive = DesktopModeStatus.isActive(mContext); - mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop()); } void setCaptionListeners( @@ -91,12 +90,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mOnCaptionTouchListener = onCaptionTouchListener; } - void setDragResizeCallback(DragResizeCallback dragResizeCallback) { - mDragResizeCallback = dragResizeCallback; + void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) { + mDragPositioningCallback = dragPositioningCallback; } - DragDetector getDragDetector() { - return mDragDetector; + void setDragDetector(DragDetector dragDetector) { + mDragDetector = dragDetector; + mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop()); } @Override @@ -131,22 +131,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; - mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width; mRelayoutParams.mShadowRadiusId = shadowRadiusID; if (isDragResizeable) { mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); } - final Resources resources = mDecorWindowContext.getResources(); - final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds(); - final int captionHeight = loadDimensionPixelSize(resources, - mRelayoutParams.mCaptionHeightId); - final int captionWidth = loadDimensionPixelSize(resources, - mRelayoutParams.mCaptionWidthId); - final int captionLeft = taskBounds.width() / 2 - - captionWidth / 2; - final int captionTop = taskBounds.top - <= captionHeight / 2 ? 0 : -captionHeight / 2; - mRelayoutParams.setCaptionPosition(captionLeft, captionTop); relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo @@ -191,7 +179,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mChoreographer, mDisplay.getDisplayId(), mDecorationContainerSurface, - mDragResizeCallback); + mDragPositioningCallback); } final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) @@ -212,10 +200,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private void setupRootView() { final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); caption.setOnTouchListener(mOnCaptionTouchListener); - final View close = caption.findViewById(R.id.close_window); - close.setOnClickListener(mOnCaptionButtonClickListener); - final View back = caption.findViewById(R.id.back_button); - back.setOnClickListener(mOnCaptionButtonClickListener); final View handle = caption.findViewById(R.id.caption_handle); handle.setOnTouchListener(mOnCaptionTouchListener); handle.setOnClickListener(mOnCaptionButtonClickListener); @@ -230,8 +214,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin desktop.setOnClickListener(mOnCaptionButtonClickListener); final View split = menu.findViewById(R.id.split_screen_button); split.setOnClickListener(mOnCaptionButtonClickListener); - final View more = menu.findViewById(R.id.more_button); - more.setOnClickListener(mOnCaptionButtonClickListener); } /** @@ -264,10 +246,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin void setButtonVisibility(boolean visible) { final int visibility = visible ? View.VISIBLE : View.GONE; final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); - final View back = caption.findViewById(R.id.back_button); - final View close = caption.findViewById(R.id.close_window); - back.setVisibility(visibility); - close.setVisibility(visibility); final int buttonTintColorRes = mDesktopActive ? R.color.decor_button_dark_color : R.color.decor_button_light_color; @@ -297,14 +275,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin void createHandleMenu() { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); final Resources resources = mDecorWindowContext.getResources(); - final int x = mRelayoutParams.mCaptionX; - final int y = mRelayoutParams.mCaptionY; - final int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId); - final int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId); + final int captionWidth = mTaskInfo.getConfiguration() + .windowConfiguration.getBounds().width(); + final int menuWidth = loadDimensionPixelSize( + resources, mCaptionMenuWidthId); + final int height = loadDimensionPixelSize( + resources, mRelayoutParams.mCaptionHeightId); + final int x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2) + - mResult.mDecorContainerOffsetX; + final int y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY; String namePrefix = "Caption Menu"; - mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, - x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY, - width, height); + mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y, + menuWidth, height); mSyncQueue.runInSync(transaction -> { transaction.merge(t); t.close(); @@ -370,7 +352,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (mResult.mRootView == null) return false; final PointF inputPoint = offsetCaptionLocation(ev); final View view = mResult.mRootView.findViewById(layoutId); - return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0); + return view != null && pointInView(view, inputPoint.x, inputPoint.y); } boolean checkTouchEventInHandle(MotionEvent ev) { @@ -387,32 +369,39 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ void checkClickEvent(MotionEvent ev) { if (mResult.mRootView == null) return; - final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); - final PointF inputPoint = offsetCaptionLocation(ev); if (!isHandleMenuActive()) { + final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); final View handle = caption.findViewById(R.id.caption_handle); - clickIfPointInView(inputPoint, handle); + clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle); } else { final View menu = mHandleMenu.mWindowViewHost.getView(); + final int captionWidth = mTaskInfo.getConfiguration().windowConfiguration + .getBounds().width(); + final int menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) + - (menu.getWidth() / 2); + final PointF inputPoint = new PointF(ev.getX() - menuX, ev.getY()); final View fullscreen = menu.findViewById(R.id.fullscreen_button); if (clickIfPointInView(inputPoint, fullscreen)) return; final View desktop = menu.findViewById(R.id.desktop_button); if (clickIfPointInView(inputPoint, desktop)) return; final View split = menu.findViewById(R.id.split_screen_button); if (clickIfPointInView(inputPoint, split)) return; - final View more = menu.findViewById(R.id.more_button); - clickIfPointInView(inputPoint, more); } } private boolean clickIfPointInView(PointF inputPoint, View v) { - if (v.pointInView(inputPoint.x - v.getLeft(), inputPoint.y, 0)) { + if (pointInView(v, inputPoint.x, inputPoint.y)) { mOnCaptionButtonClickListener.onClick(v); return true; } return false; } + private boolean pointInView(View v, float x, float y) { + return v != null && v.getLeft() <= x && v.getRight() >= x + && v.getTop() <= y && v.getBottom() >= y; + } + @Override public void close() { closeDragResizeListener(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java index 0abe8ab2e30b..4fac843b05db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java @@ -16,6 +16,7 @@ package com.android.wm.shell.windowdecor; +import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; @@ -25,63 +26,82 @@ import android.graphics.PointF; import android.view.MotionEvent; /** - * A detector for touch inputs that differentiates between drag and click inputs. + * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow + * of {@link MotionEvent} and generates a new flow of motion events with slop in consideration to + * the event handler. In particular, it always passes down, up and cancel events. It'll pass move + * events only when there is at least one move event that's beyond the slop threshold. For the + * purpose of convenience it also passes all events of other actions. + * * All touch events must be passed through this class to track a drag event. */ -public class DragDetector { +class DragDetector { + private final MotionEventHandler mEventHandler; + + private final PointF mInputDownPoint = new PointF(); private int mTouchSlop; - private PointF mInputDownPoint; private boolean mIsDragEvent; private int mDragPointerId; - public DragDetector(int touchSlop) { - mTouchSlop = touchSlop; - mInputDownPoint = new PointF(); - mIsDragEvent = false; - mDragPointerId = -1; + + private boolean mResultOfDownAction; + + DragDetector(MotionEventHandler eventHandler) { + resetState(); + mEventHandler = eventHandler; } /** - * Determine if {@link MotionEvent} is part of a drag event. - * @return {@code true} if this is a drag event, {@code false} if not - */ - public boolean detectDragEvent(MotionEvent ev) { - switch (ev.getAction()) { + * The receiver of the {@link MotionEvent} flow. + * + * @return the result returned by {@link #mEventHandler}, or the result when + * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed + */ + boolean onMotionEvent(MotionEvent ev) { + switch (ev.getActionMasked()) { case ACTION_DOWN: { + // Only touch screens generate noisy moves. + mIsDragEvent = (ev.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN; mDragPointerId = ev.getPointerId(0); float rawX = ev.getRawX(0); float rawY = ev.getRawY(0); mInputDownPoint.set(rawX, rawY); - return false; + mResultOfDownAction = mEventHandler.handleMotionEvent(ev); + return mResultOfDownAction; } case ACTION_MOVE: { if (!mIsDragEvent) { int dragPointerIndex = ev.findPointerIndex(mDragPointerId); float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x; float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y; - if (Math.hypot(dx, dy) > mTouchSlop) { - mIsDragEvent = true; - } + mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop; + } + if (mIsDragEvent) { + return mEventHandler.handleMotionEvent(ev); + } else { + return mResultOfDownAction; } - return mIsDragEvent; - } - case ACTION_UP: { - boolean result = mIsDragEvent; - mIsDragEvent = false; - mInputDownPoint.set(0, 0); - mDragPointerId = -1; - return result; } + case ACTION_UP: case ACTION_CANCEL: { - mIsDragEvent = false; - mInputDownPoint.set(0, 0); - mDragPointerId = -1; - return false; + resetState(); + return mEventHandler.handleMotionEvent(ev); } + default: + return mEventHandler.handleMotionEvent(ev); } - return mIsDragEvent; } - public void setTouchSlop(int touchSlop) { + void setTouchSlop(int touchSlop) { mTouchSlop = touchSlop; } + + private void resetState() { + mIsDragEvent = false; + mInputDownPoint.set(0, 0); + mDragPointerId = -1; + mResultOfDownAction = false; + } + + interface MotionEventHandler { + boolean handleMotionEvent(MotionEvent ev); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java index ee160a15df19..0191c609a8b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java @@ -19,28 +19,28 @@ package com.android.wm.shell.windowdecor; /** * Callback called when receiving drag-resize or drag-move related input events. */ -public interface DragResizeCallback { +public interface DragPositioningCallback { /** - * Called when a drag resize starts. + * Called when a drag-resize or drag-move starts. * * @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use * {@code 0} to indicate it's a move - * @param x x coordinate in window decoration coordinate system where the drag resize starts - * @param y y coordinate in window decoration coordinate system where the drag resize starts + * @param x x coordinate in window decoration coordinate system where the drag starts + * @param y y coordinate in window decoration coordinate system where the drag starts */ - void onDragResizeStart(@TaskPositioner.CtrlType int ctrlType, float x, float y); + void onDragPositioningStart(@TaskPositioner.CtrlType int ctrlType, float x, float y); /** - * Called when the pointer moves during a drag resize. + * Called when the pointer moves during a drag-resize or drag-move. * @param x x coordinate in window decoration coordinate system of the new pointer location * @param y y coordinate in window decoration coordinate system of the new pointer location */ - void onDragResizeMove(float x, float y); + void onDragPositioningMove(float x, float y); /** - * Called when a drag resize stops. + * Called when a drag-resize or drag-move stops. * @param x x coordinate in window decoration coordinate system where the drag resize stops * @param y y coordinate in window decoration coordinate system where the drag resize stops */ - void onDragResizeEnd(float x, float y); + void onDragPositioningEnd(float x, float y); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index d3f1332f6224..7d954ad92285 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -48,7 +48,6 @@ import com.android.internal.view.BaseIWindow; * Task edges are for resizing with a mouse. * Task corners are for resizing with touch input. */ -// TODO(b/251270585): investigate how to pass taps in corners to the tasks class DragResizeInputListener implements AutoCloseable { private static final String TAG = "DragResizeInputListener"; @@ -63,7 +62,7 @@ class DragResizeInputListener implements AutoCloseable { private final SurfaceControl mDecorationSurface; private final InputChannel mInputChannel; private final TaskResizeInputEventReceiver mInputEventReceiver; - private final com.android.wm.shell.windowdecor.DragResizeCallback mCallback; + private final DragPositioningCallback mCallback; private int mWidth; private int mHeight; @@ -84,7 +83,7 @@ class DragResizeInputListener implements AutoCloseable { Choreographer choreographer, int displayId, SurfaceControl decorationSurface, - DragResizeCallback callback) { + DragPositioningCallback callback) { mInputManager = context.getSystemService(InputManager.class); mHandler = handler; mChoreographer = choreographer; @@ -115,7 +114,8 @@ class DragResizeInputListener implements AutoCloseable { mInputEventReceiver = new TaskResizeInputEventReceiver( mInputChannel, mHandler, mChoreographer); mCallback = callback; - mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop()); + mDragDetector = new DragDetector(mInputEventReceiver); + mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop()); } /** @@ -223,12 +223,12 @@ class DragResizeInputListener implements AutoCloseable { } } - private class TaskResizeInputEventReceiver extends InputEventReceiver { + private class TaskResizeInputEventReceiver extends InputEventReceiver + implements DragDetector.MotionEventHandler { private final Choreographer mChoreographer; private final Runnable mConsumeBatchEventRunnable; private boolean mConsumeBatchEventScheduled; private boolean mShouldHandleEvents; - private boolean mDragging; private TaskResizeInputEventReceiver( InputChannel inputChannel, Handler handler, Choreographer choreographer) { @@ -270,15 +270,15 @@ class DragResizeInputListener implements AutoCloseable { if (!(inputEvent instanceof MotionEvent)) { return false; } + return mDragDetector.onMotionEvent((MotionEvent) inputEvent); + } - MotionEvent e = (MotionEvent) inputEvent; + @Override + public boolean handleMotionEvent(MotionEvent e) { boolean result = false; // Check if this is a touch event vs mouse event. // Touch events are tracked in four corners. Other events are tracked in resize edges. boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; - if (isTouch) { - mDragging = mDragDetector.detectDragEvent(e); - } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { float x = e.getX(0); @@ -293,7 +293,7 @@ class DragResizeInputListener implements AutoCloseable { float rawX = e.getRawX(0); float rawY = e.getRawY(0); int ctrlType = calculateCtrlType(isTouch, x, y); - mCallback.onDragResizeStart(ctrlType, rawX, rawY); + mCallback.onDragPositioningStart(ctrlType, rawX, rawY); result = true; } break; @@ -305,24 +305,17 @@ class DragResizeInputListener implements AutoCloseable { int dragPointerIndex = e.findPointerIndex(mDragPointerId); float rawX = e.getRawX(dragPointerIndex); float rawY = e.getRawY(dragPointerIndex); - if (!isTouch) { - // For all other types allow immediate dragging. - mDragging = true; - } - if (mDragging) { - mCallback.onDragResizeMove(rawX, rawY); - result = true; - } + mCallback.onDragPositioningMove(rawX, rawY); + result = true; break; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { - if (mShouldHandleEvents && mDragging) { + if (mShouldHandleEvents) { int dragPointerIndex = e.findPointerIndex(mDragPointerId); - mCallback.onDragResizeEnd( + mCallback.onDragPositioningEnd( e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex)); } - mDragging = false; mShouldHandleEvents = false; mDragPointerId = -1; result = true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java index 20631f85453f..d3f92277bf03 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java @@ -23,7 +23,7 @@ import android.window.WindowContainerTransaction; import com.android.wm.shell.ShellTaskOrganizer; -class TaskPositioner implements DragResizeCallback { +class TaskPositioner implements DragPositioningCallback { @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM}) @interface CtrlType {} @@ -38,11 +38,9 @@ class TaskPositioner implements DragResizeCallback { private final WindowDecoration mWindowDecoration; private final Rect mTaskBoundsAtDragStart = new Rect(); - private final PointF mResizeStartPoint = new PointF(); - private final Rect mResizeTaskBounds = new Rect(); - // Whether the |dragResizing| hint should be sent with the next bounds change WCT. - // Used to optimized fluid resizing of freeform tasks. - private boolean mPendingDragResizeHint = false; + private final PointF mRepositionStartPoint = new PointF(); + private final Rect mRepositionTaskBounds = new Rect(); + private boolean mHasMoved = false; private int mCtrlType; private DragStartListener mDragStartListener; @@ -59,72 +57,84 @@ class TaskPositioner implements DragResizeCallback { } @Override - public void onDragResizeStart(int ctrlType, float x, float y) { - if (ctrlType != CTRL_TYPE_UNDEFINED) { - // The task is being resized, send the |dragResizing| hint to core with the first - // bounds-change wct. - mPendingDragResizeHint = true; - } + public void onDragPositioningStart(int ctrlType, float x, float y) { + mHasMoved = false; mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId); mCtrlType = ctrlType; mTaskBoundsAtDragStart.set( mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds()); - mResizeStartPoint.set(x, y); + mRepositionStartPoint.set(x, y); } @Override - public void onDragResizeMove(float x, float y) { + public void onDragPositioningMove(float x, float y) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (changeBounds(wct, x, y)) { - if (mPendingDragResizeHint) { + // The task is being resized, send the |dragResizing| hint to core with the first + // bounds-change wct. + if (!mHasMoved && mCtrlType != CTRL_TYPE_UNDEFINED) { // This is the first bounds change since drag resize operation started. wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */); - mPendingDragResizeHint = false; } mTaskOrganizer.applyTransaction(wct); + mHasMoved = true; } } @Override - public void onDragResizeEnd(float x, float y) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */); - changeBounds(wct, x, y); - mTaskOrganizer.applyTransaction(wct); + public void onDragPositioningEnd(float x, float y) { + // |mHasMoved| being false means there is no real change to the task bounds in WM core, so + // we don't need a WCT to finish it. + if (mHasMoved) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */); + changeBounds(wct, x, y); + mTaskOrganizer.applyTransaction(wct); + } - mCtrlType = 0; + mCtrlType = CTRL_TYPE_UNDEFINED; mTaskBoundsAtDragStart.setEmpty(); - mResizeStartPoint.set(0, 0); - mPendingDragResizeHint = false; + mRepositionStartPoint.set(0, 0); + mHasMoved = false; } private boolean changeBounds(WindowContainerTransaction wct, float x, float y) { - float deltaX = x - mResizeStartPoint.x; - mResizeTaskBounds.set(mTaskBoundsAtDragStart); + // |mRepositionTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not + // true, we can compare it against |mTaskBoundsAtDragStart|. + final int oldLeft = mHasMoved ? mRepositionTaskBounds.left : mTaskBoundsAtDragStart.left; + final int oldTop = mHasMoved ? mRepositionTaskBounds.top : mTaskBoundsAtDragStart.top; + final int oldRight = mHasMoved ? mRepositionTaskBounds.right : mTaskBoundsAtDragStart.right; + final int oldBottom = + mHasMoved ? mRepositionTaskBounds.bottom : mTaskBoundsAtDragStart.bottom; + + final float deltaX = x - mRepositionStartPoint.x; + final float deltaY = y - mRepositionStartPoint.y; + mRepositionTaskBounds.set(mTaskBoundsAtDragStart); if ((mCtrlType & CTRL_TYPE_LEFT) != 0) { - mResizeTaskBounds.left += deltaX; + mRepositionTaskBounds.left += deltaX; } if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) { - mResizeTaskBounds.right += deltaX; + mRepositionTaskBounds.right += deltaX; } - float deltaY = y - mResizeStartPoint.y; if ((mCtrlType & CTRL_TYPE_TOP) != 0) { - mResizeTaskBounds.top += deltaY; + mRepositionTaskBounds.top += deltaY; } if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) { - mResizeTaskBounds.bottom += deltaY; + mRepositionTaskBounds.bottom += deltaY; } - if (mCtrlType == 0) { - mResizeTaskBounds.offset((int) deltaX, (int) deltaY); + if (mCtrlType == CTRL_TYPE_UNDEFINED) { + mRepositionTaskBounds.offset((int) deltaX, (int) deltaY); } - if (!mResizeTaskBounds.isEmpty()) { - wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds); - return true; + if (oldLeft == mRepositionTaskBounds.left && oldTop == mRepositionTaskBounds.top + && oldRight == mRepositionTaskBounds.right + && oldBottom == mRepositionTaskBounds.bottom) { + return false; } - return false; + wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds); + return true; } interface DragStartListener { 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 7f85988d1377..62b72f377cdb 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 @@ -252,9 +252,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); - final int captionWidth = params.mCaptionWidthId == Resources.ID_NULL - ? taskBounds.width() - : loadDimensionPixelSize(resources, params.mCaptionWidthId); + final int captionWidth = taskBounds.width(); startT.setPosition( mCaptionContainerSurface, @@ -428,7 +426,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> RunningTaskInfo mRunningTaskInfo; int mLayoutResId; int mCaptionHeightId; - int mCaptionWidthId; int mShadowRadiusId; int mOutsetTopId; @@ -454,7 +451,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> void reset() { mLayoutResId = Resources.ID_NULL; mCaptionHeightId = Resources.ID_NULL; - mCaptionWidthId = Resources.ID_NULL; mShadowRadiusId = Resources.ID_NULL; mOutsetTopId = Resources.ID_NULL; diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml index 8949a75d1a15..27d40b2b25b2 100644 --- a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml +++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml @@ -17,7 +17,7 @@ <resources> <!-- Resources used in WindowDecorationTests --> <dimen name="test_freeform_decor_caption_height">32dp</dimen> - <dimen name="test_freeform_decor_caption_width">216dp</dimen> + <dimen name="test_freeform_decor_caption_menu_width">216dp</dimen> <dimen name="test_window_decor_left_outset">10dp</dimen> <dimen name="test_window_decor_top_outset">20dp</dimen> <dimen name="test_window_decor_right_outset">30dp</dimen> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt index 1636c5f73133..0a31338a7c81 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt @@ -21,7 +21,6 @@ import android.testing.AndroidTestingRunner import android.util.SparseArray import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.bubbles.storage.BubbleXmlHelperTest.Companion.sparseArraysEqual import junit.framework.Assert.assertEquals import junit.framework.Assert.assertNotNull import junit.framework.Assert.assertTrue @@ -36,7 +35,8 @@ class BubblePersistentRepositoryTest : ShellTestCase() { // user, package, shortcut, notification key, height, res-height, title, taskId, locusId private val user0Bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, null), + BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, null, + true), BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2, null), BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null, @@ -44,7 +44,8 @@ class BubblePersistentRepositoryTest : ShellTestCase() { ) private val user1Bubbles = listOf( - BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, null), + BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, null, + true), BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4, null), BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null, @@ -76,6 +77,6 @@ class BubblePersistentRepositoryTest : ShellTestCase() { assertEquals(actual.size(), 0) repository.persistsToDisk(bubbles) - assertTrue(sparseArraysEqual(bubbles, repository.readFromDisk())) + assertTrue(bubbles.contentEquals(repository.readFromDisk())) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt index 4ab9f87dbbf6..3bfbcd26a577 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt @@ -34,7 +34,8 @@ import java.io.ByteArrayOutputStream class BubbleXmlHelperTest : ShellTestCase() { private val user0Bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1), + BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, + isDismissable = true), BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2, null), BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null, @@ -42,7 +43,8 @@ class BubbleXmlHelperTest : ShellTestCase() { ) private val user1Bubbles = listOf( - BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3), + BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, + isDismissable = true), BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4, null), BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null, @@ -51,28 +53,6 @@ class BubbleXmlHelperTest : ShellTestCase() { private val bubbles = SparseArray<List<BubbleEntity>>() - // Checks that the contents of the two sparse arrays are the same. - companion object { - fun sparseArraysEqual( - one: SparseArray<List<BubbleEntity>>?, - two: SparseArray<List<BubbleEntity>>? - ): Boolean { - if (one == null && two == null) return true - if ((one == null) != (two == null)) return false - if (one!!.size() != two!!.size()) return false - for (i in 0 until one.size()) { - val k1 = one.keyAt(i) - val v1 = one.valueAt(i) - val k2 = two.keyAt(i) - val v2 = two.valueAt(i) - if (k1 != k2 && v1 != v2) { - return false - } - } - return true - } - } - @Before fun setup() { bubbles.put(0, user0Bubbles) @@ -83,14 +63,14 @@ class BubbleXmlHelperTest : ShellTestCase() { fun testWriteXml() { val expectedEntries = """ <bs uid="0"> -<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" /> -<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" /> -<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" d="true" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" d="false" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" d="false" /> </bs> <bs uid="1"> -<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" /> -<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" /> -<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" /> +<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" d="true" /> +<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" d="false" /> +<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" d="false" /> </bs> """.trimIndent() ByteArrayOutputStream().use { @@ -107,19 +87,19 @@ class BubbleXmlHelperTest : ShellTestCase() { <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <bs v="2"> <bs uid="0"> -<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" /> -<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" /> -<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" d="true" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" d="false" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" d="false" /> </bs> <bs uid="1"> -<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" /> -<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" /> -<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" /> +<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" d="true" /> +<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" d="false" /> +<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" d="false" /> </bs> </bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) - assertTrue("failed parsing bubbles from xml\n$src", sparseArraysEqual(bubbles, actual)) + assertTrue("failed parsing bubbles from xml\n$src", bubbles.contentEquals(actual)) } // V0 -> V1 happened prior to release / during dogfood so nothing is saved @@ -161,8 +141,7 @@ class BubbleXmlHelperTest : ShellTestCase() { </bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) - assertTrue("failed parsing bubbles from xml\n$src", - sparseArraysEqual(expectedBubbles, actual)) + assertTrue("failed parsing bubbles from xml\n$src", expectedBubbles.contentEquals(actual)) } /** @@ -187,7 +166,7 @@ class BubbleXmlHelperTest : ShellTestCase() { """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) assertTrue("failed parsing bubbles from xml\n$src", - sparseArraysEqual(expectedBubbles, actual)) + expectedBubbles.contentEquals(actual)) } @Test @@ -210,6 +189,6 @@ class BubbleXmlHelperTest : ShellTestCase() { ) val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) assertTrue("failed parsing bubbles from xml\n$src", - sparseArraysEqual(expectedBubbles, actual)) + expectedBubbles.contentEquals(actual)) } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 2fc0914acbd4..875e6105b7bc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -94,7 +94,10 @@ public class CompatUIControllerTest extends ShellTestCase { private @Mock Lazy<Transitions> mMockTransitionsLazy; private @Mock CompatUIWindowManager mMockCompatLayout; private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout; + private @Mock RestartDialogWindowManager mMockRestartDialogLayout; private @Mock DockStateReader mDockStateReader; + private @Mock CompatUIConfiguration mCompatUIConfiguration; + private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler; @Captor ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor; @@ -112,10 +115,17 @@ public class CompatUIControllerTest extends ShellTestCase { doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId(); doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean()); doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean()); + + doReturn(DISPLAY_ID).when(mMockRestartDialogLayout).getDisplayId(); + doReturn(TASK_ID).when(mMockRestartDialogLayout).getTaskId(); + doReturn(true).when(mMockRestartDialogLayout).createLayout(anyBoolean()); + doReturn(true).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean()); + mShellInit = spy(new ShellInit(mMockExecutor)); mController = new CompatUIController(mContext, mShellInit, mMockShellController, mMockDisplayController, mMockDisplayInsetsController, mMockImeController, - mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader) { + mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader, + mCompatUIConfiguration, mCompatUIShellCommandHandler) { @Override CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { @@ -127,6 +137,12 @@ public class CompatUIControllerTest extends ShellTestCase { TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { return mMockLetterboxEduLayout; } + + @Override + RestartDialogWindowManager createRestartDialogWindowManager(Context context, + TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { + return mMockRestartDialogLayout; + } }; mShellInit.init(); spyOn(mController); @@ -159,6 +175,8 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); // Verify that the compat controls and letterbox education are updated with new size compat // info. @@ -167,10 +185,12 @@ public class CompatUIControllerTest extends ShellTestCase { CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - true); - verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - true); + verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); + verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); + verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); // Verify that compat controls and letterbox education are removed with null task listener. clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); @@ -180,12 +200,14 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout).release(); verify(mMockLetterboxEduLayout).release(); + verify(mMockRestartDialogLayout).release(); } @Test public void testOnCompatInfoChanged_createLayoutReturnsFalse() { doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean()); doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean()); + doReturn(false).when(mMockRestartDialogLayout).createLayout(anyBoolean()); TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); @@ -194,6 +216,8 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); // Verify that the layout is created again. clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); @@ -201,15 +225,19 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); + verify(mMockRestartDialogLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); } @Test public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() { doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean()); doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean()); + doReturn(false).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean()); TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); @@ -218,24 +246,33 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); - clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout, + mController); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - true); - verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - true); + verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); + verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); + verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); // Verify that the layout is created again. - clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout, + mController); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); + verify(mMockRestartDialogLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); } @@ -259,6 +296,7 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, never()).release(); verify(mMockLetterboxEduLayout, never()).release(); + verify(mMockRestartDialogLayout, never()).release(); verify(mMockDisplayInsetsController, never()).removeInsetsChangedListener(eq(DISPLAY_ID), any()); @@ -267,6 +305,7 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockDisplayInsetsController).removeInsetsChangedListener(eq(DISPLAY_ID), any()); verify(mMockCompatLayout).release(); verify(mMockLetterboxEduLayout).release(); + verify(mMockRestartDialogLayout).release(); } @Test @@ -278,11 +317,13 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, never()).updateDisplayLayout(any()); verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(any()); + verify(mMockRestartDialogLayout, never()).updateDisplayLayout(any()); mController.onDisplayConfigurationChanged(DISPLAY_ID, new Configuration()); verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout); verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout); + verify(mMockRestartDialogLayout).updateDisplayLayout(mMockDisplayLayout); } @Test @@ -301,12 +342,14 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout); verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout); + verify(mMockRestartDialogLayout).updateDisplayLayout(mMockDisplayLayout); // No update if the insets state is the same. - clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout); mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState)); verify(mMockCompatLayout, never()).updateDisplayLayout(mMockDisplayLayout); verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(mMockDisplayLayout); + verify(mMockRestartDialogLayout, never()).updateDisplayLayout(mMockDisplayLayout); } @Test @@ -319,22 +362,26 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout).updateVisibility(false); verify(mMockLetterboxEduLayout).updateVisibility(false); + verify(mMockRestartDialogLayout).updateVisibility(false); // Verify button remains hidden while IME is showing. TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - false); - verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - false); + verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); + verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); + verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); // Verify button is shown after IME is hidden. mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false); verify(mMockCompatLayout).updateVisibility(true); verify(mMockLetterboxEduLayout).updateVisibility(true); + verify(mMockRestartDialogLayout).updateVisibility(true); } @Test @@ -347,22 +394,26 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout).updateVisibility(false); verify(mMockLetterboxEduLayout).updateVisibility(false); + verify(mMockRestartDialogLayout).updateVisibility(false); // Verify button remains hidden while keyguard is showing. TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - false); - verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - false); + verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); + verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); + verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); // Verify button is shown after keyguard becomes not showing. mController.onKeyguardVisibilityChanged(false, false, false); verify(mMockCompatLayout).updateVisibility(true); verify(mMockLetterboxEduLayout).updateVisibility(true); + verify(mMockRestartDialogLayout).updateVisibility(true); } @Test @@ -375,20 +426,23 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, times(2)).updateVisibility(false); verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false); + verify(mMockRestartDialogLayout, times(2)).updateVisibility(false); - clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout); // Verify button remains hidden after keyguard becomes not showing since IME is showing. mController.onKeyguardVisibilityChanged(false, false, false); verify(mMockCompatLayout).updateVisibility(false); verify(mMockLetterboxEduLayout).updateVisibility(false); + verify(mMockRestartDialogLayout).updateVisibility(false); // Verify button is shown after IME is not showing. mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false); verify(mMockCompatLayout).updateVisibility(true); verify(mMockLetterboxEduLayout).updateVisibility(true); + verify(mMockRestartDialogLayout).updateVisibility(true); } @Test @@ -401,20 +455,23 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, times(2)).updateVisibility(false); verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false); + verify(mMockRestartDialogLayout, times(2)).updateVisibility(false); - clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout); // Verify button remains hidden after IME is hidden since keyguard is showing. mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false); verify(mMockCompatLayout).updateVisibility(false); verify(mMockLetterboxEduLayout).updateVisibility(false); + verify(mMockRestartDialogLayout).updateVisibility(false); // Verify button is shown after keyguard becomes not showing. mController.onKeyguardVisibilityChanged(false, false, false); verify(mMockCompatLayout).updateVisibility(true); verify(mMockLetterboxEduLayout).updateVisibility(true); + verify(mMockRestartDialogLayout).updateVisibility(true); } private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java index 7d3e718313e6..5f294d53b662 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java @@ -31,6 +31,7 @@ import android.app.ActivityManager; import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; import android.testing.AndroidTestingRunner; +import android.util.Pair; import android.view.LayoutInflater; import android.view.SurfaceControlViewHost; import android.widget.ImageButton; @@ -45,12 +46,17 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; +import junit.framework.Assert; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.function.Consumer; + /** * Tests for {@link CompatUILayout}. * @@ -65,20 +71,22 @@ public class CompatUILayoutTest extends ShellTestCase { @Mock private SyncTransactionQueue mSyncTransactionQueue; @Mock private CompatUIController.CompatUICallback mCallback; + @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @Mock private SurfaceControlViewHost mViewHost; + @Mock private CompatUIConfiguration mCompatUIConfiguration; private CompatUIWindowManager mWindowManager; private CompatUILayout mLayout; + private TaskInfo mTaskInfo; @Before public void setUp() { MockitoAnnotations.initMocks(this); - - mWindowManager = new CompatUIWindowManager(mContext, - createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN), - mSyncTransactionQueue, mCallback, mTaskListener, - new DisplayLayout(), new CompatUIHintsState()); + mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, + mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), + mCompatUIConfiguration, mOnRestartButtonClicked); mLayout = (CompatUILayout) LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null); @@ -95,8 +103,15 @@ public class CompatUILayoutTest extends ShellTestCase { final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button); button.performClick(); + @SuppressWarnings("unchecked") + ArgumentCaptor<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> restartCaptor = + ArgumentCaptor.forClass(Pair.class); + verify(mWindowManager).onRestartButtonClicked(); - verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID); + verify(mOnRestartButtonClicked).accept(restartCaptor.capture()); + final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = restartCaptor.getValue(); + Assert.assertEquals(mTaskInfo, result.first); + Assert.assertEquals(mTaskListener, result.second); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index e79b803b4304..0c5edc3f59de 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -28,6 +28,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -38,6 +39,7 @@ import android.app.ActivityManager; import android.app.TaskInfo; import android.graphics.Rect; import android.testing.AndroidTestingRunner; +import android.util.Pair; import android.view.DisplayInfo; import android.view.InsetsSource; import android.view.InsetsState; @@ -53,12 +55,17 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; +import junit.framework.Assert; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.function.Consumer; + /** * Tests for {@link CompatUIWindowManager}. * @@ -73,20 +80,22 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Mock private SyncTransactionQueue mSyncTransactionQueue; @Mock private CompatUIController.CompatUICallback mCallback; + @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @Mock private CompatUILayout mLayout; @Mock private SurfaceControlViewHost mViewHost; + @Mock private CompatUIConfiguration mCompatUIConfiguration; private CompatUIWindowManager mWindowManager; + private TaskInfo mTaskInfo; @Before public void setUp() { MockitoAnnotations.initMocks(this); - - mWindowManager = new CompatUIWindowManager(mContext, - createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN), - mSyncTransactionQueue, mCallback, mTaskListener, - new DisplayLayout(), new CompatUIHintsState()); + mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, + mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), + mCompatUIConfiguration, mOnRestartButtonClicked); spyOn(mWindowManager); doReturn(mLayout).when(mWindowManager).inflateLayout(); @@ -351,14 +360,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase { mWindowManager.updateVisibility(/* canShow= */ false); verify(mWindowManager, never()).createLayout(anyBoolean()); - verify(mLayout).setVisibility(View.GONE); + verify(mLayout, atLeastOnce()).setVisibility(View.GONE); // Show button. doReturn(View.GONE).when(mLayout).getVisibility(); mWindowManager.updateVisibility(/* canShow= */ true); verify(mWindowManager, never()).createLayout(anyBoolean()); - verify(mLayout).setVisibility(View.VISIBLE); + verify(mLayout, atLeastOnce()).setVisibility(View.VISIBLE); } @Test @@ -404,7 +413,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase { public void testOnRestartButtonClicked() { mWindowManager.onRestartButtonClicked(); - verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID); + @SuppressWarnings("unchecked") + ArgumentCaptor<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> restartCaptor = + ArgumentCaptor.forClass(Pair.class); + + verify(mOnRestartButtonClicked).accept(restartCaptor.capture()); + final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = restartCaptor.getValue(); + Assert.assertEquals(mTaskInfo, result.first); + Assert.assertEquals(mTaskListener, result.second); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java new file mode 100644 index 000000000000..e2dcdb0e91b2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java @@ -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.wm.shell.compatui; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.Consumer; + +/** + * Tests for {@link RestartDialogLayout}. + * + * Build/Install/Run: + * atest WMShellUnitTests:RestartDialogLayoutTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class RestartDialogLayoutTest extends ShellTestCase { + + @Mock private Runnable mDismissCallback; + @Mock private Consumer<Boolean> mRestartCallback; + + private RestartDialogLayout mLayout; + private View mDismissButton; + private View mRestartButton; + private View mDialogContainer; + private CheckBox mDontRepeatCheckBox; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mLayout = (RestartDialogLayout) + LayoutInflater.from(mContext).inflate(R.layout.letterbox_restart_dialog_layout, + null); + mDismissButton = mLayout.findViewById(R.id.letterbox_restart_dialog_dismiss_button); + mRestartButton = mLayout.findViewById(R.id.letterbox_restart_dialog_restart_button); + mDialogContainer = mLayout.findViewById(R.id.letterbox_restart_dialog_container); + mDontRepeatCheckBox = mLayout.findViewById(R.id.letterbox_restart_dialog_checkbox); + mLayout.setDismissOnClickListener(mDismissCallback); + mLayout.setRestartOnClickListener(mRestartCallback); + } + + @Test + public void testOnFinishInflate() { + assertEquals(mLayout.getDialogContainerView(), + mLayout.findViewById(R.id.letterbox_restart_dialog_container)); + assertEquals(mLayout.getDialogTitle(), + mLayout.findViewById(R.id.letterbox_restart_dialog_title)); + assertEquals(mLayout.getBackgroundDimDrawable(), mLayout.getBackground()); + assertEquals(mLayout.getBackground().getAlpha(), 0); + } + + @Test + public void testOnDismissButtonClicked() { + assertTrue(mDismissButton.performClick()); + + verify(mDismissCallback).run(); + } + + @Test + public void testOnRestartButtonClickedWithoutCheckbox() { + mDontRepeatCheckBox.setChecked(false); + assertTrue(mRestartButton.performClick()); + + verify(mRestartCallback).accept(false); + } + + @Test + public void testOnRestartButtonClickedWithCheckbox() { + mDontRepeatCheckBox.setChecked(true); + assertTrue(mRestartButton.performClick()); + + verify(mRestartCallback).accept(true); + } + + @Test + public void testOnBackgroundClickedDoesntDismiss() { + assertFalse(mLayout.performClick()); + + verify(mDismissCallback, never()).run(); + } + + @Test + public void testOnDialogContainerClicked() { + assertTrue(mDialogContainer.performClick()); + + verify(mDismissCallback, never()).run(); + verify(mRestartCallback, never()).accept(anyBoolean()); + } + + @Test + public void testSetDismissOnClickListenerNull() { + mLayout.setDismissOnClickListener(null); + + assertFalse(mDismissButton.performClick()); + assertFalse(mLayout.performClick()); + assertTrue(mDialogContainer.performClick()); + + verify(mDismissCallback, never()).run(); + } + + @Test + public void testSetRestartOnClickListenerNull() { + mLayout.setRestartOnClickListener(null); + + assertFalse(mRestartButton.performClick()); + assertFalse(mLayout.performClick()); + assertTrue(mDialogContainer.performClick()); + + verify(mRestartCallback, never()).accept(anyBoolean()); + } + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt new file mode 100644 index 000000000000..8f84008e8d2d --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.windowdecor + +import android.os.SystemClock +import android.testing.AndroidTestingRunner +import android.view.MotionEvent +import android.view.InputDevice +import androidx.test.filters.SmallTest +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any +import org.mockito.Mockito.argThat +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Tests for [DragDetector]. + * + * Build/Install/Run: + * atest WMShellUnitTests:DragDetectorTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DragDetectorTest { + private val motionEvents = mutableListOf<MotionEvent>() + + @Mock + private lateinit var eventHandler: DragDetector.MotionEventHandler + + private lateinit var dragDetector: DragDetector + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(eventHandler.handleMotionEvent(any())).thenReturn(true) + + dragDetector = DragDetector(eventHandler) + dragDetector.setTouchSlop(SLOP) + } + + @After + fun tearDown() { + motionEvents.forEach { + it.recycle() + } + motionEvents.clear() + } + + @Test + fun testNoMove_passesDownAndUp() { + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + } + + @Test + fun testMoveInSlop_touch_passesDownAndUp() { + `when`(eventHandler.handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN + })).thenReturn(false) + + assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + val newX = X + SLOP - 1 + assertFalse( + dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) + verify(eventHandler, never()).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + } + + @Test + fun testMoveInSlop_mouse_passesDownMoveAndUp() { + `when`(eventHandler.handleMotionEvent(argThat { + it.action == MotionEvent.ACTION_DOWN + })).thenReturn(false) + + assertFalse(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + val newX = X + SLOP - 1 + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + } + + @Test + fun testMoveBeyondSlop_passesDownMoveAndUp() { + `when`(eventHandler.handleMotionEvent(argThat { + it.action == MotionEvent.ACTION_DOWN + })).thenReturn(false) + + assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + val newX = X + SLOP + 1 + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + } + + @Test + fun testPassesHoverEnter() { + `when`(eventHandler.handleMotionEvent(argThat { + it.action == MotionEvent.ACTION_HOVER_ENTER + })).thenReturn(false) + + assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y + }) + } + + @Test + fun testPassesHoverMove() { + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y + }) + } + + @Test + fun testPassesHoverExit() { + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y + }) + } + + private fun createMotionEvent(action: Int, x: Float = X, y: Float = Y, isTouch: Boolean = true): + MotionEvent { + val time = SystemClock.uptimeMillis() + val ev = MotionEvent.obtain(time, time, action, x, y, 0) + ev.source = if (isTouch) InputDevice.SOURCE_TOUCHSCREEN else InputDevice.SOURCE_MOUSE + motionEvents.add(ev) + return ev + } + + companion object { + private const val SLOP = 10 + private const val X = 123f + private const val Y = 234f + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt index ac10ddb0116a..f185a8a80719 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt @@ -1,6 +1,7 @@ package com.android.wm.shell.windowdecor import android.app.ActivityManager +import android.app.WindowConfiguration import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner @@ -10,6 +11,7 @@ import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT +import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED import org.junit.Before import org.junit.Test @@ -63,8 +65,92 @@ class TaskPositionerTest : ShellTestCase() { } @Test + fun testDragResize_notMove_skipsTransactionOnEnd() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + taskPositioner.onDragPositioningEnd( + STARTING_BOUNDS.left.toFloat() + 10, + STARTING_BOUNDS.top.toFloat() + 10 + ) + + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) + } + }) + } + + @Test + fun testDragResize_noEffectiveMove_skipsTransactionOnMoveAndEnd() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + taskPositioner.onDragPositioningMove( + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + taskPositioner.onDragPositioningEnd( + STARTING_BOUNDS.left.toFloat() + 10, + STARTING_BOUNDS.top.toFloat() + 10 + ) + + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) + } + }) + } + + @Test + fun testDragResize_hasEffectiveMove_issuesTransactionOnMoveAndEnd() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + taskPositioner.onDragPositioningMove( + STARTING_BOUNDS.left.toFloat() + 10, + STARTING_BOUNDS.top.toFloat() + ) + val rectAfterMove = Rect(STARTING_BOUNDS) + rectAfterMove.right += 10 + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && + change.configuration.windowConfiguration.bounds == rectAfterMove + } + }) + + taskPositioner.onDragPositioningEnd( + STARTING_BOUNDS.left.toFloat() + 10, + STARTING_BOUNDS.top.toFloat() + 10 + ) + val rectAfterEnd = Rect(rectAfterMove) + rectAfterEnd.top += 10 + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && + change.configuration.windowConfiguration.bounds == rectAfterEnd + } + }) + } + + @Test fun testDragResize_move_skipsDragResizingFlag() { - taskPositioner.onDragResizeStart( + taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // Move STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat() @@ -73,12 +159,12 @@ class TaskPositionerTest : ShellTestCase() { // Move the task 10px to the right. val newX = STARTING_BOUNDS.left.toFloat() + 10 val newY = STARTING_BOUNDS.top.toFloat() - taskPositioner.onDragResizeMove( + taskPositioner.onDragPositioningMove( newX, newY ) - taskPositioner.onDragResizeEnd(newX, newY) + taskPositioner.onDragPositioningEnd(newX, newY) verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> return@argThat wct.changes.any { (token, change) -> @@ -91,7 +177,7 @@ class TaskPositionerTest : ShellTestCase() { @Test fun testDragResize_resize_setsDragResizingFlag() { - taskPositioner.onDragResizeStart( + taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat() @@ -100,12 +186,12 @@ class TaskPositionerTest : ShellTestCase() { // Resize the task by 10px to the right. val newX = STARTING_BOUNDS.right.toFloat() + 10 val newY = STARTING_BOUNDS.top.toFloat() - taskPositioner.onDragResizeMove( + taskPositioner.onDragPositioningMove( newX, newY ) - taskPositioner.onDragResizeEnd(newX, newY) + taskPositioner.onDragPositioningEnd(newX, newY) verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> return@argThat wct.changes.any { (token, change) -> 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 ec4f17fd072b..2f5263cf11d8 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 @@ -107,6 +107,7 @@ public class WindowDecorationTests extends ShellTestCase { private SurfaceControl.Transaction mMockSurfaceControlFinishT; private SurfaceControl.Transaction mMockSurfaceControlAddWindowT; private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams(); + private int mCaptionMenuWidthId; @Before public void setUp() { @@ -116,8 +117,7 @@ public class WindowDecorationTests extends ShellTestCase { mRelayoutParams.mLayoutResId = 0; mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height; - // Caption should have fixed width except in testLayoutResultCalculation_fullWidthCaption() - mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width; + mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width; mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius; doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory) @@ -240,7 +240,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40); - verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64); + verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); @@ -248,7 +248,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlViewHost) .setView(same(mMockView), argThat(lp -> lp.height == 64 - && lp.width == 432 + && lp.width == 300 && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0)); if (ViewRootImpl.CAPTION_ON_SHELL) { verify(mMockView).setTaskFocusState(true); @@ -484,7 +484,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); - mRelayoutParams.mCaptionWidthId = Resources.ID_NULL; windowDecor.relayout(taskInfo); verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); @@ -558,7 +557,7 @@ public class WindowDecorationTests extends ShellTestCase { final Resources resources = mDecorWindowContext.getResources(); int x = mRelayoutParams.mCaptionX; int y = mRelayoutParams.mCaptionY; - int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId); + int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId); int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId); String name = "Test Window"; WindowDecoration.AdditionalWindow additionalWindow = diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 798688ea7b46..2c139b72eab3 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1337,12 +1337,8 @@ public class AudioManager { public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags) { Preconditions.checkNotNull(attr, "attr must not be null"); final IAudioService service = getService(); - try { - service.setVolumeIndexForAttributes(attr, index, flags, - getContext().getOpPackageName(), getContext().getAttributionTag()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + int groupId = getVolumeGroupIdForAttributes(attr); + setVolumeGroupVolumeIndex(groupId, index, flags); } /** @@ -1361,11 +1357,8 @@ public class AudioManager { public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) { Preconditions.checkNotNull(attr, "attr must not be null"); final IAudioService service = getService(); - try { - return service.getVolumeIndexForAttributes(attr); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + int groupId = getVolumeGroupIdForAttributes(attr); + return getVolumeGroupVolumeIndex(groupId); } /** @@ -1382,11 +1375,8 @@ public class AudioManager { public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) { Preconditions.checkNotNull(attr, "attr must not be null"); final IAudioService service = getService(); - try { - return service.getMaxVolumeIndexForAttributes(attr); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + int groupId = getVolumeGroupIdForAttributes(attr); + return getVolumeGroupMaxVolumeIndex(groupId); } /** @@ -1403,8 +1393,168 @@ public class AudioManager { public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) { Preconditions.checkNotNull(attr, "attr must not be null"); final IAudioService service = getService(); + int groupId = getVolumeGroupIdForAttributes(attr); + return getVolumeGroupMinVolumeIndex(groupId); + } + + /** + * Returns the volume group id associated to the given {@link AudioAttributes}. + * + * @param attributes The {@link AudioAttributes} to consider. + * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given + * {@link AudioAttributes} if found, + * {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise. + * @hide + */ + public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) { + Preconditions.checkNotNull(attributes, "Audio Attributes must not be null"); + return AudioProductStrategy.getVolumeGroupIdForAudioAttributes(attributes, + /* fallbackOnDefault= */ false); + } + + /** + * Sets the volume index for a particular group associated to given id. + * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @param index The volume index to set. See + * {@link #getVolumeGroupMaxVolumeIndex(id)} for the largest valid value + * {@link #getVolumeGroupMinVolumeIndex(id)} for the lowest valid value. + * @param flags One or more flags. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setVolumeGroupVolumeIndex(int groupId, int index, int flags) { + final IAudioService service = getService(); try { - return service.getMinVolumeIndexForAttributes(attr); + service.setVolumeGroupVolumeIndex(groupId, index, flags, + getContext().getOpPackageName(), getContext().getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the current volume index for a particular group associated to given id. + * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @return The current volume index for the stream. + * @hide + */ + @IntRange(from = 0) + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public int getVolumeGroupVolumeIndex(int groupId) { + final IAudioService service = getService(); + try { + return service.getVolumeGroupVolumeIndex(groupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the maximum volume index for a particular group associated to given id. + * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @return The maximum valid volume index for the {@link AudioAttributes}. + * @hide + */ + @IntRange(from = 0) + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public int getVolumeGroupMaxVolumeIndex(int groupId) { + final IAudioService service = getService(); + try { + return service.getVolumeGroupMaxVolumeIndex(groupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the minimum volume index for a particular group associated to given id. + * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @return The minimum valid volume index for the {@link AudioAttributes}. + * @hide + */ + @IntRange(from = 0) + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public int getVolumeGroupMinVolumeIndex(int groupId) { + final IAudioService service = getService(); + try { + return service.getVolumeGroupMinVolumeIndex(groupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adjusts the volume of a particular group associated to given id by one step in a direction. + * <p> If the volume group is associated to a stream type, it fallbacks on + * {@link AudioManager#adjustStreamVolume()} for compatibility reason. + * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @param direction The direction to adjust the volume. One of + * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or + * {@link #ADJUST_SAME}. + * @param flags One or more flags. + * @throws SecurityException if the adjustment triggers a Do Not Disturb change and the caller + * is not granted notification policy access. + * @hide + */ + public void adjustVolumeGroupVolume(int groupId, int direction, int flags) { + IAudioService service = getService(); + try { + service.adjustVolumeGroupVolume(groupId, direction, flags, + getContext().getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get last audible volume of the group associated to given id before it was muted. + * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @return current volume if not muted, volume before muted otherwise. + * @hide + */ + @RequiresPermission("android.permission.QUERY_AUDIO_STATE") + @IntRange(from = 0) + public int getLastAudibleVolumeGroupVolume(int groupId) { + IAudioService service = getService(); + try { + return service.getLastAudibleVolumeGroupVolume(groupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the current mute state for a particular volume group associated to the given id. + * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id. + * @see #adjustAttributesVolume(AudioAttributes, int, int) + * @hide + */ + public boolean isVolumeGroupMuted(int groupId) { + IAudioService service = getService(); + try { + return service.isVolumeGroupMuted(groupId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -7687,7 +7837,8 @@ public class AudioManager { Objects.requireNonNull(device); try { if (device.getId() == 0) { - throw new IllegalArgumentException("In valid device: " + device); + Log.w(TAG, "setCommunicationDevice: device not found: " + device); + return false; } return getService().setCommunicationDevice(mICallBack, device.getId()); } catch (RemoteException e) { diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index ad933e02c0d5..88a0321d34da 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -126,14 +126,20 @@ interface IAudioService { List<AudioVolumeGroup> getAudioVolumeGroups(); - void setVolumeIndexForAttributes(in AudioAttributes aa, int index, int flags, - String callingPackage, in String attributionTag); + void setVolumeGroupVolumeIndex(int groupId, int index, int flags, String callingPackage, + in String attributionTag); + + int getVolumeGroupVolumeIndex(int groupId); + + int getVolumeGroupMaxVolumeIndex(int groupId); + + int getVolumeGroupMinVolumeIndex(int groupId); - int getVolumeIndexForAttributes(in AudioAttributes aa); + int getLastAudibleVolumeGroupVolume(int groupId); - int getMaxVolumeIndexForAttributes(in AudioAttributes aa); + boolean isVolumeGroupMuted(int groupId); - int getMinVolumeIndexForAttributes(in AudioAttributes aa); + void adjustVolumeGroupVolume(int groupId, int direction, int flags, String callingPackage); int getLastAudibleStreamVolume(int streamType); diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java index 31d596765bcc..a792501a07ba 100644 --- a/media/java/android/media/audiopolicy/AudioProductStrategy.java +++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java @@ -29,10 +29,10 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * @hide @@ -142,7 +142,7 @@ public final class AudioProductStrategy implements Parcelable { */ public static int getLegacyStreamTypeForStrategyWithAudioAttributes( @NonNull AudioAttributes audioAttributes) { - Preconditions.checkNotNull(audioAttributes, "AudioAttributes must not be null"); + Objects.requireNonNull(audioAttributes, "AudioAttributes must not be null"); for (final AudioProductStrategy productStrategy : AudioProductStrategy.getAudioProductStrategies()) { if (productStrategy.supportsAudioAttributes(audioAttributes)) { @@ -162,6 +162,30 @@ public final class AudioProductStrategy implements Parcelable { return AudioSystem.STREAM_MUSIC; } + /** + * @hide + * @param attributes the {@link AudioAttributes} to identify VolumeGroupId with + * @param fallbackOnDefault if set, allows to fallback on the default group (e.g. the group + * associated to {@link AudioManager#STREAM_MUSIC}). + * @return volume group id associated with the given {@link AudioAttributes} if found, + * default volume group id if fallbackOnDefault is set + * <p>By convention, the product strategy with default attributes will be associated to the + * default volume group (e.g. associated to {@link AudioManager#STREAM_MUSIC}) + * or {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} if not found. + */ + public static int getVolumeGroupIdForAudioAttributes( + @NonNull AudioAttributes attributes, boolean fallbackOnDefault) { + Objects.requireNonNull(attributes, "attributes must not be null"); + int volumeGroupId = getVolumeGroupIdForAudioAttributesInt(attributes); + if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { + return volumeGroupId; + } + if (fallbackOnDefault) { + return getVolumeGroupIdForAudioAttributesInt(getDefaultAttributes()); + } + return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; + } + private static List<AudioProductStrategy> initializeAudioProductStrategies() { ArrayList<AudioProductStrategy> apsList = new ArrayList<AudioProductStrategy>(); int status = native_list_audio_product_strategies(apsList); @@ -192,8 +216,8 @@ public final class AudioProductStrategy implements Parcelable { */ private AudioProductStrategy(@NonNull String name, int id, @NonNull AudioAttributesGroup[] aag) { - Preconditions.checkNotNull(name, "name must not be null"); - Preconditions.checkNotNull(aag, "AudioAttributesGroups must not be null"); + Objects.requireNonNull(name, "name must not be null"); + Objects.requireNonNull(aag, "AudioAttributesGroups must not be null"); mName = name; mId = id; mAudioAttributesGroups = aag; @@ -211,6 +235,15 @@ public final class AudioProductStrategy implements Parcelable { /** * @hide + * @return the product strategy ID (which is the generalisation of Car Audio Usage / legacy + * routing_strategy linked to {@link AudioAttributes#getUsage()}). + */ + @NonNull public String getName() { + return mName; + } + + /** + * @hide * @return first {@link AudioAttributes} associated to this product strategy. */ @SystemApi @@ -243,7 +276,7 @@ public final class AudioProductStrategy implements Parcelable { */ @TestApi public int getLegacyStreamTypeForAudioAttributes(@NonNull AudioAttributes aa) { - Preconditions.checkNotNull(aa, "AudioAttributes must not be null"); + Objects.requireNonNull(aa, "AudioAttributes must not be null"); for (final AudioAttributesGroup aag : mAudioAttributesGroups) { if (aag.supportsAttributes(aa)) { return aag.getStreamType(); @@ -260,7 +293,7 @@ public final class AudioProductStrategy implements Parcelable { */ @SystemApi public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) { - Preconditions.checkNotNull(aa, "AudioAttributes must not be null"); + Objects.requireNonNull(aa, "AudioAttributes must not be null"); for (final AudioAttributesGroup aag : mAudioAttributesGroups) { if (aag.supportsAttributes(aa)) { return true; @@ -293,7 +326,7 @@ public final class AudioProductStrategy implements Parcelable { */ @TestApi public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) { - Preconditions.checkNotNull(aa, "AudioAttributes must not be null"); + Objects.requireNonNull(aa, "AudioAttributes must not be null"); for (final AudioAttributesGroup aag : mAudioAttributesGroups) { if (aag.supportsAttributes(aa)) { return aag.getVolumeGroupId(); @@ -302,6 +335,17 @@ public final class AudioProductStrategy implements Parcelable { return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; } + private static int getVolumeGroupIdForAudioAttributesInt(@NonNull AudioAttributes attributes) { + Objects.requireNonNull(attributes, "attributes must not be null"); + for (AudioProductStrategy productStrategy : getAudioProductStrategies()) { + int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes); + if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { + return volumeGroupId; + } + } + return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; + } + @Override public int describeContents() { return 0; @@ -377,8 +421,8 @@ public final class AudioProductStrategy implements Parcelable { */ private static boolean attributesMatches(@NonNull AudioAttributes refAttr, @NonNull AudioAttributes attr) { - Preconditions.checkNotNull(refAttr, "refAttr must not be null"); - Preconditions.checkNotNull(attr, "attr must not be null"); + Objects.requireNonNull(refAttr, "reference AudioAttributes must not be null"); + Objects.requireNonNull(attr, "requester's AudioAttributes must not be null"); String refFormattedTags = TextUtils.join(";", refAttr.getTags()); String cliFormattedTags = TextUtils.join(";", attr.getTags()); if (refAttr.equals(DEFAULT_ATTRIBUTES)) { diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 2f4dd8fad8bb..408883e4e9e2 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -1004,9 +1004,10 @@ DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) { static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) { sp<IDrm> drm = android::DrmUtils::MakeDrm(); + if (drm == NULL) return env->NewByteArray(0); + std::vector<uint8_t> bv; drm->getSupportedSchemes(bv); - jbyteArray jUuidBytes = env->NewByteArray(bv.size()); env->SetByteArrayRegion(jUuidBytes, 0, bv.size(), reinterpret_cast<const jbyte *>(bv.data())); return jUuidBytes; diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml index 3ab18dbcad8f..042488292da7 100644 --- a/packages/CompanionDeviceManager/res/values-nl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml @@ -18,7 +18,7 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> <string name="confirmation_title" msgid="3785000297483688997">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> toegang geven tot je <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> - <string name="profile_name_watch" msgid="576290739483672360">"horloge"</string> + <string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string> <string name="chooser_title" msgid="2262294130493605839">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om te beheren met <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="summary_watch" msgid="3002344206574997652">"Deze app is vereist om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> kan interactie hebben met je meldingen en toegang krijgen tot rechten voor Telefoon, Sms, Contacten, Agenda, Gesprekslijsten en Apparaten in de buurt."</string> <string name="permission_apps" msgid="6142133265286656158">"Apps"</string> @@ -41,8 +41,8 @@ <string name="consent_yes" msgid="8344487259618762872">"Toestaan"</string> <string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string> <string name="consent_back" msgid="2560683030046918882">"Terug"</string> - <string name="permission_sync_confirmation_title" msgid="667074294393493186">"App-rechten overzetten naar je horloge"</string> - <string name="permission_sync_summary" msgid="8873391306499120778">"We willen het makkelijker voor je maken om je horloge in te stellen. Daarom gebruiken apps die tijdens het instellen worden geïnstalleerd op je horloge, dezelfde rechten als op je telefoon.\n\n Deze rechten kunnen toegang tot de microfoon en locatie van je horloge omvatten."</string> + <string name="permission_sync_confirmation_title" msgid="667074294393493186">"App-rechten overzetten naar je smartwatch"</string> + <string name="permission_sync_summary" msgid="8873391306499120778">"We willen het makkelijker voor je maken om je smartwatch in te stellen. Daarom gebruiken apps die tijdens het instellen worden geïnstalleerd op je smartwatch, dezelfde rechten als op je telefoon.\n\n Deze rechten kunnen toegang tot de microfoon en locatie van je smartwatch omvatten."</string> <string name="vendor_icon_description" msgid="4445875290032225965">"App-icoon"</string> <string name="vendor_header_button_description" msgid="6566660389500630608">"Knop Meer informatie"</string> </resources> diff --git a/packages/PackageInstaller/res/values-pt-rBR/strings.xml b/packages/PackageInstaller/res/values-pt-rBR/strings.xml index b9e5be871a7a..43dd3311a65c 100644 --- a/packages/PackageInstaller/res/values-pt-rBR/strings.xml +++ b/packages/PackageInstaller/res/values-pt-rBR/strings.xml @@ -53,7 +53,7 @@ <string name="uninstall_application_title" msgid="4045420072401428123">"Desinstalar app"</string> <string name="uninstall_update_title" msgid="824411791011583031">"Desinstalar atualização"</string> <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> é parte do seguinte app:"</string> - <string name="uninstall_application_text" msgid="3816830743706143980">"Quer desinstalar este app?"</string> + <string name="uninstall_application_text" msgid="3816830743706143980">"Você quer desinstalar este app?"</string> <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Quer desinstalar este app para "<b>"todos"</b>" os usuários? O aplicativo e os dados dele serão removidos para "<b>"todos"</b>" os usuários do dispositivo."</string> <string name="uninstall_application_text_user" msgid="498072714173920526">"Quer desinstalar este app para o usuário <xliff:g id="USERNAME">%1$s</xliff:g>?"</string> <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Você quer desinstalar esse app do seu perfil de trabalho?"</string> diff --git a/packages/PackageInstaller/res/values-pt/strings.xml b/packages/PackageInstaller/res/values-pt/strings.xml index b9e5be871a7a..43dd3311a65c 100644 --- a/packages/PackageInstaller/res/values-pt/strings.xml +++ b/packages/PackageInstaller/res/values-pt/strings.xml @@ -53,7 +53,7 @@ <string name="uninstall_application_title" msgid="4045420072401428123">"Desinstalar app"</string> <string name="uninstall_update_title" msgid="824411791011583031">"Desinstalar atualização"</string> <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> é parte do seguinte app:"</string> - <string name="uninstall_application_text" msgid="3816830743706143980">"Quer desinstalar este app?"</string> + <string name="uninstall_application_text" msgid="3816830743706143980">"Você quer desinstalar este app?"</string> <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Quer desinstalar este app para "<b>"todos"</b>" os usuários? O aplicativo e os dados dele serão removidos para "<b>"todos"</b>" os usuários do dispositivo."</string> <string name="uninstall_application_text_user" msgid="498072714173920526">"Quer desinstalar este app para o usuário <xliff:g id="USERNAME">%1$s</xliff:g>?"</string> <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Você quer desinstalar esse app do seu perfil de trabalho?"</string> diff --git a/packages/PrintSpooler/res/values-or/strings.xml b/packages/PrintSpooler/res/values-or/strings.xml index 6f215d3af18b..dd29700f812b 100644 --- a/packages/PrintSpooler/res/values-or/strings.xml +++ b/packages/PrintSpooler/res/values-or/strings.xml @@ -47,7 +47,7 @@ <string name="savetopdf_button" msgid="2976186791686924743">"PDFରେ ସେଭ୍ କରନ୍ତୁ"</string> <string name="print_options_expanded" msgid="6944679157471691859">"ପ୍ରିଣ୍ଟ ବିକଳ୍ପକୁ ବଡ଼ କରାଯାଇଛି"</string> <string name="print_options_collapsed" msgid="7455930445670414332">"ପ୍ରିଣ୍ଟ ବିକଳ୍ପକୁ ଛୋଟ କରାଯାଇଛି"</string> - <string name="search" msgid="5421724265322228497">"ସନ୍ଧାନ କରନ୍ତୁ"</string> + <string name="search" msgid="5421724265322228497">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string> <string name="all_printers_label" msgid="3178848870161526399">"ସମସ୍ତ ପ୍ରିଣ୍ଟର୍"</string> <string name="add_print_service_label" msgid="5356702546188981940">"ସେବା ଯୋଗ କରନ୍ତୁ"</string> <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ସର୍ଚ୍ଚ ବକ୍ସ ଦେଖାଯାଇଛି"</string> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index 7377c3e9a49e..44c3d413dce9 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -319,8 +319,8 @@ <string name="select_logd_size_dialog_title" msgid="2105401994681013578">"প্ৰতিটো লগ বাফাৰত ল\'গাৰৰ আকাৰ বাছনি কৰক"</string> <string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"লগাৰৰ স্থায়ী ষ্ট’ৰেজৰ বস্তুবোৰ মচিবনে?"</string> <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"পাৰ্ছিছটেণ্ট লগাৰ ব্যৱহাৰ কৰ নিৰীক্ষণ নকৰাৰ সময়ত, আমি আপোনাৰ ডিভাইচত থকা লগাৰ ডেটা নিৱাসীক মচা দৰকাৰ।"</string> - <string name="select_logpersist_title" msgid="447071974007104196">"ডিভাইচটোত লগাৰৰ ডেটা নিৰবচ্ছিন্নভাৱে সঞ্চয় কৰক"</string> - <string name="select_logpersist_dialog_title" msgid="7745193591195485594">"ডিভাইচত স্থায়ীভাৱে সঞ্চয় কৰিবলৈ লগ বাফাৰবোৰ বাছনি কৰক"</string> + <string name="select_logpersist_title" msgid="447071974007104196">"ডিভাইচটোত লগাৰৰ ডেটা নিৰবচ্ছিন্নভাৱে ষ্ট’ৰ কৰক"</string> + <string name="select_logpersist_dialog_title" msgid="7745193591195485594">"ডিভাইচত স্থায়ীভাৱে ষ্ট’ৰ কৰিবলৈ লগ বাফাৰবোৰ বাছনি কৰক"</string> <string name="select_usb_configuration_title" msgid="6339801314922294586">"ইউএছবি কনফিগাৰেশ্বন বাছনি কৰক"</string> <string name="select_usb_configuration_dialog_title" msgid="3579567144722589237">"ইউএছবি কনফিগাৰেশ্বন বাছনি কৰক"</string> <string name="allow_mock_location" msgid="2102650981552527884">"নকল অৱস্থানৰ অনুমতি দিয়ক"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 518161a8a7d7..94509279b3ce 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -183,7 +183,7 @@ <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> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index a904fb2ac421..05da8008167d 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -183,7 +183,7 @@ <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> @@ -229,9 +229,9 @@ <string name="development_settings_enable" msgid="4285094651288242183">"Иштеп чыгуучунун параметрлерин иштетүү"</string> <string name="development_settings_summary" msgid="8718917813868735095">"Колдонмо өндүрүү мүмкүнчүлүктөрүн орнотуу"</string> <string name="development_settings_not_available" msgid="355070198089140951">"Бул колдонуучуга өнүктүүрүүчү мүмкүнчүлүктөрү берилген эмес."</string> - <string name="vpn_settings_not_available" msgid="2894137119965668920">"Бул колдонуучу VPN жөндөөлөрүн колдоно албайт"</string> - <string name="tethering_settings_not_available" msgid="266821736434699780">"Бул колдонуучу модем режиминин жөндөөлөрүн өзгөртө албайт"</string> - <string name="apn_settings_not_available" msgid="1147111671403342300">"Бул колдонуучу мүмкүндүк алуу түйүнүнүн аталышынын жөндөөлөрүн колдоно албайт"</string> + <string name="vpn_settings_not_available" msgid="2894137119965668920">"Бул колдонуучу VPN параметрлерин колдоно албайт"</string> + <string name="tethering_settings_not_available" msgid="266821736434699780">"Бул колдонуучу модем режиминин параметрлерин өзгөртө албайт"</string> + <string name="apn_settings_not_available" msgid="1147111671403342300">"Бул колдонуучу мүмкүндүк алуу түйүнүнүн аталышынын параметрлерин колдоно албайт"</string> <string name="enable_adb" msgid="8072776357237289039">"USB аркылуу мүчүлүштүктөрдү аныктоо"</string> <string name="enable_adb_summary" msgid="3711526030096574316">"USB компьютерге сайылганда мүчүлүштүктөрдү оңдоо режими иштейт"</string> <string name="clear_adb_keys" msgid="3010148733140369917">"USB аркылуу мүчүлүштүктөрдү аныктоо уруксатын артка кайтаруу"</string> @@ -332,7 +332,7 @@ <string name="adb_warning_message" msgid="8145270656419669221">"USB-жөндөө - өндүрүү максатында гана түзүлгөн. Аны компүтериңиз менен түзмөгүңүздүн ортосунда берилиштерди алмашуу, түзмөгүңүзгө колдонмолорду эскертүүсүз орнотуу жана лог берилиштерин окуу үчүн колдонсоңуз болот."</string> <string name="adbwifi_warning_title" msgid="727104571653031865">"Мүчүлүштүктөрдү Wi-Fi аркылуу оңдоого уруксат бересизби?"</string> <string name="adbwifi_warning_message" msgid="8005936574322702388">"Мүчүлүштүктөрдү Wi-Fi аркылуу аныктоо – өндүрүү максатында гана түзүлгөн. Аны компьютериңиз менен түзмөгүңүздүн ортосунда маалыматты алмашуу, колдонмолорду түзмөгүңүзгө эскертүүсүз орнотуу жана маалыматтар таржымалын окуу үчүн колдонсоңуз болот."</string> - <string name="adb_keys_warning_message" msgid="2968555274488101220">"Сиз мурун USB жөндөөлөрүнө уруксат берген бардык компүтерлердин жеткиси жокко чыгарылсынбы?"</string> + <string name="adb_keys_warning_message" msgid="2968555274488101220">"Сиз мурун USB параметрлерине уруксат берген бардык компүтерлердин жеткиси жокко чыгарылсынбы?"</string> <string name="dev_settings_warning_title" msgid="8251234890169074553">"Параметрлерди өзгөртүү"</string> <string name="dev_settings_warning_message" msgid="37741686486073668">"Бул орнотуулар өндүрүүчүлөр үчүн гана берилген. Булар түзмөгүңүздүн колдонмолорун бузулушуна же туура эмес иштешине алып келиши мүмкүн."</string> <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Орнотулуучу колдонмону текшерүү"</string> @@ -430,7 +430,7 @@ <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Иштеген жок. Күйгүзүү үчүн басып коюңуз."</string> <string name="inactive_app_active_summary" msgid="8047630990208722344">"Иштеп турат. Өчүрүү үчүн басып коюңуз."</string> <string name="standby_bucket_summary" msgid="5128193447550429600">"Көшүү режиминдеги колдонмонун абалы:<xliff:g id="BUCKET"> %s</xliff:g>"</string> - <string name="transcode_settings_title" msgid="2581975870429850549">"Медиа файлдарды транскоддоо жөндөөлөрү"</string> + <string name="transcode_settings_title" msgid="2581975870429850549">"Медиа файлдарды транскоддоо параметрлери"</string> <string name="transcode_user_control" msgid="6176368544817731314">"Демейки жүргүзүлгөн транскоддоону өзгөртүп коюу"</string> <string name="transcode_enable_all" msgid="2411165920039166710">"Транскоддоо жүргүзүүнү иштетүү"</string> <string name="transcode_default" msgid="3784803084573509491">"Колдонмолордо заманбап форматтар колдоого алынат"</string> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 91ab0242d73e..c75c40fa4ca9 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -183,7 +183,7 @@ <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> diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index d6586db1b50a..d9d7cc9085fa 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -185,6 +185,24 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { public abstract String getId(); /** + * Get disabled reason of device + * + * @return disabled reason of device + */ + public int getDisableReason() { + return -1; + } + + /** + * Checks if device is has disabled reason + * + * @return true if device has disabled reason + */ + public boolean hasDisabledReason() { + return false; + } + + /** * Checks if device is suggested device from application * * @return true if device is suggested device diff --git a/packages/SettingsProvider/res/values-ky/strings.xml b/packages/SettingsProvider/res/values-ky/strings.xml index 8058b4da5634..7ab6582cebcb 100644 --- a/packages/SettingsProvider/res/values-ky/strings.xml +++ b/packages/SettingsProvider/res/values-ky/strings.xml @@ -20,6 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4567566098528588863">"Жөндөөлөрдү сактоо"</string> - <string name="wifi_softap_config_change" msgid="5688373762357941645">"Байланыш түйүнү жөндөөлөрү өзгөрдү"</string> + <string name="wifi_softap_config_change" msgid="5688373762357941645">"Байланыш түйүнү параметрлери өзгөрдү"</string> <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Чоо-жайын билүү үчүн басыңыз"</string> </resources> diff --git a/packages/Shell/res/values-mk/strings.xml b/packages/Shell/res/values-mk/strings.xml index 085619857e14..93d4d525a8bf 100644 --- a/packages/Shell/res/values-mk/strings.xml +++ b/packages/Shell/res/values-mk/strings.xml @@ -37,7 +37,7 @@ <string name="bugreport_info_action" msgid="2158204228510576227">"Детали"</string> <string name="bugreport_screenshot_action" msgid="8677781721940614995">"Слика од екранот"</string> <string name="bugreport_screenshot_taken" msgid="5684211273096253120">"Успешно е направена слика од екранот."</string> - <string name="bugreport_screenshot_failed" msgid="5853049140806834601">"Не може да се направи слика од екранот."</string> + <string name="bugreport_screenshot_failed" msgid="5853049140806834601">"Не може да се зачува слика од екранот."</string> <string name="bugreport_info_dialog_title" msgid="1355948594292983332">"Детали за извештајот за грешки <xliff:g id="ID">#%d</xliff:g>"</string> <string name="bugreport_info_name" msgid="4414036021935139527">"Име на датотека"</string> <string name="bugreport_info_title" msgid="2306030793918239804">"Наслов на грешката"</string> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index b6e006f3948a..e96aead597b3 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -674,6 +674,7 @@ <intent-filter> <action android:name="android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG" /> <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="tel" /> </intent-filter> </activity> diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index c729b09628af..17a94b8639d0 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -41,6 +41,7 @@ import androidx.annotation.BinderThread import androidx.annotation.UiThread import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils +import java.lang.IllegalArgumentException import kotlin.math.roundToInt private const val TAG = "ActivityLaunchAnimator" @@ -338,13 +339,24 @@ class ActivityLaunchAnimator( * Return a [Controller] that will animate and expand [view] into the opening window. * * Important: The view must be attached to a [ViewGroup] when calling this function and - * during the animation. For safety, this method will return null when it is not. + * during the animation. For safety, this method will return null when it is not. The + * view must also implement [LaunchableView], otherwise this method will throw. * * Note: The background of [view] should be a (rounded) rectangle so that it can be * properly animated. */ @JvmStatic fun fromView(view: View, cujType: Int? = null): Controller? { + // Make sure the View we launch from implements LaunchableView to avoid visibility + // issues. + if (view !is LaunchableView) { + throw IllegalArgumentException( + "An ActivityLaunchAnimator.Controller was created from a View that does " + + "not implement LaunchableView. This can lead to subtle bugs where the" + + " visibility of the View we are launching from is not what we expected." + ) + } + if (view.parent !is ViewGroup) { Log.e( TAG, diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt new file mode 100644 index 000000000000..1c9dabbb0e07 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt @@ -0,0 +1,6 @@ +package com.android.systemui.animation + +interface AnimationFeatureFlags { + val isPredictiveBackQsDialogAnim: Boolean + get() = false +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index a3ed0856c60a..b8d78fb208f4 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -33,8 +33,14 @@ import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS import android.widget.FrameLayout +import android.window.OnBackInvokedDispatcher import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CujType +import com.android.systemui.animation.back.BackAnimationSpec +import com.android.systemui.animation.back.applyTo +import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi +import com.android.systemui.animation.back.onBackAnimationCallbackFrom +import java.lang.IllegalArgumentException import kotlin.math.roundToInt private const val TAG = "DialogLaunchAnimator" @@ -55,8 +61,9 @@ class DialogLaunchAnimator constructor( private val callback: Callback, private val interactionJankMonitor: InteractionJankMonitor, + private val featureFlags: AnimationFeatureFlags, private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS), - private val isForTesting: Boolean = false + private val isForTesting: Boolean = false, ) { private companion object { private val TIMINGS = ActivityLaunchAnimator.TIMINGS @@ -151,12 +158,23 @@ constructor( * Create a [Controller] that can animate [source] to and from a dialog. * * Important: The view must be attached to a [ViewGroup] when calling this function and - * during the animation. For safety, this method will return null when it is not. + * during the animation. For safety, this method will return null when it is not. The + * view must also implement [LaunchableView], otherwise this method will throw. * * Note: The background of [view] should be a (rounded) rectangle so that it can be * properly animated. */ fun fromView(source: View, cuj: DialogCuj? = null): Controller? { + // Make sure the View we launch from implements LaunchableView to avoid visibility + // issues. + if (source !is LaunchableView) { + throw IllegalArgumentException( + "A DialogLaunchAnimator.Controller was created from a View that does not " + + "implement LaunchableView. This can lead to subtle bugs where the " + + "visibility of the View we are launching from is not what we expected." + ) + } + if (source.parent !is ViewGroup) { Log.e( TAG, @@ -243,23 +261,6 @@ constructor( } ?: controller - if ( - animatedParent == null && - controller is ViewDialogLaunchAnimatorController && - controller.source !is LaunchableView - ) { - // Make sure the View we launch from implements LaunchableView to avoid visibility - // issues. Given that we don't own dialog decorViews so we can't enforce it for launches - // from a dialog. - // TODO(b/243636422): Throw instead of logging to enforce this. - Log.w( - TAG, - "A dialog was launched from a View that does not implement LaunchableView. This " + - "can lead to subtle bugs where the visibility of the View we are " + - "launching from is not what we expected." - ) - } - // Make sure we don't run the launch animation from the same source twice at the same time. if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) { Log.e( @@ -273,15 +274,16 @@ constructor( val animatedDialog = AnimatedDialog( - launchAnimator, - callback, - interactionJankMonitor, - controller, + launchAnimator = launchAnimator, + callback = callback, + interactionJankMonitor = interactionJankMonitor, + controller = controller, onDialogDismissed = { openedDialogs.remove(it) }, dialog = dialog, - animateBackgroundBoundsChange, - animatedParent, - isForTesting, + animateBackgroundBoundsChange = animateBackgroundBoundsChange, + parentAnimatedDialog = animatedParent, + forceDisableSynchronization = isForTesting, + featureFlags = featureFlags, ) openedDialogs.add(animatedDialog) @@ -517,6 +519,7 @@ private class AnimatedDialog( * Whether synchronization should be disabled, which can be useful if we are running in a test. */ private val forceDisableSynchronization: Boolean, + private val featureFlags: AnimationFeatureFlags, ) { /** * The DecorView of this dialog window. @@ -605,10 +608,16 @@ private class AnimatedDialog( } // Animate that view with the background. Throw if we didn't find one, because - // otherwise - // it's not clear what we should animate. + // otherwise it's not clear what we should animate. + if (viewGroupWithBackground == null) { + error("Unable to find ViewGroup with background") + } + + if (viewGroupWithBackground !is LaunchableView) { + error("The animated ViewGroup with background must implement LaunchableView") + } + viewGroupWithBackground - ?: throw IllegalStateException("Unable to find ViewGroup with background") } else { // We will make the dialog window (and therefore its DecorView) fullscreen to make // it possible to animate outside its bounds. @@ -631,7 +640,7 @@ private class AnimatedDialog( FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) ) - val dialogContentWithBackground = FrameLayout(dialog.context) + val dialogContentWithBackground = LaunchableFrameLayout(dialog.context) dialogContentWithBackground.background = decorView.background // Make the window background transparent. Note that setting the window (or @@ -712,7 +721,10 @@ private class AnimatedDialog( // Make the background view invisible until we start the animation. We use the transition // visibility like GhostView does so that we don't mess up with the accessibility tree (see - // b/204944038#comment17). + // b/204944038#comment17). Given that this background implements LaunchableView, we call + // setShouldBlockVisibilityChanges() early so that the current visibility (VISIBLE) is + // restored at the end of the animation. + dialogContentWithBackground.setShouldBlockVisibilityChanges(true) dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE) // Make sure the dialog is visible instantly and does not do any window animation. @@ -778,12 +790,45 @@ private class AnimatedDialog( // the dialog. dialog.setDismissOverride(this::onDialogDismissed) + if (featureFlags.isPredictiveBackQsDialogAnim) { + // TODO(b/265923095) Improve animations for QS dialogs on configuration change + registerOnBackInvokedCallback(targetView = dialogContentWithBackground) + } + // Show the dialog. dialog.show() - moveSourceDrawingToDialog() } + private fun registerOnBackInvokedCallback(targetView: View) { + val metrics = targetView.resources.displayMetrics + + val onBackAnimationCallback = + onBackAnimationCallbackFrom( + backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(metrics), + displayMetrics = metrics, // TODO(b/265060720): We could remove this + onBackProgressed = { backTransformation -> backTransformation.applyTo(targetView) }, + onBackInvoked = { dialog.dismiss() }, + ) + + val dispatcher = dialog.onBackInvokedDispatcher + targetView.addOnAttachStateChangeListener( + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + dispatcher.registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + onBackAnimationCallback + ) + } + + override fun onViewDetachedFromWindow(v: View) { + targetView.removeOnAttachStateChangeListener(this) + dispatcher.unregisterOnBackInvokedCallback(onBackAnimationCallback) + } + } + ) + } + private fun moveSourceDrawingToDialog() { if (decorView.viewRootImpl == null) { // Make sure that we have access to the dialog view root to move the drawing to the diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 26aa0e85cbd2..23e3a01c2686 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -34,6 +34,7 @@ import android.view.ViewGroup import android.view.ViewGroupOverlay import android.widget.FrameLayout import com.android.internal.jank.InteractionJankMonitor +import java.lang.IllegalArgumentException import java.util.LinkedList import kotlin.math.min import kotlin.math.roundToInt @@ -46,7 +47,8 @@ private const val TAG = "GhostedViewLaunchAnimatorController" * of the ghosted view. * * Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during - * the animation. + * the animation. It must also implement [LaunchableView], otherwise an exception will be thrown + * during this controller instantiation. * * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView] * whenever possible instead. @@ -101,6 +103,15 @@ constructor( private val background: Drawable? init { + // Make sure the View we launch from implements LaunchableView to avoid visibility issues. + if (ghostedView !is LaunchableView) { + throw IllegalArgumentException( + "A GhostedViewLaunchAnimatorController was created from a View that does not " + + "implement LaunchableView. This can lead to subtle bugs where the visibility " + + "of the View we are launching from is not what we expected." + ) + } + /** Find the first view with a background in [view] and its children. */ fun findBackground(view: View): Drawable? { if (view.background != null) { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt new file mode 100644 index 000000000000..2eb503b43cc5 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.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.animation + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout + +/** A [FrameLayout] that also implements [LaunchableView]. */ +open class LaunchableFrameLayout : FrameLayout, LaunchableView { + private val delegate = + LaunchableViewDelegate( + this, + superSetVisibility = { super.setVisibility(it) }, + ) + + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + override fun setShouldBlockVisibilityChanges(block: Boolean) { + delegate.setShouldBlockVisibilityChanges(block) + } + + override fun setVisibility(visibility: Int) { + delegate.setVisibility(visibility) + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt index 9257f99efe96..46d5a5c0af8c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt @@ -25,7 +25,7 @@ import com.android.internal.jank.InteractionJankMonitor /** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */ class ViewDialogLaunchAnimatorController internal constructor( - internal val source: View, + private val source: View, override val cuj: DialogCuj?, ) : DialogLaunchAnimator.Controller { override val viewRoot: ViewRootImpl? diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt new file mode 100644 index 000000000000..f3d8b1782486 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.animation.back + +import android.util.DisplayMetrics +import android.view.animation.Interpolator +import android.window.BackEvent +import com.android.systemui.animation.Interpolators +import com.android.systemui.util.dpToPx + +/** Used to convert [BackEvent] into a [BackTransformation]. */ +fun interface BackAnimationSpec { + + /** Computes transformation based on a [backEvent] and sets it to [result]. */ + fun getBackTransformation( + backEvent: BackEvent, + progressY: Float, // TODO(b/265060720): Remove progressY. Could be retrieved from backEvent + result: BackTransformation, + ) + + companion object +} + +/** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */ +fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec( + displayMetrics: DisplayMetrics, + maxMarginXdp: Float, + maxMarginYdp: Float, + minScale: Float, + translateXEasing: Interpolator = Interpolators.STANDARD_DECELERATE, + translateYEasing: Interpolator = Interpolators.LINEAR, + scaleEasing: Interpolator = Interpolators.STANDARD_DECELERATE, +): BackAnimationSpec { + val screenWidthPx = displayMetrics.widthPixels + val screenHeightPx = displayMetrics.heightPixels + + val maxMarginXPx = maxMarginXdp.dpToPx(displayMetrics) + val maxMarginYPx = maxMarginYdp.dpToPx(displayMetrics) + val maxTranslationXByScale = (screenWidthPx - screenWidthPx * minScale) / 2 + val maxTranslationX = maxTranslationXByScale - maxMarginXPx + val maxTranslationYByScale = (screenHeightPx - screenHeightPx * minScale) / 2 + val maxTranslationY = maxTranslationYByScale - maxMarginYPx + val minScaleReversed = 1f - minScale + + return BackAnimationSpec { backEvent, progressY, result -> + val direction = if (backEvent.swipeEdge == BackEvent.EDGE_LEFT) 1 else -1 + val progressX = backEvent.progress + + val ratioTranslateX = translateXEasing.getInterpolation(progressX) + val ratioTranslateY = translateYEasing.getInterpolation(progressY) + val ratioScale = scaleEasing.getInterpolation(progressX) + + result.apply { + translateX = ratioTranslateX * direction * maxTranslationX + translateY = ratioTranslateY * maxTranslationY + scale = 1f - (ratioScale * minScaleReversed) + } + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt new file mode 100644 index 000000000000..c6b70736bc67 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.animation.back + +import android.util.DisplayMetrics + +/** + * SysUI transitions - Dismiss app (ST1) Return to launching surface or place of origin + * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-1-dismiss-app + */ +fun BackAnimationSpec.Companion.dismissAppForSysUi( + displayMetrics: DisplayMetrics, +): BackAnimationSpec = + BackAnimationSpec.createFloatingSurfaceAnimationSpec( + displayMetrics = displayMetrics, + maxMarginXdp = 8f, + maxMarginYdp = 8f, + minScale = 0.8f, + ) + +/** + * SysUI transitions - Cross task (ST2) Return to previous task/app, keeping the current one open + * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-2-cross-task + */ +fun BackAnimationSpec.Companion.crossTaskForSysUi( + displayMetrics: DisplayMetrics, +): BackAnimationSpec = + BackAnimationSpec.createFloatingSurfaceAnimationSpec( + displayMetrics = displayMetrics, + maxMarginXdp = 8f, + maxMarginYdp = 8f, + minScale = 0.8f, + ) + +/** + * SysUI transitions - Inner area dismiss (ST3) Dismiss non-detachable surface + * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-3-inner-area-dismiss + */ +fun BackAnimationSpec.Companion.innerAreaDismissForSysUi( + displayMetrics: DisplayMetrics, +): BackAnimationSpec = + BackAnimationSpec.createFloatingSurfaceAnimationSpec( + displayMetrics = displayMetrics, + maxMarginXdp = 0f, + maxMarginYdp = 0f, + minScale = 0.9f, + ) + +/** + * SysUI transitions - Floating system surfaces (ST4) + * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-4-floating-system-surfaces + */ +fun BackAnimationSpec.Companion.floatingSystemSurfacesForSysUi( + displayMetrics: DisplayMetrics, +): BackAnimationSpec = + BackAnimationSpec.createFloatingSurfaceAnimationSpec( + displayMetrics = displayMetrics, + maxMarginXdp = 8f, + maxMarginYdp = 8f, + minScale = 0.8f, + ) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt new file mode 100644 index 000000000000..49d1fb423d2b --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.animation.back + +import android.view.View + +/** + * This object that represents the transformation to apply to the target. The properties of this + * object are mutable for performance reasons (avoid recreating this object) + */ +data class BackTransformation( + var translateX: Float = Float.NaN, + var translateY: Float = Float.NaN, + var scale: Float = Float.NaN, +) + +/** Apply the transformation to the [targetView] */ +fun BackTransformation.applyTo(targetView: View) { + if (translateX.isFinite()) targetView.translationX = translateX + if (translateY.isFinite()) targetView.translationY = translateY + if (scale.isFinite()) { + targetView.scaleX = scale + targetView.scaleY = scale + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt new file mode 100644 index 000000000000..33d14b14c702 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.animation.back + +import android.util.DisplayMetrics +import android.window.BackEvent +import android.window.OnBackAnimationCallback + +/** + * Generates an [OnBackAnimationCallback] given a [backAnimationSpec]. [onBackProgressed] will be + * called on each update passing the current [BackTransformation]. + * + * Optionally, you can specify [onBackStarted], [onBackInvoked], and [onBackCancelled] callbacks. + */ +fun onBackAnimationCallbackFrom( + backAnimationSpec: BackAnimationSpec, + displayMetrics: DisplayMetrics, // TODO(b/265060720): We could remove this + onBackProgressed: (BackTransformation) -> Unit, + onBackStarted: (BackEvent) -> Unit = {}, + onBackInvoked: () -> Unit = {}, + onBackCancelled: () -> Unit = {}, +): OnBackAnimationCallback { + return object : OnBackAnimationCallback { + private var initialY = 0f + private val lastTransformation = BackTransformation() + + override fun onBackStarted(backEvent: BackEvent) { + initialY = backEvent.touchY + onBackStarted(backEvent) + } + + override fun onBackProgressed(backEvent: BackEvent) { + val progressY = (backEvent.touchY - initialY) / displayMetrics.heightPixels + + backAnimationSpec.getBackTransformation( + backEvent = backEvent, + progressY = progressY, + result = lastTransformation, + ) + + onBackProgressed(lastTransformation) + } + + override fun onBackInvoked() { + onBackInvoked() + } + + override fun onBackCancelled() { + onBackCancelled() + } + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt index 0e3d41c40c97..7897934fa264 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt @@ -48,7 +48,7 @@ class RippleAnimation(private val config: RippleAnimationConfig) { animator.addUpdateListener { updateListener -> val now = updateListener.currentPlayTime val progress = updateListener.animatedValue as Float - rippleShader.progress = progress + rippleShader.rawProgress = progress rippleShader.distortionStrength = if (config.shouldDistort) 1 - progress else 0f rippleShader.time = now.toFloat() } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt index 90585109643a..4322d53bcab9 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt @@ -18,6 +18,7 @@ package com.android.systemui.surfaceeffects.ripple import android.graphics.PointF import android.graphics.RuntimeShader import android.util.MathUtils +import com.android.systemui.animation.Interpolators import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary @@ -47,7 +48,6 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : """ uniform vec2 in_center; uniform vec2 in_size; - uniform float in_progress; uniform float in_cornerRadius; uniform float in_thickness; uniform float in_time; @@ -162,21 +162,15 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : maxSize.y = height } - /** Progress of the ripple. Float value between [0, 1]. */ - var progress: Float = 0.0f + /** + * Linear progress of the ripple. Float value between [0, 1]. + * + * <p>Note that the progress here is expected to be linear without any curve applied. + */ + var rawProgress: Float = 0.0f set(value) { field = value - setFloatUniform("in_progress", value) - val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value) - - currentWidth = maxSize.x * curvedProg - currentHeight = maxSize.y * curvedProg - setFloatUniform("in_size", /* width= */ currentWidth, /* height= */ currentHeight) - setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f) - // radius should not exceed width and height values. - setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg) - - setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value)) + progress = Interpolators.STANDARD.getInterpolation(value) val fadeIn = subProgress(0f, 0.1f, value) val fadeOutNoise = subProgress(0.4f, 1f, value) @@ -191,6 +185,20 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple)) } + /** Progress with Standard easing curve applied. */ + private var progress: Float = 0.0f + set(value) { + currentWidth = maxSize.x * value + currentHeight = maxSize.y * value + setFloatUniform("in_size", currentWidth, currentHeight) + + setFloatUniform("in_thickness", maxSize.y * value * 0.5f) + // radius should not exceed width and height values. + setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * value) + + setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value)) + } + /** Play time since the start of the effect. */ var time: Float = 0.0f set(value) { @@ -220,7 +228,7 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : var distortionStrength: Float = 0.0f set(value) { field = value - setFloatUniform("in_distort_radial", 75 * progress * value) + setFloatUniform("in_distort_radial", 75 * rawProgress * value) setFloatUniform("in_distort_xy", 75 * value) } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt index b37c73417119..bc4796aff042 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt @@ -77,7 +77,7 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a rippleShader = RippleShader(rippleShape) rippleShader.color = RippleAnimationConfig.RIPPLE_DEFAULT_COLOR - rippleShader.progress = 0f + rippleShader.rawProgress = 0f rippleShader.sparkleStrength = RippleAnimationConfig.RIPPLE_SPARKLE_STRENGTH rippleShader.pixelDensity = resources.displayMetrics.density @@ -93,7 +93,7 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a animator.addUpdateListener { updateListener -> val now = updateListener.currentPlayTime val progress = updateListener.animatedValue as Float - rippleShader.progress = progress + rippleShader.rawProgress = progress rippleShader.distortionStrength = 1 - progress rippleShader.time = now.toFloat() invalidate() @@ -142,25 +142,13 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a } // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the // active effect area. Values here should be kept in sync with the animation implementation - // in the ripple shader. + // in the ripple shader. (Twice bigger) if (rippleShape == RippleShape.CIRCLE) { - val maskRadius = - (1 - - (1 - rippleShader.progress) * - (1 - rippleShader.progress) * - (1 - rippleShader.progress)) * maxWidth + val maskRadius = rippleShader.currentWidth canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint) } else { - val maskWidth = - (1 - - (1 - rippleShader.progress) * - (1 - rippleShader.progress) * - (1 - rippleShader.progress)) * maxWidth * 2 - val maskHeight = - (1 - - (1 - rippleShader.progress) * - (1 - rippleShader.progress) * - (1 - rippleShader.progress)) * maxHeight * 2 + val maskWidth = rippleShader.currentWidth * 2 + val maskHeight = rippleShader.currentHeight * 2 canvas.drawRect( /* left= */ centerX - maskWidth, /* top= */ centerY - maskHeight, diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt new file mode 100644 index 000000000000..4bc9972dd50f --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import android.content.Context +import android.content.res.Resources +import android.util.DisplayMetrics +import android.util.TypedValue + +/** Convert [this] number of dps to device pixels. */ +fun Number.dpToPx(context: Context): Float = dpToPx(resources = context.resources) + +/** Convert [this] number of dps to device pixels. */ +fun Number.dpToPx(resources: Resources): Float = dpToPx(displayMetrics = resources.displayMetrics) + +/** Convert [this] number of dps to device pixels. */ +fun Number.dpToPx(displayMetrics: DisplayMetrics): Float = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, toFloat(), displayMetrics) diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt index 4e96ddabfda6..cfc38df08b0a 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt @@ -33,8 +33,8 @@ import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.LocalMinimumTouchTargetEnforcement import androidx.compose.material3.contentColorFor +import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect @@ -65,21 +65,17 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.layout.boundsInRoot import androidx.compose.ui.layout.findRootCoordinates -import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewTreeLifecycleOwner import androidx.lifecycle.ViewTreeViewModelStoreOwner -import com.android.compose.runtime.movableContentOf import com.android.systemui.animation.Expandable import com.android.systemui.animation.LaunchAnimator import kotlin.math.max import kotlin.math.min -import kotlin.math.roundToInt /** * Create an expandable shape that can launch into an Activity or a Dialog. @@ -220,21 +216,8 @@ fun Expandable( // If this expandable is expanded when it's being directly clicked on, let's ensure that it has // the minimum interactive size followed by all M3 components (48.dp). val minInteractiveSizeModifier = - if (onClick != null && LocalMinimumTouchTargetEnforcement.current) { - // TODO(b/242040009): Replace this by Modifier.minimumInteractiveComponentSize() once - // http://aosp/2305511 is available. - val minTouchSize = LocalViewConfiguration.current.minimumTouchTargetSize - Modifier.layout { measurable, constraints -> - // Copied from androidx.compose.material3.InteractiveComponentSize.kt - val placeable = measurable.measure(constraints) - val width = maxOf(placeable.width, minTouchSize.width.roundToPx()) - val height = maxOf(placeable.height, minTouchSize.height.roundToPx()) - layout(width, height) { - val centerX = ((width - placeable.width) / 2f).roundToInt() - val centerY = ((height - placeable.height) / 2f).roundToInt() - placeable.place(centerX, centerY) - } - } + if (onClick != null) { + Modifier.minimumInteractiveComponentSize() } else { Modifier } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt index 18e8a962dc70..bf922bc98bcb 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt @@ -21,4 +21,5 @@ object KeyguardQuickAffordancePreviewConstants { const val MESSAGE_ID_SLOT_SELECTED = 1337 const val KEY_SLOT_ID = "slot_id" const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id" + const val KEY_HIGHLIGHT_QUICK_AFFORDANCES = "highlight_quick_affordances" } diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md index ccb35fac36f2..79d5718efe0d 100644 --- a/packages/SystemUI/docs/device-entry/quickaffordance.md +++ b/packages/SystemUI/docs/device-entry/quickaffordance.md @@ -52,6 +52,10 @@ A picker experience may: * Unselect an already-selected quick affordance from a slot * Unselect all already-selected quick affordances from a slot +## Testing +* Add a unit test for your implementation of `KeyguardQuickAffordanceConfig` +* Manually verify that your implementation works in multi-user environments from both the main user and a secondary user + ## Debugging To see the current state of the system, you can run `dumpsys`: diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 1f0081278092..1a67691e30bf 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -197,13 +197,10 @@ -packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt -packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt -packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -611,7 +608,6 @@ -packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt -packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt -packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index bc6e5ec6117c..e0cc8f4a7596 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -50,16 +50,24 @@ public interface BcSmartspaceDataPlugin extends Plugin { String TAG = "BcSmartspaceDataPlugin"; /** Register a listener to get Smartspace data. */ - void registerListener(SmartspaceTargetListener listener); + default void registerListener(SmartspaceTargetListener listener) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** Unregister a listener. */ - void unregisterListener(SmartspaceTargetListener listener); + default void unregisterListener(SmartspaceTargetListener listener) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** Register a SmartspaceEventNotifier. */ - default void registerSmartspaceEventNotifier(SmartspaceEventNotifier notifier) {} + default void registerSmartspaceEventNotifier(SmartspaceEventNotifier notifier) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** Push a SmartspaceTargetEvent to the SmartspaceEventNotifier. */ - default void notifySmartspaceEvent(SmartspaceTargetEvent event) {} + default void notifySmartspaceEvent(SmartspaceTargetEvent event) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** Allows for notifying the SmartspaceSession of SmartspaceTargetEvents. */ interface SmartspaceEventNotifier { @@ -72,16 +80,20 @@ public interface BcSmartspaceDataPlugin extends Plugin { * will be responsible for correctly setting the LayoutParams */ default SmartspaceView getView(ViewGroup parent) { - return null; + throw new UnsupportedOperationException("Not implemented by " + getClass()); } /** * As the smartspace view becomes available, allow listeners to receive an event. */ - default void addOnAttachStateChangeListener(View.OnAttachStateChangeListener listener) { } + default void addOnAttachStateChangeListener(View.OnAttachStateChangeListener listener) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** Updates Smartspace data and propagates it to any listeners. */ - void onTargetsAvailable(List<SmartspaceTarget> targets); + default void onTargetsAvailable(List<SmartspaceTarget> targets) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** Provides Smartspace data to registered listeners. */ interface SmartspaceTargetListener { @@ -96,7 +108,9 @@ public interface BcSmartspaceDataPlugin extends Plugin { /** * Sets {@link BcSmartspaceConfigPlugin}. */ - void registerConfigProvider(BcSmartspaceConfigPlugin configProvider); + default void registerConfigProvider(BcSmartspaceConfigPlugin configProvider) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** * Primary color for unprotected text @@ -138,28 +152,38 @@ public interface BcSmartspaceDataPlugin extends Plugin { /** * Set or clear Do Not Disturb information. */ - void setDnd(@Nullable Drawable image, @Nullable String description); + default void setDnd(@Nullable Drawable image, @Nullable String description) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** * Set or clear next alarm information */ - void setNextAlarm(@Nullable Drawable image, @Nullable String description); + default void setNextAlarm(@Nullable Drawable image, @Nullable String description) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** * Set or clear device media playing */ - void setMediaTarget(@Nullable SmartspaceTarget target); + default void setMediaTarget(@Nullable SmartspaceTarget target) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** * Get the index of the currently selected page. */ - int getSelectedPage(); + default int getSelectedPage() { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } /** * Return the top padding value from the currently visible card, or 0 if there is no current * card. */ - int getCurrentCardTopPadding(); + default int getCurrentCardTopPadding() { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } } /** Interface for launching Intents, which can differ on the lockscreen */ diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java index 9ed3bac57e66..70b5d739ea7c 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java @@ -105,6 +105,11 @@ public interface StatusBarStateController { default void onDozingChanged(boolean isDozing) {} /** + * Callback to be notified when Dreaming changes. Dreaming is stored separately from state. + */ + default void onDreamingChanged(boolean isDreaming) {} + + /** * Callback to be notified when the doze amount changes. Useful for animations. * Note: this will be called for each animation frame. Please be careful to avoid * performance regressions. diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml index 2cac9c706fe9..90851e2a921c 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml @@ -45,7 +45,7 @@ android:id="@+id/user_switcher_header" android:textDirection="locale" android:layout_width="@dimen/bouncer_user_switcher_width" - android:layout_height="wrap_content" /> + android:layout_height="match_parent" /> </com.android.keyguard.KeyguardUserSwitcherAnchor> </LinearLayout> diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml index a1068c65bae2..6c8db91d49bb 100644 --- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml @@ -25,9 +25,6 @@ <!-- Margin around the various security views --> <dimen name="keyguard_security_view_top_margin">12dp</dimen> - <!-- Padding for the lock icon on the keyguard --> - <dimen name="lock_icon_padding">16dp</dimen> - <!-- Overload default clock widget parameters --> <dimen name="widget_big_font_size">100dp</dimen> <dimen name="widget_label_font_size">18sp</dimen> diff --git a/packages/SystemUI/res-product/values-pt-rBR/strings.xml b/packages/SystemUI/res-product/values-pt-rBR/strings.xml index fed58e681606..fff7606d4732 100644 --- a/packages/SystemUI/res-product/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res-product/values-pt-rBR/strings.xml @@ -43,6 +43,6 @@ <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Desbloqueie seu smartphone para ver mais opções"</string> <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Desbloqueie seu tablet para ver mais opções"</string> <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Desbloqueie seu dispositivo para ver mais opções"</string> - <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Mídia tocando neste smartphone"</string> + <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Tocando neste smartphone"</string> <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Mídia tocando neste tablet"</string> </resources> diff --git a/packages/SystemUI/res-product/values-pt/strings.xml b/packages/SystemUI/res-product/values-pt/strings.xml index fed58e681606..fff7606d4732 100644 --- a/packages/SystemUI/res-product/values-pt/strings.xml +++ b/packages/SystemUI/res-product/values-pt/strings.xml @@ -43,6 +43,6 @@ <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Desbloqueie seu smartphone para ver mais opções"</string> <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Desbloqueie seu tablet para ver mais opções"</string> <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Desbloqueie seu dispositivo para ver mais opções"</string> - <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Mídia tocando neste smartphone"</string> + <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Tocando neste smartphone"</string> <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Mídia tocando neste tablet"</string> </resources> diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml index acd2462dbf78..7d03b0d64b90 100644 --- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml +++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml @@ -21,7 +21,7 @@ <item android:state_selected="true"> <shape android:shape="oval"> <stroke - android:color="@color/control_primary_text" + android:color="?android:attr/textColorPrimary" android:width="2dp"/> <size android:width="@dimen/keyguard_affordance_fixed_width" diff --git a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml new file mode 100644 index 000000000000..3807b92ae39d --- /dev/null +++ b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~ + --> +<ripple + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white"/> + <corners android:radius="28dp" /> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <solid android:color="?androidprv:attr/colorSurface" /> + <corners android:radius="28dp" /> + </shape> + </item> +</ripple> diff --git a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml deleted file mode 100644 index 1a1fc75a41a1..000000000000 --- a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml +++ /dev/null @@ -1,160 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> -<com.android.systemui.screenshot.DraggableConstraintLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/clipboard_ui" - android:theme="@style/FloatingOverlay" - android:alpha="0" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:contentDescription="@string/clipboard_overlay_window_name"> - <ImageView - android:id="@+id/actions_container_background" - android:visibility="gone" - android:layout_height="0dp" - android:layout_width="0dp" - android:elevation="4dp" - android:background="@drawable/action_chip_container_background" - android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" - app:layout_constraintBottom_toBottomOf="@+id/actions_container" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@+id/actions_container" - app:layout_constraintEnd_toEndOf="@+id/actions_container"/> - <HorizontalScrollView - android:id="@+id/actions_container" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" - android:paddingEnd="@dimen/overlay_action_container_padding_right" - android:paddingVertical="@dimen/overlay_action_container_padding_vertical" - android:elevation="4dp" - android:scrollbars="none" - android:layout_marginBottom="4dp" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintWidth_percent="1.0" - app:layout_constraintWidth_max="wrap" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toEndOf="@+id/preview_border" - app:layout_constraintEnd_toEndOf="parent"> - <LinearLayout - android:id="@+id/actions" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:animateLayoutChanges="true"> - <include layout="@layout/overlay_action_chip" - android:id="@+id/share_chip"/> - <include layout="@layout/overlay_action_chip" - android:id="@+id/remote_copy_chip"/> - <include layout="@layout/overlay_action_chip" - android:id="@+id/edit_chip"/> - </LinearLayout> - </HorizontalScrollView> - <View - android:id="@+id/preview_border" - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_marginStart="@dimen/overlay_offset_x" - android:layout_marginBottom="12dp" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - android:elevation="7dp" - app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end" - app:layout_constraintTop_toTopOf="@id/clipboard_preview_top" - android:background="@drawable/overlay_border"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/clipboard_preview_end" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierMargin="@dimen/overlay_border_width" - app:barrierDirection="end" - app:constraint_referenced_ids="clipboard_preview"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/clipboard_preview_top" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierDirection="top" - app:barrierMargin="@dimen/overlay_border_width_neg" - app:constraint_referenced_ids="clipboard_preview"/> - <FrameLayout - android:id="@+id/clipboard_preview" - android:elevation="7dp" - android:background="@drawable/overlay_preview_background" - android:clipChildren="true" - android:clipToOutline="true" - android:clipToPadding="true" - android:layout_width="@dimen/clipboard_preview_size" - android:layout_margin="@dimen/overlay_border_width" - android:layout_height="wrap_content" - android:layout_gravity="center" - app:layout_constraintBottom_toBottomOf="@id/preview_border" - app:layout_constraintStart_toStartOf="@id/preview_border" - app:layout_constraintEnd_toEndOf="@id/preview_border" - app:layout_constraintTop_toTopOf="@id/preview_border"> - <TextView android:id="@+id/text_preview" - android:textFontWeight="500" - android:padding="8dp" - android:gravity="center|start" - android:ellipsize="end" - android:autoSizeTextType="uniform" - android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font" - android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font" - android:textColor="?attr/overlayButtonTextColor" - android:textColorLink="?attr/overlayButtonTextColor" - android:background="?androidprv:attr/colorAccentSecondary" - android:layout_width="@dimen/clipboard_preview_size" - android:layout_height="@dimen/clipboard_preview_size"/> - <ImageView - android:id="@+id/image_preview" - android:scaleType="fitCenter" - android:adjustViewBounds="true" - android:contentDescription="@string/clipboard_image_preview" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - <TextView - android:id="@+id/hidden_preview" - android:visibility="gone" - android:textFontWeight="500" - android:padding="8dp" - android:gravity="center" - android:textSize="14sp" - android:textColor="?attr/overlayButtonTextColor" - android:background="?androidprv:attr/colorAccentSecondary" - android:layout_width="@dimen/clipboard_preview_size" - android:layout_height="@dimen/clipboard_preview_size"/> - </FrameLayout> - <FrameLayout - android:id="@+id/dismiss_button" - android:layout_width="@dimen/overlay_dismiss_button_tappable_size" - android:layout_height="@dimen/overlay_dismiss_button_tappable_size" - android:elevation="10dp" - android:visibility="gone" - android:alpha="0" - app:layout_constraintStart_toEndOf="@id/clipboard_preview" - app:layout_constraintEnd_toEndOf="@id/clipboard_preview" - app:layout_constraintTop_toTopOf="@id/clipboard_preview" - app:layout_constraintBottom_toTopOf="@id/clipboard_preview" - android:contentDescription="@string/clipboard_dismiss_description"> - <ImageView - android:id="@+id/dismiss_image" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_margin="@dimen/overlay_dismiss_button_margin" - android:src="@drawable/overlay_cancel"/> - </FrameLayout> -</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml index de96e9765668..446bb0122630 100644 --- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml +++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml @@ -20,7 +20,7 @@ android:layout_width="wrap_content" android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding"> - <ImageView + <com.android.systemui.common.ui.view.LaunchableImageView android:id="@+id/home_controls_chip" android:layout_height="@dimen/keyguard_affordance_fixed_height" android:layout_width="@dimen/keyguard_affordance_fixed_width" diff --git a/packages/SystemUI/res/layout/emergency_cryptkeeper_text.xml b/packages/SystemUI/res/layout/emergency_cryptkeeper_text.xml deleted file mode 100644 index 0a1730a72920..000000000000 --- a/packages/SystemUI/res/layout/emergency_cryptkeeper_text.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2016 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.policy.EmergencyCryptkeeperText - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/emergency_cryptkeeper_text" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:textAppearance="@style/TextAppearance.StatusBar.Clock" - android:paddingStart="6dp" - android:singleLine="true" - android:ellipsize="marquee" - android:gravity="center_vertical|start" - />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/global_actions_grid_lite.xml b/packages/SystemUI/res/layout/global_actions_grid_lite.xml index 5588fd391681..a64c9aebec3e 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_lite.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_lite.xml @@ -33,7 +33,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:layout_weight="1"> - <androidx.constraintlayout.widget.ConstraintLayout + <com.android.systemui.common.ui.view.LaunchableConstraintLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@android:id/list" @@ -55,6 +55,6 @@ app:flow_horizontalGap="@dimen/global_actions_lite_padding" app:flow_verticalGap="@dimen/global_actions_lite_padding" app:flow_horizontalStyle="packed"/> - </androidx.constraintlayout.widget.ConstraintLayout> + </com.android.systemui.common.ui.view.LaunchableConstraintLayout> </com.android.systemui.globalactions.GlobalActionsLayoutLite> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml index 6f3362308484..07c428b5dd7a 100644 --- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml +++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml @@ -24,7 +24,7 @@ android:layout_gravity="end"> <!-- We add a background behind the UserAvatarView with the same color and with a circular shape so that this view can be expanded into a Dialog or an Activity. --> - <FrameLayout + <com.android.systemui.animation.LaunchableFrameLayout android:id="@+id/kg_multi_user_avatar_with_background" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -42,5 +42,5 @@ systemui:framePadding="0dp" systemui:frameWidth="0dp"> </com.android.systemui.statusbar.phone.UserAvatarView> - </FrameLayout> + </com.android.systemui.animation.LaunchableFrameLayout> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml new file mode 100644 index 000000000000..89d88fea1817 --- /dev/null +++ b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~ + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minHeight="52dp" + android:orientation="horizontal" + android:gravity="center_vertical" + android:background="@drawable/keyguard_settings_popup_menu_background" + android:paddingStart="16dp" + android:paddingEnd="24dp" + android:paddingVertical="16dp"> + + <ImageView + android:id="@+id/icon" + android:layout_width="20dp" + android:layout_height="20dp" + android:layout_marginEnd="16dp" + android:tint="?android:attr/textColorPrimary" + android:importantForAccessibility="no" + tools:ignore="UseAppTint" /> + + <TextView + android:id="@+id/text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:attr/textColorPrimary" + android:textSize="14sp" + android:maxLines="1" + android:ellipsize="end" /> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml new file mode 100644 index 000000000000..101fad97beb5 --- /dev/null +++ b/packages/SystemUI/res/layout/media_recommendation_view.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<!-- Layout for media recommendation item inside QSPanel carousel --> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Album cover --> + <ImageView + android:id="@+id/media_cover" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:translationZ="0dp" + android:scaleType="centerCrop" + android:adjustViewBounds="true" + android:clipToOutline="true" + android:background="@drawable/bg_smartspace_media_item"/> + + <!-- App icon --> + <com.android.internal.widget.CachingIconView + android:id="@+id/media_rec_app_icon" + android:layout_width="@dimen/qs_media_rec_icon_top_margin" + android:layout_height="@dimen/qs_media_rec_icon_top_margin" + android:layout_marginStart="@dimen/qs_media_info_spacing" + android:layout_marginTop="@dimen/qs_media_info_spacing"/> + + <!-- Artist name --> + <TextView + android:id="@+id/media_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/qs_media_info_spacing" + android:layout_marginEnd="@dimen/qs_media_info_spacing" + android:layout_marginBottom="@dimen/qs_media_rec_album_title_bottom_margin" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:singleLine="true" + android:textSize="12sp" + android:gravity="top" + android:layout_gravity="bottom"/> + + <!-- Album name --> + <TextView + android:id="@+id/media_subtitle" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_rec_album_subtitle_height" + android:layout_marginEnd="@dimen/qs_media_info_spacing" + android:layout_marginStart="@dimen/qs_media_info_spacing" + android:layout_marginBottom="@dimen/qs_media_info_spacing" + android:fontFamily="@*android:string/config_headlineFontFamily" + android:singleLine="true" + android:textSize="11sp" + android:gravity="center_vertical" + android:layout_gravity="bottom"/> +</merge>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_recommendations.xml b/packages/SystemUI/res/layout/media_recommendations.xml new file mode 100644 index 000000000000..65fc19c5b2a4 --- /dev/null +++ b/packages/SystemUI/res/layout/media_recommendations.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<!-- Layout for media recommendations inside QSPanel carousel --> +<com.android.systemui.util.animation.TransitionLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/media_recommendations_updated" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" + android:forceHasOverlappingRendering="false" + android:background="@drawable/qs_media_background" + android:theme="@style/MediaPlayer"> + + <!-- This view just ensures the full media player is a certain height. --> + <View + android:id="@+id/sizing_view" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_session_height_expanded" /> + + <TextView + android:id="@+id/media_rec_title" + style="@style/MediaPlayer.Recommendation.Header" + android:text="@string/controls_media_smartspace_rec_header"/> + + <FrameLayout + android:id="@+id/media_cover1_container" + style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" + > + + <include + layout="@layout/media_recommendation_view"/> + + </FrameLayout> + + + <FrameLayout + android:id="@+id/media_cover2_container" + style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" + > + + <include + layout="@layout/media_recommendation_view"/> + + </FrameLayout> + + <FrameLayout + android:id="@+id/media_cover3_container" + style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" + > + + <include + layout="@layout/media_recommendation_view"/> + + </FrameLayout> + + <include + layout="@layout/media_long_press_menu" /> + +</com.android.systemui.util.animation.TransitionLayout> diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index abc83379950a..f2e114b511bc 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -106,7 +106,7 @@ app:layout_constrainedWidth="true" app:layout_constraintWidth_min="@dimen/min_clickable_item_size" app:layout_constraintHeight_min="@dimen/min_clickable_item_size"> - <LinearLayout + <com.android.systemui.common.ui.view.LaunchableLinearLayout android:id="@+id/media_seamless_button" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -135,7 +135,7 @@ android:textDirection="locale" android:textSize="12sp" android:lineHeight="16sp" /> - </LinearLayout> + </com.android.systemui.common.ui.view.LaunchableLinearLayout> </LinearLayout> <!-- Song name --> diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml index c949ba0db171..18d231c5d32f 100644 --- a/packages/SystemUI/res/layout/ongoing_call_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml @@ -23,7 +23,7 @@ android:layout_gravity="center_vertical|start" android:layout_marginStart="5dp" > - <LinearLayout + <com.android.systemui.common.ui.view.LaunchableLinearLayout android:id="@+id/ongoing_call_chip_background" android:layout_width="wrap_content" android:layout_height="@dimen/ongoing_appops_chip_height" @@ -55,5 +55,5 @@ android:textColor="?android:attr/colorPrimary" /> - </LinearLayout> + </com.android.systemui.common.ui.view.LaunchableLinearLayout> </FrameLayout> diff --git a/packages/SystemUI/res/layout/screenshot_detection_notice.xml b/packages/SystemUI/res/layout/screenshot_detection_notice.xml new file mode 100644 index 000000000000..fc936c01227e --- /dev/null +++ b/packages/SystemUI/res/layout/screenshot_detection_notice.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/screenshot_detection_notice" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:padding="12dp" + android:visibility="gone"> + + <TextView + android:id="@+id/screenshot_detection_notice_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:lineHeight="24sp" + android:textSize="18sp" /> +</FrameLayout> diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml index 496eb6e6130e..a748e29ee041 100644 --- a/packages/SystemUI/res/layout/screenshot_static.xml +++ b/packages/SystemUI/res/layout/screenshot_static.xml @@ -31,7 +31,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/actions_container" app:layout_constraintEnd_toEndOf="@+id/actions_container" - app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"/> + app:layout_constraintBottom_toTopOf="@id/guideline"/> <HorizontalScrollView android:id="@+id/actions_container" android:layout_width="0dp" @@ -127,57 +127,30 @@ app:layout_constraintTop_toTopOf="@id/screenshot_preview" android:elevation="7dp"/> - <androidx.constraintlayout.widget.ConstraintLayout + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_end="0dp" /> + + <FrameLayout android:id="@+id/screenshot_message_container" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal" - android:layout_marginVertical="4dp" + android:layout_marginTop="4dp" + android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" android:paddingHorizontal="@dimen/overlay_action_container_padding_end" android:paddingVertical="@dimen/overlay_action_container_padding_vertical" android:elevation="4dp" android:background="@drawable/action_chip_container_background" android:visibility="gone" + app:layout_constraintTop_toBottomOf="@id/guideline" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintBottom_toBottomOf="parent"> - - <ImageView - android:id="@+id/screenshot_message_icon" - android:layout_width="48dp" - android:layout_height="48dp" - android:paddingEnd="4dp" - android:src="@drawable/ic_work_app_badge" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/screenshot_message_content" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent"/> - - <TextView - android:id="@+id/screenshot_message_content" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_gravity="start" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toEndOf="@id/screenshot_message_icon" - app:layout_constraintEnd_toStartOf="@id/message_dismiss_button"/> - - <FrameLayout - android:id="@+id/message_dismiss_button" - android:layout_width="@dimen/overlay_dismiss_button_tappable_size" - android:layout_height="@dimen/overlay_dismiss_button_tappable_size" - app:layout_constraintStart_toEndOf="@id/screenshot_message_content" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - android:contentDescription="@string/screenshot_dismiss_work_profile"> - <ImageView - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_margin="@dimen/overlay_dismiss_button_margin" - android:src="@drawable/overlay_cancel"/> - </FrameLayout> - - </androidx.constraintlayout.widget.ConstraintLayout> + > + <include layout="@layout/screenshot_work_profile_first_run" /> + <include layout="@layout/screenshot_detection_notice" /> + </FrameLayout> </com.android.systemui.screenshot.DraggableConstraintLayout> diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml new file mode 100644 index 000000000000..c1817242aa01 --- /dev/null +++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/work_profile_first_run" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:visibility="gone"> + <ImageView + android:id="@+id/screenshot_message_icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:paddingEnd="4dp" + android:src="@drawable/ic_work_app_badge"/> + + <TextView + android:id="@+id/screenshot_message_content" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_gravity="start"/> + + <FrameLayout + android:id="@+id/message_dismiss_button" + android:layout_width="@dimen/overlay_dismiss_button_tappable_size" + android:layout_height="@dimen/overlay_dismiss_button_tappable_size" + android:contentDescription="@string/screenshot_dismiss_work_profile"> + <ImageView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="@dimen/overlay_dismiss_button_margin" + android:src="@drawable/overlay_cancel"/> + </FrameLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 3b71dc32b256..64aa629b02f0 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -149,11 +149,4 @@ </FrameLayout> </LinearLayout> - <ViewStub - android:id="@+id/emergency_cryptkeeper_text" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout="@layout/emergency_cryptkeeper_text" - /> - </com.android.systemui.statusbar.phone.PhoneStatusBarView> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 159323a5b557..3c860a9c2c31 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -26,6 +26,11 @@ android:layout_height="match_parent" android:background="@android:color/transparent"> + <com.android.systemui.common.ui.view.LongPressHandlingView + android:id="@+id/keyguard_long_press" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <ViewStub android:id="@+id/keyguard_qs_user_switch_stub" android:layout="@layout/keyguard_qs_user_switch" diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index fbe6280f71bd..fb4a3cbe938f 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Outomaties"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Geen klank of vibrasie nie"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Geen klank of vibrasie nie en verskyn laer in gespreksafdeling"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan lui of vibreer op grond van fooninstellings"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan lui of vibreer op grond van fooninstellings. Gesprekke van <xliff:g id="APP_NAME">%1$s</xliff:g> af verskyn by verstek in \'n borrel."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Kan lui of vibreer op grond van toestelinstellings"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kan lui of vibreer op grond van toestelinstellings. Gesprekke van <xliff:g id="APP_NAME">%1$s</xliff:g> af verskyn by verstek in ’n borrel."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Laat die stelsel bepaal of hierdie kennisgewing \'n klank moet maak of vibreer"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Bevorder na Verstek"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Gedegradeer na Stil"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Kies program om kontroles by te voeg"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrole bygevoeg.}other{# kontroles bygevoeg.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Verwyder"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Voeg <xliff:g id="APPNAME">%s</xliff:g> by?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Wanneer jy <xliff:g id="APPNAME">%s</xliff:g> byvoeg, kan dit kontroles en inhoud by hierdie paneel voeg. Jy kan in sommige apps kies watter kontroles hier verskyn."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"As gunsteling gemerk"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"As gunsteling gemerk; posisie <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"As gunsteling ontmerk"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Ontdoen"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Beweeg nader om op <xliff:g id="DEVICENAME">%1$s</xliff:g> te speel"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Beweeg nader aan <xliff:g id="DEVICENAME">%1$s</xliff:g> om hier te speel"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Speel tans op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Iets is fout. Probeer weer."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Laai tans"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Fout, probeer weer"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Voeg kontroles by"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Wysig kontroles"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Voeg app by"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Voeg uitvoere by"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Groep"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 toestel gekies"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Hierdie skerm sal afskakel"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Voubare toestel word ontvou"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Voubare toestel word omgekeer"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batterykrag oor"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Koppel jou stilus aan ’n laaier"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Stilus se battery is amper pap"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Kan nie van hierdie profiel af bel nie"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Jou werkbeleid laat jou toe om slegs van die werkprofiel af foonoproepe te maak"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Skakel oor na werkprofiel"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Maak toe"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 0a26c1d4354c..bf00ec390676 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"የታች ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"የግራ ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"የቀኝ ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"የሥራ ቅጽበታዊ ገጽ እይታዎች በ<xliff:g id="APP">%1$s</xliff:g> መተግበሪያ ውስጥ ይቀመጣሉ"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ፋይሎች"</string> <string name="screenrecord_name" msgid="2596401223859996572">"የማያ መቅጃ"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"የማያ ገጽ ቀረጻን በማሰናዳት ላይ"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"ለአንድ የማያ ገጽ ቀረጻ ክፍለ-ጊዜ በመካሄድ ያለ ማሳወቂያ"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ራስ-ሰር"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"ምንም ድምፅ ወይም ንዝረት የለም"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ምንም ድምፅ ወይም ንዝረት የለም እና በውይይት ክፍል ላይ አይታይም"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"በእርስዎ የስልክ ቅንብሮች የሚወሰን ሆኖ ሊደውል ወይም ሊነዝር ይችላል"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"በእርስዎ የስልክ ቅንብሮች የሚወሰን ሆኖ ሊደውል ወይም ሊነዝር ይችላል። የ<xliff:g id="APP_NAME">%1$s</xliff:g> አረፋ ውይይቶች በነባሪነት።"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"በመሣሪያ ቅንብሮች መሰረት ሊጮህ ወይም ሊነዝር ይችላል"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"በስልክ ቅንብሮች መሰረት ሊጮህ ወይም ሊነዝር ይችላል። የ<xliff:g id="APP_NAME">%1$s</xliff:g> ውይይቶች በነባሪነት አረፋ ይሆናሉ።"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ይህ ማሳወቂያ ድምፅ ወይም ንዝረት መደረግ ካለበት ስርዓቱ እንዲወሰን ያድርጉት"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>ሁኔታ:</b> ለነባሪ ከፍ ተዋውቋል።"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>ሁኔታ:</b> ወደ ዝምታ ዝቅ ተደርጓል"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"መቆጣጠሪያዎችን ለማከል መተግበሪያ ይምረጡ"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ቁጥጥር ታክሏል።}one{# ቁጥጥር ታክሏል።}other{# ቁጥጥሮች ታክለዋል።}}"</string> <string name="controls_removed" msgid="3731789252222856959">"ተወግዷል"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"ተወዳጅ የተደረገ"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ተወዳጅ ተደርጓል፣ አቋም <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ተወዳጅ አልተደረገም"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ከ<xliff:g id="APP_LABEL">%2$s</xliff:g> ያጫውቱ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ቀልብስ"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ ለማጫወት ጠጋ ያድርጉ"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"እዚህ ለማጫወት ወደ <xliff:g id="DEVICENAME">%1$s</xliff:g> ጠጋ ይበሉ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ በማጫወት ላይ"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"የሆነ ችግር ተፈጥሯል። እንደገና ይሞክሩ።"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"በመጫን ላይ"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ጡባዊ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"አልተገኘም"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"መቆጣጠሪያ አይገኝም"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"ስህተት፣ እንደገና ይሞክሩ"</string> <string name="controls_menu_add" msgid="4447246119229920050">"መቆጣጠሪያዎችን አክል"</string> <string name="controls_menu_edit" msgid="890623986951347062">"መቆጣጠሪያዎችን ያርትዑ"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ውጽዓቶችን ያክሉ"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"ቡድን"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 መሣሪያ ተመርጧል"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"የድምጽ መጠን"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ድምጽ ማውጫዎች እና ማሳያዎች"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"የተጠቆሙ መሣሪያዎች"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ማሰራጨት እንዴት እንደሚሠራ"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"ስርጭት"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ተኳሃኝ የብሉቱዝ መሣሪያዎች ያላቸው በአቅራቢያዎ ያሉ ሰዎች እርስዎ እያሰራጩት ያሉትን ሚዲያ ማዳመጥ ይችላሉ"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ይህ ማያ ገጽ ይጠፋል"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"መታጠፍ የሚችል መሣሪያ እየተዘረጋ ነው"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"መታጠፍ የሚችል መሣሪያ እየተገለበጠ ነው"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ባትሪ ይቀራል"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ብሮስፌዎን ከኃይል መሙያ ጋር ያገናኙ"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"የብሮስፌ ባትሪ ዝቅተኛ ነው"</string> + <string name="video_camera" msgid="7654002575156149298">"የቪድዮ ካሜራ"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"ከዚህ መገለጫ መደወል አይቻልም"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"የሥራ መመሪያዎ እርስዎ ከሥራ መገለጫው ብቻ ጥሪ እንዲያደርጉ ይፈቅድልዎታል"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"ወደ የሥራ መገለጫ ቀይር"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"ዝጋ"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index d641887c6153..804e565cc50a 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"الحد السفلى <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"الحد الأيسر <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"الحد الأيمن <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"يتم حفظ لقطات الشاشة الخاصة بالعمل في تطبيق \"<xliff:g id="APP">%1$s</xliff:g>\"."</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"الملفات"</string> <string name="screenrecord_name" msgid="2596401223859996572">"مسجّل الشاشة"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"جارٍ معالجة تسجيل الشاشة"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"إشعار مستمر لجلسة تسجيل شاشة"</string> @@ -438,7 +436,7 @@ <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"تطبيقات العمل الخاصة بك متّصلة بالإنترنت من خلال <xliff:g id="VPN_APP">%1$s</xliff:g>. يمكن لمشرف تكنولوجيا المعلومات ومزوّد خدمة الشبكة الافتراضية الخاصة (VPN) رؤية أنشطة الشبكة في تطبيقات العمل، بما في ذلك الرسائل الإلكترونية وبيانات التصفُّح."</string> <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"تطبيقاتك الشخصية متّصلة بالإنترنت من خلال <xliff:g id="VPN_APP">%1$s</xliff:g>. تظهر أنشطة الشبكة، بما في ذلك الرسائل الإلكترونية وبيانات التصفُّح، لمزوّد خدمة الشبكة الافتراضية الخاصة (VPN)."</string> <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string> - <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"فتح إعدادات الشبكة الافتراضية الخاصة (VPN)"</string> + <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"فتح إعدادات شبكة VPN"</string> <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"يتولّى أحد الوالدين إدارة هذا الجهاز. يمكن للوالدين عرض وإدارة معلوماتك، مثلاً التطبيقات التي تستخدمها وموقعك الجغرافي ووقت النظر إلى الشاشة."</string> <string name="legacy_vpn_name" msgid="4174223520162559145">"شبكة افتراضية خاصة"</string> <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"فتح القفل باستمرار بواسطة TrustAgent"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"تلقائي"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"بدون صوت أو اهتزاز"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"بدون صوت أو اهتزاز وتظهر في أسفل قسم المحادثات"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"يمكن إصدار رنين أو اهتزاز بناءً على إعدادات الهاتف."</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"يمكن إصدار رنين أو اهتزاز بناءً على إعدادات الهاتف. تظهر المحادثات من <xliff:g id="APP_NAME">%1$s</xliff:g> كفقاعات تلقائيًا."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"يمكن إصدار رنين أو اهتزاز بناءً على إعدادات الجهاز"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"يمكن إصدار رنين أو اهتزاز بناءً على إعدادات الجهاز. تظهر المحادثات من \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" كفقاعات تلقائيًا."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"السماح للنظام بتحديد ما إذا يجب اهتزاز الجهاز أو إصدار رنين عند تلقّي هذا الإشعار"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>الحالة:</b> تمت الترقية إلى الإعداد التلقائي"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>الحالة:</b> تم خفض الترتيب إلى الوضع صامت"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"اختيار تطبيق لإضافة عناصر التحكّم"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{تمت إضافة عنصر تحكّم واحد.}zero{تمت إضافة # عنصر تحكّم.}two{تمت إضافة عنصرَي تحكّم.}few{تمت إضافة # عناصر تحكّم.}many{تمت إضافة # عنصر تحكّم.}other{تمت إضافة # عنصر تحكّم.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"تمت الإزالة"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"تمت الإضافة إلى المفضّلة"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"تمت الإضافة إلى المفضّلة، الموضع <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"تمت الإزالة من المفضّلة"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"تراجع"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"عليك الاقتراب لتشغيل الوسائط على <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"للتشغيل هنا، عليك الاقتراب من \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\"."</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"جارٍ تشغيل الوسائط على <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"حدث خطأ. يُرجى إعادة المحاولة."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"جارٍ التحميل"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"جهاز لوحي"</string> <string name="controls_error_timeout" msgid="794197289772728958">"غير نشط، تحقّق من التطبيق."</string> <string name="controls_error_removed" msgid="6675638069846014366">"لم يتم العثور عليه."</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"عنصر التحكّم غير متوفّر"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"حدث خطأ، يُرجى إعادة المحاولة."</string> <string name="controls_menu_add" msgid="4447246119229920050">"إضافة عناصر تحكّم"</string> <string name="controls_menu_edit" msgid="890623986951347062">"تعديل عناصر التحكّم"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"إضافة مخرجات"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"مجموعة"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"تم اختيار جهاز واحد."</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"مستوى الصوت"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"مكبّرات الصوت والشاشات"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"الأجهزة المقترَحة"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"كيفية عمل البث"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"البث"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"يمكن للأشخاص القريبين منك الذين لديهم أجهزة متوافقة تتضمّن بلوتوث الاستماع إلى الوسائط التي تبثها."</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* سيتم إطفاء هذه الشاشة."</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"جهاز قابل للطي يجري فتحه"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"جهاز قابل للطي يجري قلبه"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"النسبة المئوية المتبقية من شحن البطارية: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"عليك توصيل قلم الشاشة بشاحن."</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"بطارية قلم الشاشة منخفضة"</string> + <string name="video_camera" msgid="7654002575156149298">"كاميرا فيديو"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"لا يمكن الاتصال باستخدام هذا الملف الشخصي."</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"تسمح لك سياسة العمل بإجراء المكالمات الهاتفية من الملف الشخصي للعمل فقط."</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"التبديل إلى الملف الشخصي للعمل"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"إغلاق"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index e376a6c14bbb..9e08a8160a60 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"তলৰ সীমা <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"বাওঁফালৰ সীমা <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"সোঁফালৰ সীমা <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"<xliff:g id="APP">%1$s</xliff:g> এপ্টোত কৰ্মস্থানৰ স্ক্ৰীনশ্বটসমূহ ছেভ কৰা হয়"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ফাইল"</string> <string name="screenrecord_name" msgid="2596401223859996572">"স্ক্ৰীন ৰেকৰ্ডাৰ"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"স্ক্রীন ৰেকৰ্ডিঙৰ প্ৰক্ৰিয়াকৰণ হৈ আছে"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রীন ৰেকৰ্ডিং ছেশ্বন চলি থকা সময়ত পোৱা জাননী"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"স্বয়ংক্ৰিয়"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"কোনো ধ্বনি অথবা কম্পন নাই"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"কোনো ধ্বনি অথবা কম্পন নাই আৰু বাৰ্তালাপ শাখাটোৰ তলৰ অংশত দেখা পোৱা যায়"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ফ’নৰ ছেটিঙৰ ওপৰত নিৰ্ভৰ কৰি ৰিং কৰিব অথবা কম্পন হ’ব পাৰে"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ফ’নৰ ছেটিঙৰ ওপৰত নিৰ্ভৰ কৰি ৰিং কৰিব অথবা কম্পন হ’ব পাৰে। <xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাৰ্তালাপ ডিফ’ল্ট হিচাপে বাবল হয়।"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"ডিভাইচৰ ছেটিঙৰ ওপৰত নিৰ্ভৰ কৰি ৰিং কৰিব অথবা কম্পন হ’ব পাৰে"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ডিভাইচৰ ছেটিঙৰ ওপৰত নিৰ্ভৰ কৰি ৰিং কৰিব অথবা কম্পন হ’ব পাৰে। <xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাৰ্তালাপ ডিফ’ল্টভাৱে বাবল হিচাপে প্ৰদৰ্শিত হয়।"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"এই জাননীটোৱে ধ্বনি নে কম্পন সৃষ্টি কৰিব সেয়া ছিষ্টেমটোক নিৰ্ধাৰণ কৰিবলৈ দিয়ক"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>স্থিতি:</b> ডিফ’ল্টলৈ বৃদ্ধি কৰা হৈছে"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>স্থিতি:</b> নীৰৱলৈ হ্ৰাস কৰা হৈছে"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"নিয়ন্ত্ৰণসমূহ যোগ কৰিবলৈ এপ্ বাছনি কৰক"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# টা নিয়ন্ত্ৰণ যোগ দিয়া হৈছে।}one{# টা নিয়ন্ত্ৰণ যোগ দিয়া হৈছে।}other{# টা নিয়ন্ত্ৰণ যোগ দিয়া হৈছে।}}"</string> <string name="controls_removed" msgid="3731789252222856959">"আঁতৰোৱা হ’ল"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"প্ৰিয় হিচাপে চিহ্নিত কৰা হ’ল"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"প্ৰিয় হিচাপে চিহ্নিত কৰা হ’ল, স্থান <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"অপ্ৰিয় হিচাপে চিহ্নিত কৰা হ’ল"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ত <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"আনডু কৰক"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে’ কৰিবলৈ ওচৰলৈ যাওক"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> - <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে কৰি থকা হৈছে"</string> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ইয়াত প্লে’ কৰিবলৈ, <xliff:g id="DEVICENAME">%1$s</xliff:g>ৰ ওচৰলৈ যাওক"</string> + <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে\' কৰি থকা হৈছে"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"কিবা ভুল হ’ল। পুনৰ চেষ্টা কৰক।"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"ল’ড হৈ আছে"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"টেবলেট"</string> <string name="controls_error_timeout" msgid="794197289772728958">"সক্ৰিয় নহয়, এপ্টো পৰীক্ষা কৰক"</string> <string name="controls_error_removed" msgid="6675638069846014366">"বিচাৰি পোৱা নগ’ল"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"নিয়ন্ত্ৰণটো উপলব্ধ নহয়"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"আসোঁৱাহ হৈছে, আকৌ চেষ্টা কৰক"</string> <string name="controls_menu_add" msgid="4447246119229920050">"নিয়ন্ত্ৰণসমূহ যোগ দিয়ক"</string> <string name="controls_menu_edit" msgid="890623986951347062">"নিয়ন্ত্ৰণসমূহ সম্পাদনা কৰক"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"আউটপুটসমূহ যোগ দিয়ক"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"গোট"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"১ টা ডিভাইচ বাছনি কৰা হৈছে"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ভলিউম"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"স্পীকাৰ আৰু ডিছপ্লে’"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"পৰামৰ্শ হিচাপে পোৱা ডিভাইচ"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"সম্প্ৰচাৰ কৰাটোৱে কেনেকৈ কাম কৰে"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"সম্প্ৰচাৰ"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"সমিল ব্লুটুথ ডিভাইচৰ সৈতে আপোনাৰ নিকটৱৰ্তী স্থানত থকা লোকসকলে আপুনি সম্প্ৰচাৰ কৰা মিডিয়াটো শুনিব পাৰে"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ই স্ক্ৰীনখন অফ হ’ব"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"জপাব পৰা ডিভাইচৰ জাপ খুলি থকা হৈছে"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"জপাব পৰা ডিভাইচৰ ওলোটাই থকা হৈছে"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> বেটাৰী বাকী আছে"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"আপোনাৰ ষ্টাইলাছ এটা চাৰ্জাৰৰ সৈতে সংযোগ কৰক"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"ষ্টাইলাছৰ বেটাৰী কম আছে"</string> + <string name="video_camera" msgid="7654002575156149298">"ভিডিঅ’ কেমেৰা"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"এই প্ৰ’ফাইলৰ পৰা কল কৰিব নোৱাৰি"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"আপোনাৰ কৰ্মস্থানৰ নীতিয়ে আপোনাক কেৱল কৰ্মস্থানৰ প্ৰ’ফাইলৰ পৰা ফ’ন কল কৰিবলৈ দিয়ে"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"কৰ্মস্থানৰ প্ৰ’ফাইললৈ সলনি কৰক"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"বন্ধ কৰক"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index a2f20cea73fc..73149e876544 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Aşağı sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sol sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sağ sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"İş skrinşotları <xliff:g id="APP">%1$s</xliff:g> tətbiqində saxlanılır"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fayllar"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Ekran Yazıcısı"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekran çəkilişi emal edilir"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekranın video çəkimi ərzində silinməyən bildiriş"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Avtomatik"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Səs və ya vibrasiya yoxdur"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Söhbət siyahısının aşağısında səssiz və vibrasiyasız görünür"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Telefon ayarlarına əsasən zəng çala və ya titrəyə bilər"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Telefon ayarlarına əsasən zəng çala və ya vibrasiya edə bilər. <xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqindən söhbətlərdə defolt olaraq qabarcıq çıxır."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Cihaz ayarlarına əsasən zəng çala və ya vibrasiya edə bilər"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Cihaz ayarlarına əsasən zəng çala və ya vibrasiya edə bilər. <xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqindən söhbətlərdə defolt olaraq qabarcıq çıxır."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Bu bildirişin səs çıxarması və ya vibrasiya etməsi sistem tərəfindən təyin edilsin"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Defolt ayara keçirilib"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Səssiz rejimə keçirilib"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Kontrol əlavə etmək üçün tətbiq seçin"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# nizamlayıcı əlavə edilib.}other{# nizamlayıcı əlavə edilib.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Silinib"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> əlavə edilsin?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> əlavə etdiyiniz zaman, o, bu panelə nizamlayıcılar və məzmun əlavə edə bilər. Bəzi tətbiqlərdə burada hansı nizamlayıcıların göstərilməsini seçə bilərsiniz."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Sevimlilərə əlavə edilib"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Sevimlilərə əlavə edilib, sıra: <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Sevimlilərdən silinib"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%2$s</xliff:g> tətbiqindən oxudun"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Geri qaytarın"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxutmaq üçün yaxınlaşın"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Burada oxutmaq üçün <xliff:g id="DEVICENAME">%1$s</xliff:g> cihazına yaxınlaşdırın"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxudulur"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Xəta oldu. Yenə cəhd edin."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Yüklənir"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"planşet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Aktiv deyil, tətbiqi yoxlayın"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tapılmadı"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Nəzarət əlçatan deyil"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Xəta, yenidən cəhd edin"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Vidcet əlavə edin"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Vidcetlərə düzəliş edin"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Tətbiq əlavə edin"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Nəticələri əlavə edin"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Qrup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçilib"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Səs"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Dinamiklər & Displeylər"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Təklif olunan Cihazlar"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Yayım necə işləyir"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Yayım"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Uyğun Bluetooth cihazları olan yaxınlığınızdakı insanlar yayımladığınız medianı dinləyə bilər"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Bu ekran deaktiv ediləcək"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Qatlana bilən cihaz açılır"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Qatlana bilən cihaz fırladılır"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> enerji qalıb"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Qələmi adapterə qoşun"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Qələm enerjisi azdır"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Bu profildən zəng etmək mümkün deyil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"İş siyasətiniz yalnız iş profilindən telefon zəngləri etməyə imkan verir"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"İş profilinə keçin"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Bağlayın"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 19209870ac4d..89d72a15e9ea 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Donja ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Leva ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desna ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Snimci ekrana za posao se čuvaju u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fajlovi"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Snimač ekrana"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrađujemo video snimka ekrana"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Obaveštenje o sesiji snimanja ekrana je aktivno"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatska"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Bez zvuka i vibriranja"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Bez zvuka i vibriranja i prikazuje se u nastavku odeljka za konverzacije"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Može da zvoni ili vibrira u zavisnosti od podešavanja telefona"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Može da zvoni ili vibrira u zavisnosti od podešavanja telefona. Konverzacije iz aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g> se podrazumevano prikazuju u oblačićima."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Može da zvoni ili vibrira u zavisnosti od podešavanja uređaja"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Može da zvoni ili vibrira u zavisnosti od podešavanja uređaja. Konverzacije iz aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g> podrazumevano se prikazuju u oblačićima."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Neka sistem utvrdi da li ovo obaveštenje treba da emituje zvuk ili da vibrira"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Unapređeno u Podrazumevano"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Degradirano u Nečujno"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Odaberite aplikaciju za dodavanje kontrola"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrola je dodata.}one{# kontrola je dodata.}few{# kontrole su dodate.}other{# kontrola je dodato.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Uklonjeno"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Želite li da dodate <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Kada dodate aplikaciju <xliff:g id="APPNAME">%s</xliff:g>, ona može da dodaje kontrole i sadržaj u ovo okno. U nekim aplikacijama možete da izaberete koje će se kontrole ovde prikazivati."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Označeno je kao omiljeno"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Označeno je kao omiljeno, <xliff:g id="NUMBER">%d</xliff:g>. pozicija"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Uklonjeno je iz omiljenih"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Opozovi"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite da biste puštali muziku na: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Da biste puštali sadržaj ovde, približite uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Pušta se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Došlo je do greške. Probajte ponovo."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Učitava se"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno. Vidite aplikaciju"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Greška. Probajte ponovo"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Izmeni kontrole"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Dodaj aplikaciju"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodajte izlaze"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izabran je 1 uređaj"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Zvuk"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Zvučnici i ekrani"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Predloženi uređaji"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako funkcioniše emitovanje"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Emitovanje"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Ljudi u blizini sa kompatibilnim Bluetooth uređajima mogu da slušaju medijski sadržaj koji emitujete"</string> @@ -1016,15 +1014,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• da je dostupan barem jedan uređaj"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Dodirnite i zadržite prečicu"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Otkaži"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Obrnite"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Obrni"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Otvorite telefon za bolji selfi"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Želite da obrnete na prednji ekran za bolji selfi?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Koristite zadnju kameru da biste snimili širu sliku sa višom rezolucijom."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ovaj ekran će se isključiti"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Uređaj na preklop se otvara"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Uređaj na preklop se obrće"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo je još<xliff:g id="PERCENTAGE">%s</xliff:g> baterije"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Povežite pisaljku sa punjačem"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Nizak nivo baterije pisaljke"</string> + <string name="video_camera" msgid="7654002575156149298">"Video kamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Ne možete da upućujete pozive sa ovog profila"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Smernice za posao vam omogućavaju da telefonirate samo sa poslovnog profila"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Pređi na poslovni profil"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zatvori"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 537622e07e2a..5bbe00111f3a 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ніжняя граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Левая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Правая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Працоўныя здымкі экрана захаваны ў праграме \"<xliff:g id="APP">%1$s</xliff:g>\""</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлы"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Запіс экрана"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Апрацоўваецца запіс экрана"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Бягучае апавяшчэнне для сеанса запісу экрана"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Аўтаматычна"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без гуку ці вібрацыі"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Паказваецца без гуку ці вібрацыі ў раздзеле размоў"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"У залежнасці ад налад тэлефона магчымы званок або вібрацыя"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"У залежнасці ад налад тэлефона магчымы званок або вібрацыя. Размовы ў праграме \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" стандартна паяўляюцца ў выглядзе ўсплывальных апавяшчэнняў."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"У залежнасці ад налад прылады магчымы званок або вібрацыя"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"У залежнасці ад налад прылады магчымы званок або вібрацыя. Размовы ў праграме \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" стандартна паяўляюцца ў выглядзе ўсплывальных апавяшчэнняў."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Сістэма сама будзе вызначаць, ці трэба для гэтага апавяшчэння ўключаць гук або вібрацыю"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Стан:</b> Пазначана як стандартнае"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Стан:</b> Пераведзена ў рэжым \"Без гуку\""</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Выберыце праграму для дадавання элементаў кіравання"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Дададзены # элемент кіравання.}one{Дададзена # элемента кіравання.}few{Дададзена # элементы кіравання.}many{Дададзена # элементаў кіравання.}other{Дададзена # элемента кіравання.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Выдалена"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Дададзена ў абранае"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Дададзена ў абранае, пазіцыя <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Выдалена з абранага"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" з дапамогай праграмы \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Адрабіць"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Каб прайграць мультымедыя на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", наблізьцеся да яе"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Каб прайграць тут, падыдзіце бліжэй да прылады \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Прайграецца на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Нешта пайшло не так. Паўтарыце спробу."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Ідзе загрузка"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшэт"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактыўна, праверце праграму"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не знойдзена"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Кіраванне недаступнае"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Памылка, паўтарыце спробу"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Дадаць элементы кіравання"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Змяніць элементы кіравання"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Дадайце прылады вываду"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрана 1 прылада"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Гучнасць"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Дынамікі і дысплэі"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Прылады, якія падтрымліваюцца"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Як адбываецца трансляцыя"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Трансляцыя"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Людзі паблізу, у якіх ёсць прылады з Bluetooth, змогуць праслухваць мультымедыйнае змесціва, якое вы трансліруеце"</string> @@ -1018,13 +1019,19 @@ <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Скасаваць"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Пераключыць"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Каб атрымаць лепшае сэлфі, раскрыйце тэлефон"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Пераключыць на пярэднюю камеру для лепшага сэлфі?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Пераключыць на пярэдні дысплэй для лепшага сэлфі?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Каб зрабіць шырэйшае фота з больш высокай раздзяляльнасцю, скарыстайце заднюю камеру."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Гэты экран будзе выключаны"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складная прылада ў раскладзеным выглядзе"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перавернутая складная прылада"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Засталося зараду: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Падключыце пяро да зараднай прылады"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Нізкі ўзровень зараду пяра"</string> + <string name="video_camera" msgid="7654002575156149298">"Відэакамера"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Не ўдалося зрабіць выклік з гэтага профілю"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Згодна з палітыкай вашай арганізацыі, рабіць тэлефонныя выклікі дазволена толькі з працоўнага профілю"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Пераключыцца на працоўны профіль"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Закрыць"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index eba0d0333033..72c6b9de00b0 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Долна граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лява граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Дясна граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Екранните снимки, направени в служебния потребителски профил, се запазват в приложението „<xliff:g id="APP">%1$s</xliff:g>“"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Запис на екрана"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Записът на екрана се обработва"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущо известие за сесия за записване на екрана"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматично"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звук или вибриране"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звук или вибриране и се показва по-долу в секцията с разговори"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Може да звъни или да вибрира въз основа на настройките за телефона"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Може да звъни или да вибрира според настройките за телефона. Разговорите от <xliff:g id="APP_NAME">%1$s</xliff:g> се показват като балончета по подразбиране."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Може да звъни или да вибрира въз основа на настройките на устройството"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Може да звъни или да вибрира въз основа на настройките на устройството. Разговорите от <xliff:g id="APP_NAME">%1$s</xliff:g> се показват като балончета по подразбиране."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Нека системата да определя дали дадено известие да се придружава от звук, или вибриране"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Състояние:</b> Повишено до основно"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Състояние:</b> Понижено до беззвучно"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Изберете приложение, за да добавите контроли"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Добавена е # контрола.}other{Добавени са # контроли.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Премахнато"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Означено като любимо"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Означено като любимо – позиция <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Не е означено като любимо"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> от <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Отмяна"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Преместете се по-близо, за да се възпроизведе на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"За възпроизвеждане тук се приближете до <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Възпроизвежда се на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Нещо се обърка. Опитайте отново."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Зарежда се"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, проверете прилож."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не е намерено"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Контролата не е налице"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Грешка. Опитайте отново"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Добавяне на контроли"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Редактиране на контролите"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Добавяне на изходящи устройства"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 избрано устройство"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Сила на звука"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Високоговорители и екрани"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Предложени устройства"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Как работи предаването"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Предаване"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Хората в близост със съвместими устройства с Bluetooth могат да слушат мултимедията, която предавате"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Този екран ще се изключи"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Разгъване на сгъваемо устройство"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Обръщане на сгъваемо устройство"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Оставаща батерия: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Свържете писалката към зарядно устройство"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Батерията на писалката е изтощена"</string> + <string name="video_camera" msgid="7654002575156149298">"Видеокамера"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Не може да се извърши обаждане от този потребителски профил"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Служебните правила ви дават възможност да извършвате телефонни обаждания само от служебния потребителски профил"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Превключване към служебния потребителски профил"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Затваряне"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 13353aa1a0be..598db2c0f341 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"অটোমেটিক"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"আওয়াজ করবে না বা ভাইব্রেট হবে না"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"আওয়াজ করবে না বা ভাইব্রেট হবে না এবং কথোপকথন বিভাগের নিচের দিকে দেখা যাবে"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ফোনের সেটিংস অনুযায়ী ফোন রিং বা ভাইব্রেট হতে পারে"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ফোনের সেটিংস অনুযায়ী ফোন রিং বা ভাইব্রেট হতে পারে। <xliff:g id="APP_NAME">%1$s</xliff:g>-এর কথোপকথন সাধারণত বাবলের মতো দেখাবে।"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"ডিভাইসের সেটিংস অনুযায়ী রিং বা ভাইব্রেট হতে পারে"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ডিভাইসের সেটিংস অনুযায়ী রিং বা ভাইব্রেট হতে পারে। <xliff:g id="APP_NAME">%1$s</xliff:g>-এর কথোপকথন সাধারণত বাবলের মতো দেখাবে।"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"এই বিজ্ঞপ্তি এলে ডিভাইস আওয়াজ করবে না ভাইব্রেট করবে তা সিস্টেমকে সেট করতে দিন"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>স্ট্যাটাস:</b> লেভেল বাড়িয়ে ডিফল্ট করা হয়েছে"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>স্ট্যাটাস:</b> লেভেল কমিয়ে সাইলেন্ করা হয়েছে"</string> @@ -800,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"কন্ট্রোল যোগ করতে অ্যাপ বেছে নিন"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{#টি কন্ট্রোল যোগ করা হয়েছে।}one{#টি কন্ট্রোল যোগ করা হয়েছে।}other{#টি কন্ট্রোল যোগ করা হয়েছে।}}"</string> <string name="controls_removed" msgid="3731789252222856959">"সরানো হয়েছে"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"পছন্দসই হিসেবে চিহ্নিত করেছেন"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"পছন্দসই হিসেবে চিহ্নিত করেছেন, অবস্থান <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"পছন্দসই থেকে সরিয়ে দিয়েছেন"</string> @@ -852,8 +856,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%2$s</xliff:g> অ্যাপে চালান"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"আগের অবস্থায় ফিরুন"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ চালাতে আরও কাছে আনুন"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"এখানে চালানোর জন্য আপনার ডিভাইস <xliff:g id="DEVICENAME">%1$s</xliff:g>-এর কাছে নিয়ে যান"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ ভিডিও চালানো হচ্ছে"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"কোনও সমস্যা হয়েছে। আবার চেষ্টা করুন।"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"লোড করা হচ্ছে"</string> @@ -867,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"সমস্যা হয়েছে, আবার চেষ্টা করুন"</string> <string name="controls_menu_add" msgid="4447246119229920050">"কন্ট্রোল যোগ করুন"</string> <string name="controls_menu_edit" msgid="890623986951347062">"কন্ট্রোল এডিট করুন"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"আউটপুট যোগ করুন"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"গ্রুপ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"১টি ডিভাইস বেছে নেওয়া হয়েছে"</string> @@ -1019,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ এই স্ক্রিন বন্ধ হয়ে যাবে"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ফোল্ড করা যায় এমন ডিভাইস খোলা হচ্ছে"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ফোল্ড করা যায় এমন ডিভাইস উল্টানো হচ্ছে"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ব্যাটারির চার্জ বাকি আছে"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"কোনও চার্জারের সাথে আপনার স্টাইলাস কানেক্ট করুন"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"স্টাইলাস ব্যাটারিতে চার্জ কম আছে"</string> + <string name="video_camera" msgid="7654002575156149298">"ভিডিও ক্যামেরা"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"এই প্রোফাইল থেকে কল করা যাচ্ছে না"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"কাজ সংক্রান্ত নীতি, আপনাকে শুধুমাত্র অফিস প্রোফাইল থেকে কল করার অনুমতি দেয়"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"অফিস প্রোফাইলে পাল্টে নিন"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"বন্ধ করুন"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 2475a477c15d..88ce4db6d50f 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatski"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Bez zvuka ili vibracije"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Bez zvuka ili vibracije i pojavljuje se pri dnu odjeljka razgovora"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Može zvoniti ili vibrirati na osnovu postavki telefona"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Može zvoniti ili vibrirati na osnovu postavki telefona. Razgovori iz oblačića u aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> kao zadana opcija."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Može zvoniti ili vibrirati na osnovu postavki uređaja"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Može zvoniti ili vibrirati na osnovu postavki uređaja. Razgovori iz aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g> prikazuju se u oblačićima prema zadanim postavkama."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Neka sistem odluči treba li se ovo obavještenje oglasiti zvukom ili vibracijom"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> je unaprijeđen u Zadano"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> je unazađen u Nečujno"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Odaberite aplikaciju da dodate kontrole"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Dodana je # kontrola.}one{Dodana je # kontrola.}few{Dodane su # kontrole.}other{Dodano je # kontrola.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Uklonjeno"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Dodati aplikaciju <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Kada dodate aplikaciju <xliff:g id="APPNAME">%s</xliff:g>, ona može dodavati kontrole i sadržaj na ovu ploču. U nekim aplikacijama možete odabrati koje kontrole se prikazuju ovdje."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Dodano u omiljeno"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Dodano u omiljeno, pozicija <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Uklonjeno iz omiljenog"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite da reproducirate na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Da reproducirate ovdje, približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Nešto nije uredu. Pokušajte ponovo."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Učitavanje"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Greška, pokušajte ponovo"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Uredi kontrole"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Dodaj aplikaciju"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodajte izlaze"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Odabran je 1 uređaj"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ekran će se isključiti"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Sklopivi uređaj se rasklapa"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Sklopivi uređaj se obrće"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo baterije: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Priključite pisaljku na punjač"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Baterija pisaljke je slaba"</string> + <string name="video_camera" msgid="7654002575156149298">"Video kamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nije moguće pozvati s ovog profila"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Radna pravila vam dozvoljavaju upućivanje telefonskih poziva samo s radnog profila"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Pređite na radni profil"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zatvori"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index ef732184a9f6..953cc3578636 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Marge inferior <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Marge esquerre <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Marge dret <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Les captures de pantalla de treball es desen a l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fitxers"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Gravació de pantalla"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processant gravació de pantalla"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificació en curs d\'una sessió de gravació de la pantalla"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automàtic"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sense so ni vibració"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sense so ni vibració i es mostra més avall a la secció de converses"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Pot sonar o vibrar en funció de la configuració del telèfon"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pot sonar o vibrar en funció de la configuració del telèfon. Les converses de l\'aplicació <xliff:g id="APP_NAME">%1$s</xliff:g> es mostren com a bombolles de manera predeterminada."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Pot sonar o vibrar en funció de la configuració del dispositiu"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Pot sonar o vibrar en funció de la configuració del dispositiu. Les converses de l\'aplicació <xliff:g id="APP_NAME">%1$s</xliff:g> es mostren com a bombolles de manera predeterminada."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Fes que el sistema determini si aquesta notificació ha d\'emetre un so o una vibració"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Estat</b>: s\'ha augmentat a Predeterminat"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Estat</b>: s\'ha disminuït a Silenci"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Selecciona l\'aplicació per afegir controls"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{S\'ha afegit # control.}many{# controls added.}other{S\'han afegit # controls.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Suprimit"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Afegit als preferits"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Afegit als preferits, posició <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Suprimit dels preferits"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> des de l\'aplicació <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfés"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Mou més a prop per reproduir a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Per reproduir contingut aquí, apropa\'l a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"S\'està reproduint a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"S\'ha produït un error. Torna-ho a provar."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"S\'està carregant"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tauleta"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactiu; comprova l\'aplicació"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No s\'ha trobat"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"El control no està disponible"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Error; torna-ho a provar"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Afegeix controls"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Edita els controls"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Afegeix sortides"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositiu seleccionat"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volum"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altaveus i pantalles"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositius suggerits"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Com funciona l\'emissió"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Emet"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les persones properes amb dispositius Bluetooth compatibles poden escoltar el contingut multimèdia que emets"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Aquesta pantalla s\'apagarà"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositiu plegable desplegant-se"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositiu plegable girant"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Queda un <xliff:g id="PERCENTAGE">%s</xliff:g> de bateria"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connecta el llapis òptic a un carregador"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria del llapis òptic baixa"</string> + <string name="video_camera" msgid="7654002575156149298">"Càmera de vídeo"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"No es pot trucar des d\'aquest perfil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"La teva política de treball et permet fer trucades només des del perfil de treball"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Canvia al perfil de treball"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Tanca"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 3c8c6da4ff5c..7c70cd86365c 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Dolní okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Levý okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Pravý okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Pracovní snímky obrazovky se ukládají do aplikace <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Soubory"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Rekordér obrazovky"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Záznam obrazovky se zpracovává"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Trvalé oznámení o relaci nahrávání"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automaticky"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Žádný zvuk ani vibrace"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Žádný zvuk ani vibrace a zobrazuje se níže v sekci konverzací"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Vyzvání nebo vibruje podle nastavení telefonu"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Vyzvání nebo vibruje podle nastavení telefonu. Konverzace z aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> mají ve výchozím nastavení podobu bublin."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Vyzvání nebo vibruje podle nastavení zařízení"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Vyzvání nebo vibruje podle nastavení zařízení. Konverzace z aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> ve výchozím nastavení bublají."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Nechat systém rozhodnout, zda má toto oznámení vydat zvuk či zavibrovat"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Stav:</b> priorita zvýšena na Výchozí"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Stav:</b> priorita snížena na Tiché"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikaci, pro kterou chcete přidat ovládací prvky"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Byl přidán # ovládací prvek.}few{Byly přidány # ovládací prvky.}many{Bylo přidáno # ovládacího prvku.}other{Bylo přidáno # ovládacích prvků.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Odstraněno"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Přidáno do oblíbených"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Přidáno do oblíbených na pozici <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Odebráno z oblíbených"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Vrátit zpět"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Pokud chcete přehrávat na zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>, přibližte se k němu"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pokud obsah chcete přehrát na tomto zařízení, přesuňte ho blíže k zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Přehrávání v zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Došlo k chybě. Zkuste to znovu."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Načítání"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivní, zkontrolujte aplikaci"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nenalezeno"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Ovládání není k dispozici"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Chyba, zkuste to znovu"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Přidat ovládací prvky"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Upravit ovládací prvky"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Přidání výstupů"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Je vybráno 1 zařízení"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hlasitost"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Reproduktory a displeje"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Navrhovaná zařízení"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jak vysílání funguje"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Vysílání"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Lidé ve vašem okolí s kompatibilními zařízeními Bluetooth mohou poslouchat média, která vysíláte"</string> @@ -1018,13 +1019,19 @@ <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Zrušit"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Otočit"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Rozložte telefon, selfie bude lepší"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Otočit na přední fotoaparát pro lepší selfie?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Otočit na přední displej pro lepší selfie?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Pomocí zadního fotoaparátu pořiďte širší fotku s vyšším rozlišením."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Tato obrazovka se vypne"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozkládání rozkládacího zařízení"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Otáčení rozkládacího zařízení"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Zbývá <xliff:g id="PERCENTAGE">%s</xliff:g> baterie"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Připojte dotykové pero k nabíječce"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Slabá baterie dotykového pera"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Z tohoto profilu nelze volat"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Vaše pracovní zásady vám umožňují telefonovat pouze z pracovního profilu"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Přepnout na pracovní profil"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zavřít"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 585d85838e81..7c4e92c3821a 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Nederste kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Venstre kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Højre kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Screenshots, der tages via arbejdsprofilen, gemmer i appen <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Skærmoptagelse"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Behandler skærmoptagelse"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Konstant notifikation om skærmoptagelse"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatisk"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ingen lyd eller vibration"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ingen lyd eller vibration, og den vises længere nede i samtalesektionen"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan ringe eller vibrere baseret på telefonens indstillinger"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan ringe eller vibrere baseret på telefonens indstillinger. Samtaler fra <xliff:g id="APP_NAME">%1$s</xliff:g> vises som standard i bobler."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Kan ringe eller vibrere baseret på enhedens indstillinger"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kan ringe eller vibrere baseret på enhedens indstillinger. Samtaler fra <xliff:g id="APP_NAME">%1$s</xliff:g> vises som standard i bobler."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Få systemet til at afgøre, om denne notifikation skal vibrere eller afspille en lyd"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Angivet som Standard"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Angivet som Lydløs"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Vælg en app for at tilføje styring"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# styringselement er tilføjet.}one{# styringselement er tilføjet.}other{# styringselementer er tilføjet.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Fjernet"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Angivet som favorit"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Angivet som favorit. Position <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Fjernet fra favoritter"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Fortryd"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ryk tættere på for at afspille på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"For at afspille her skal enheden tættere på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Afspilles på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Noget gik galt. Prøv igen."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Indlæser"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Tjek appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ikke fundet"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Styringselement ikke tilgængeligt"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Der opstod en fejl. Prøv igen"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Tilføj styring"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Rediger styring"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tilføj medieudgange"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Der er valgt 1 enhed"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Lydstyrke"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Højttalere og skærme"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Foreslåede enheder"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Sådan fungerer udsendelser"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Udsendelse"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personer i nærheden, som har kompatible Bluetooth-enheder, kan lytte til det medie, du udsender"</string> @@ -1020,11 +1021,17 @@ <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Fold telefonen ud for at tage en bedre selfie"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Vil du bruge frontkameraet for at få bedre selfie?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Brug bagsidekameraet for at få et bredere billede med højere opløsning."</string> - <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ *Denne skærm slukkes"</b></string> + <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Denne skærm slukkes"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldbar enhed foldes ud"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldbar enhed vendes om"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteri tilbage"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Slut din styluspen til en oplader"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Lavt batteriniveau på styluspen"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Du kan ikke ringe fra denne profil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Din arbejdspolitik tillader kun, at du kan foretage telefonopkald fra arbejdsprofilen"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Skift til arbejdsprofil"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Luk"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 078c02d2b259..2b975bc78236 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Unterer Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linker Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Rechter Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Mit einem Arbeitsprofil aufgenommene Screenshots werden in der App „<xliff:g id="APP">%1$s</xliff:g>“ gespeichert"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Dateien"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Bildschirmaufzeichnung"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Bildschirmaufzeichnung…"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Fortlaufende Benachrichtigung für eine Bildschirmaufzeichnung"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatisch"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Kein Ton und keine Vibration"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Kein Ton und keine Vibration, erscheint weiter unten im Bereich „Unterhaltungen“"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kann klingeln oder vibrieren, abhängig von den Telefoneinstellungen"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kann klingeln oder vibrieren, je nach Telefoneinstellungen. Unterhaltungen von <xliff:g id="APP_NAME">%1$s</xliff:g> werden standardmäßig als Bubble angezeigt."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Kann je nach Geräteeinstellungen klingeln oder vibrieren"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kann je nach Geräteeinstellungen klingeln oder vibrieren. Unterhaltungen von <xliff:g id="APP_NAME">%1$s</xliff:g> werden standardmäßig als Bubble angezeigt."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Das System entscheiden lassen, ob bei dieser Benachrichtigung ein Ton oder eine Vibration ausgegeben wird"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status</b>: auf „Standard“ hochgestuft"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status</b>: auf „Lautlos“ herabgestuft"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"App zum Hinzufügen von Steuerelementen auswählen"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# Steuerelement hinzugefügt.}other{# Steuerelemente hinzugefügt.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Entfernt"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Zu Favoriten hinzugefügt"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Zu Favoriten hinzugefügt, Position <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Aus Favoriten entfernt"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> über <xliff:g id="APP_LABEL">%2$s</xliff:g> wiedergeben"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Rückgängig machen"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Gehe für die Wiedergabe näher an „<xliff:g id="DEVICENAME">%1$s</xliff:g>“ heran"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Für eine Wiedergabe auf diesem Gerät muss es näher bei <xliff:g id="DEVICENAME">%1$s</xliff:g> sein"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Wiedergabe läuft auf „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string> - <string name="media_transfer_failed" msgid="7955354964610603723">"Es gab ein Problem. Versuch es noch einmal."</string> + <string name="media_transfer_failed" msgid="7955354964610603723">"Ein Fehler ist aufgetreten. Versuch es noch einmal."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Wird geladen"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"Tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv – sieh in der App nach"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nicht gefunden"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Steuerelement nicht verfügbar"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Fehler – versuch es noch mal"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Steuerelemente hinzufügen"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Steuerelemente bearbeiten"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ausgabegeräte hinzufügen"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ein Gerät ausgewählt"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Lautstärke"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Lautsprecher & Displays"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Vorgeschlagene Geräte"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Funktionsweise von Nachrichten an alle"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Nachricht an alle"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personen, die in der Nähe sind und kompatible Bluetooth-Geräten haben, können sich die Medien anhören, die du per Nachricht an alle sendest"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Dieses Display wird dann ausgeschaltet"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Faltbares Gerät wird geöffnet"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Faltbares Gerät wird umgeklappt"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akku bei <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Schließe deinen Eingabestift an ein Ladegerät an"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus-Akkustand niedrig"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Keine Anrufe über dieses Profil möglich"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Gemäß den Arbeitsrichtlinien darfst du nur über dein Arbeitsprofil telefonieren"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Zum Arbeitsprofil wechseln"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Schließen"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index fce50e56cb30..47bcfcbbfe3c 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Αυτόματο"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Χωρίς ήχο ή δόνηση"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Χωρίς ήχο ή δόνηση και εμφανίζεται χαμηλά στην ενότητα συζητήσεων"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Ενδέχεται να κουδουνίζει ή να δονείται βάσει των ρυθμίσεων του τηλεφώνου"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Ενδέχεται να κουδουνίζει ή να δονείται βάσει των ρυθμίσεων του τηλεφώνου. Οι συζητήσεις από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> εμφανίζονται σε συννεφάκι από προεπιλογή."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Ενδέχεται να κουδουνίζει ή να δονείται βάσει των ρυθμίσεων συσκευής"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Ενδέχεται να κουδουνίζει ή να δονείται βάσει των ρυθμίσεων συσκευής. Οι συζητήσεις από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> εμφανίζονται σε συννεφάκι από προεπιλογή."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Επιτρέψτε στο σύστημα να αποφασίσει αν αυτή η ειδοποίηση θα αναπαράγει έναν ήχο ή θα ενεργοποιήσει τη δόνηση της συσκευής"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Κατάσταση:</b> Προάχθηκε σε Προεπιλογή"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Κατάσταση:</b> Υποβιβάστηκε σε Αθόρυβη"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Επιλογή εφαρμογής για προσθήκη στοιχείων ελέγχου"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Προστέθηκε # στοιχείο ελέγχου.}other{Προστέθηκαν # στοιχεία ελέγχου.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Καταργήθηκε"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Προσθήκη <xliff:g id="APPNAME">%s</xliff:g>;"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Όταν προσθέσετε την εφαρμογή <xliff:g id="APPNAME">%s</xliff:g>, μπορεί να προσθέσει στοιχεία ελέγχου και περιεχόμενο σε αυτό το πλαίσιο. Σε ορισμένες εφαρμογές, μπορείτε να επιλέξετε ποια στοιχεία ελέγχου θα εμφανίζονται εδώ."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Προστέθηκε στα αγαπημένα"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Προστέθηκε στα αγαπημένα, στη θέση <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Αφαιρέθηκε από τα αγαπημένα"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Αναίρεση"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Πλησιάστε για αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Για να γίνει αναπαραγωγή εδώ, μετακινηθείτε πιο κοντά στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Παρουσιάστηκε κάποιο πρόβλημα. Δοκιμάστε ξανά."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Φόρτωση"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Σφάλμα, προσπαθήστε ξανά."</string> <string name="controls_menu_add" msgid="4447246119229920050">"Προσθήκη στοιχείων ελέγχου"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Επεξεργασία στοιχείων ελέγχου"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Προσθήκη εφαρμογής"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Προσθήκη εξόδων"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Ομάδα"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Επιλέχτηκε 1 συσκευή"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* Αυτή η οθόνη θα απενεργοποιηθεί"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Αναδιπλούμενη συσκευή που ξεδιπλώνει"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Αναδιπλούμενη συσκευή που διπλώνει"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Απομένει το <xliff:g id="PERCENTAGE">%s</xliff:g> της μπαταρίας"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Συνδέστε τη γραφίδα σε έναν φορτιστή"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Χαμηλή στάθμη μπαταρίας γραφίδας"</string> + <string name="video_camera" msgid="7654002575156149298">"Βιντεοκάμερα"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Δεν είναι δυνατή η κλήση από αυτό το προφίλ"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Η πολιτική εργασίας σάς επιτρέπει να πραγματοποιείτε τηλεφωνικές κλήσεις μόνο από το προφίλ εργασίας σας."</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Εναλλαγή σε προφίλ εργασίας"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Κλείσιμο"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index b0421c0818b2..2b5e8f987f7c 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatic"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"No sound or vibration"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No sound or vibration and appears lower in conversation section"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"May ring or vibrate based on device settings"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"May ring or vibrate based on device settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> promoted to default"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> demoted to silent"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Removed"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Add <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"When you add <xliff:g id="APPNAME">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favourited"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favourited, position <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavourited"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Add app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string> @@ -1021,4 +1023,11 @@ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string> <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string> <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string> + <string name="video_camera" msgid="7654002575156149298">"Video camera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Can\'t call from this profile"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Your work policy allows you to make phone calls only from the work profile"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Switch to work profile"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Close"</string> + <string name="lock_screen_settings" msgid="9197175446592718435">"Lock screen settings"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 5e564c365944..77452b5bf76c 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatic"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"No sound or vibration"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No sound or vibration and appears lower in conversation section"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"May ring or vibrate based on device settings"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"May ring or vibrate based on device settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Promoted to Default"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Demoted to Silent"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Removed"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Add <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"When you add <xliff:g id="APPNAME">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favorited"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favorited, position <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavorited"</string> @@ -866,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Add app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device selected"</string> @@ -1020,4 +1023,11 @@ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string> <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string> <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string> + <string name="video_camera" msgid="7654002575156149298">"Video camera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Can\'t call from this profile"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Your work policy allows you to make phone calls only from the work profile"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Switch to work profile"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Close"</string> + <string name="lock_screen_settings" msgid="9197175446592718435">"Lock screen settings"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index b0421c0818b2..2b5e8f987f7c 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatic"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"No sound or vibration"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No sound or vibration and appears lower in conversation section"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"May ring or vibrate based on device settings"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"May ring or vibrate based on device settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> promoted to default"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> demoted to silent"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Removed"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Add <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"When you add <xliff:g id="APPNAME">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favourited"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favourited, position <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavourited"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Add app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string> @@ -1021,4 +1023,11 @@ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string> <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string> <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string> + <string name="video_camera" msgid="7654002575156149298">"Video camera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Can\'t call from this profile"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Your work policy allows you to make phone calls only from the work profile"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Switch to work profile"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Close"</string> + <string name="lock_screen_settings" msgid="9197175446592718435">"Lock screen settings"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index b0421c0818b2..2b5e8f987f7c 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatic"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"No sound or vibration"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No sound or vibration and appears lower in conversation section"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"May ring or vibrate based on device settings"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"May ring or vibrate based on device settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> promoted to default"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> demoted to silent"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Removed"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Add <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"When you add <xliff:g id="APPNAME">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favourited"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favourited, position <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavourited"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Add app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string> @@ -1021,4 +1023,11 @@ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string> <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string> <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string> + <string name="video_camera" msgid="7654002575156149298">"Video camera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Can\'t call from this profile"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Your work policy allows you to make phone calls only from the work profile"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Switch to work profile"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Close"</string> + <string name="lock_screen_settings" msgid="9197175446592718435">"Lock screen settings"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 2ab012df6456..173b905704b6 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatic"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"No sound or vibration"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No sound or vibration and appears lower in conversation section"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"May ring or vibrate based on device settings"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"May ring or vibrate based on device settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Promoted to Default"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Demoted to Silent"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Removed"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Add <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"When you add <xliff:g id="APPNAME">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favorited"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favorited, position <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavorited"</string> @@ -866,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Add app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device selected"</string> @@ -1020,4 +1023,11 @@ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string> <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string> <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string> + <string name="video_camera" msgid="7654002575156149298">"Video camera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Can\'t call from this profile"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Your work policy allows you to make phone calls only from the work profile"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Switch to work profile"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Close"</string> + <string name="lock_screen_settings" msgid="9197175446592718435">"Lock screen settings"</string> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 3d6419dba9eb..e5e20ba11022 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Límite inferior: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Límite izquierdo: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Límite derecho: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Las capturas de pantalla de trabajo se guardan en la app de <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Grabadora de pantalla"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Procesando grabación pantalla"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación constante para una sesión de grabación de pantalla"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sin sonido ni vibración"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No suena ni vibra, y aparece en la parte inferior de la sección de conversaciones."</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Puede sonar o vibrar en función de la configuración del teléfono."</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Puede sonar o vibrar en función de la configuración del teléfono. Conversaciones de la burbuja de <xliff:g id="APP_NAME">%1$s</xliff:g> de forma predeterminada."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Puede sonar o vibrar según la configuración del dispositivo"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Puede sonar o vibrar según la configuración del dispositivo. Conversaciones de la burbuja de <xliff:g id="APP_NAME">%1$s</xliff:g> de forma predeterminada."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Dejar que el sistema determine si esta notificación debe emitir un sonido o una vibración"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Estado:</b> Se promovió a Predeterminada"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Estado:</b> Descendió de nivel a Silenciada"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Elige la app para agregar los controles"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Se agregó # control.}many{Se agregaron # controles.}other{Se agregaron # controles.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Quitados"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"¿Quieres agregar <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Si agregas la app de <xliff:g id="APPNAME">%s</xliff:g>, se incluirán controles y contenido en este panel. Algunas apps te permiten elegir qué controles mostrar aquí."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Está en favoritos"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Está en favoritos en la posición <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"No está en favoritos"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducir <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Acércate para reproducir en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproducir aquí, acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Se produjo un error. Vuelve a intentarlo."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Cargando"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Verifica la app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No se encontró"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"El control no está disponible"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Error. Vuelve a intentarlo."</string> <string name="controls_menu_add" msgid="4447246119229920050">"Agregar controles"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Agregar app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Agregar salidas"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Se seleccionó 1 dispositivo"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volumen"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Bocinas y pantallas"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cómo funciona la transmisión"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Transmisión"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Las personas cercanas con dispositivos Bluetooth compatibles pueden escuchar el contenido multimedia que transmites"</string> @@ -1018,13 +1016,19 @@ <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Cancelar"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Girar ahora"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Despliega el teléfono para tomar una selfie mejor"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"¿Cambiar a pantalla frontal para mejores selfies?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"¿Girar a pantalla frontal para mejores selfies?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Usa la cámara trasera para tomar una foto más amplia y con mejor resolución."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Esta pantalla se apagará"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable siendo desplegado"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable siendo girado"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de batería restante"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta tu pluma stylus a un cargador"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"La pluma stylus tiene poca batería"</string> + <string name="video_camera" msgid="7654002575156149298">"Videocámara"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"No se puede llamar desde este perfil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Tu política del trabajo te permite hacer llamadas telefónicas solo desde el perfil de trabajo"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Cambiar al perfil de trabajo"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Cerrar"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 9839e509acbf..41d9c9e96666 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite inferior"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite izquierdo"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite derecho"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Las capturas de pantalla de trabajo se guardan en la aplicación <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Archivos"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Grabación de pantalla"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Procesando grabación de pantalla"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación continua de una sesión de grabación de la pantalla"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sin sonido ni vibración"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sin sonido ni vibración, y se muestra más abajo en la sección de conversaciones"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Puede sonar o vibrar según los ajustes del teléfono"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Puede sonar o vibrar según los ajustes del teléfono. Las conversaciones de <xliff:g id="APP_NAME">%1$s</xliff:g> aparecen como burbujas de forma predeterminada."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Puede sonar o vibrar según los ajustes del dispositivo"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Puede sonar o vibrar según los ajustes del dispositivo. Las conversaciones de <xliff:g id="APP_NAME">%1$s</xliff:g> aparecen como burbujas de forma predeterminada."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Haz que el sistema determine si con esta notificación el dispositivo debe sonar o vibrar"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Estado:</b> cambio a Predeterminado"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Estado:</b> cambio a Silencio"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Elige una aplicación para añadir controles"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control añadido.}many{# controles añadidos.}other{# controles añadidos.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Quitado"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Añadido a favoritos"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Añadido a favoritos (posición <xliff:g id="NUMBER">%d</xliff:g>)"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Quitado de favoritos"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para reproducir contenido ahí"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproducirlo, acércate al dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>)"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Se ha producido un error. Inténtalo de nuevo."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Cargando"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo, comprobar aplicación"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No se ha encontrado"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Control no disponible"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Error: Vuelve a intentarlo"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Añadir controles"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Añadir dispositivos de salida"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo seleccionado"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volumen"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altavoces y pantallas"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Sugerencias de dispositivos"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cómo funciona la emisión"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Emisión"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Las personas cercanas con dispositivos Bluetooth compatibles pueden escuchar el contenido multimedia que emites"</string> @@ -1016,15 +1017,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Al menos un dispositivo debe estar disponible"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Mantén pulsado el acceso directo"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Cancelar"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Girar ahora"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Usar ahora"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Despliega el teléfono para hacer un selfie mejor"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"¿Usar pantalla frontal para hacer mejores selfies?"</string> - <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Usa la cámara trasera para hacer una foto más amplia y con mayor resolución."</string> + <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Usa la cámara trasera para hacer fotos más amplias y con mayor resolución."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Esta pantalla se apagará"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable desplegándose"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable mostrado desde varios ángulos"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batería restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta tu lápiz óptico a un cargador"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Batería del lápiz óptico baja"</string> + <string name="video_camera" msgid="7654002575156149298">"Videocámara"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"No se puede llamar desde este perfil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Tu política del trabajo solo te permite hacer llamadas telefónicas desde el perfil de trabajo"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Cambiar al perfil de trabajo"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Cerrar"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 5a83eb6fb60a..225812ca4639 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alapiir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vasak piir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Parem piir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Töö ekraanipildid salvestatakse rakendusse <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Ekraanisalvesti"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekraanisalvestuse töötlemine"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pooleli märguanne ekraanikuva salvestamise seansi puhul"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automaatne"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ilma heli ja vibreerimiseta"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ilma heli ja vibreerimiseta, kuvatakse vestluste jaotises allpool"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Võib telefoni seadete põhjal heliseda või vibreerida"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Võib telefoni seadete põhjal heliseda või vibreerida. Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> vestlused kuvatakse vaikimisi mullis."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Võib seadme seadete põhjal heliseda või vibreerida"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Võib seadme seadete põhjal heliseda või vibreerida. Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> vestlused kuvatakse vaikimisi mullis."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Laske süsteemil määrata, kas selle märguande puhul peaks esitama heli või vibreerima"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Olek:</b> määrati prioriteet Vaikimisi"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Olek:</b> määrati prioriteet Vaikne"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Valige juhtelementide lisamiseks rakendus"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Lisati # juhtnupp.}other{Lisati # juhtnuppu.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Eemaldatud"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Lisatud lemmikuks"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Lisatud lemmikuks, positsioon <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Eemaldatud lemmikute hulgast"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Võta tagasi"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Liikuge lähemale, et seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g> esitada"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Siin esitamiseks minge seadmele <xliff:g id="DEVICENAME">%1$s</xliff:g> lähemale"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Esitatakse seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Midagi läks valesti. Proovige uuesti."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Laadimine"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tahvelarvuti"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Passiivne, vaadake rakendust"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ei leitud"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Juhtelement pole saadaval"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Ilmnes viga, proovige uuesti"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Lisa juhtelemente"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Muuda juhtelemente"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Väljundite lisamine"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 seade on valitud"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Helitugevus"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Kõlarid ja ekraanid"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Soovitatud seadmed"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kuidas ülekandmine toimib?"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Ülekanne"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Teie läheduses olevad inimesed, kellel on ühilduvad Bluetooth-seadmed, saavad kuulata teie ülekantavat meediat"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ See ekraan lülitatakse välja"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Volditava seadme lahtivoltimine"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Volditava seadme ümberpööramine"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akutase on <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ühendage elektronpliiats laadijaga"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Elektronpliiatsi akutase on madal"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokaamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Sellelt profiililt ei saa helistada"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Teie töökoha eeskirjad lubavad teil helistada ainult tööprofiililt"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Lülitu tööprofiilile"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Sule"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 38db7066608c..37a66b0c5a2e 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Beheko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Ezkerreko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Eskuineko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Laneko pantaila-argazkiak <xliff:g id="APP">%1$s</xliff:g> aplikazioan gordetzen dira"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fitxategiak"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Pantaila-grabagailua"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Pantaila-grabaketa prozesatzen"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pantailaren grabaketa-saioaren jakinarazpen jarraitua"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatikoa"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ez du tonurik jotzen edo dar-dar egiten"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ez du tonurik jotzen edo dar-dar egiten, eta elkarrizketen atalaren behealdean agertzen da"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Tonua joko du, edo dar-dar egingo, telefonoaren ezarpenen arabera"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Tonua joko du, edo dar-dar egingo, telefonoaren ezarpenen arabera. Modu lehenetsian, <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioko elkarrizketak burbuila gisa agertzen dira."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Baliteke tonua jotzea edo dardara egitea, gailuaren ezarpenen arabera"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Baliteke tonua jotzea edo dardara egitea, gailuaren ezarpenen arabera. Modu lehenetsian, <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioko elkarrizketak burbuila gisa agertzen dira."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Ezarri sistemak zehaztu dezala jakinarazpen honek soinua edo dardara egin behar duen ala ez"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"Lehenetsi gisa ezarri da <b>egoera:</b>"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"Soinurik gabeko modura aldatu da <b>egoera:</b>"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Aukeratu aplikazio bat kontrolatzeko aukerak gehitzeko"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Kontrolatzeko # aukera gehitu da.}other{Kontrolatzeko # aukera gehitu dira.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Kenduta"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> gehitu nahi duzu?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> gehitzen duzunean, kontrolatzeko aukerak eta edukia gehi ditzake panelean. Aplikazio batzuetan, hemen zein kontrolatzeko aukera agertzen diren aukera dezakezu."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Gogokoetan dago"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"<xliff:g id="NUMBER">%d</xliff:g>. gogokoa da"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Ez dago gogokoetan"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> bidez"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desegin"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Gertura ezazu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailuan erreproduzitzeko"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Hemen erreproduzitzeko, hurbildu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailura"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> gailuan erreproduzitzen"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Arazoren bat izan da. Saiatu berriro."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Kargatzen"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tableta"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktibo; egiaztatu aplikazioa"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ez da aurkitu"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Ez dago erabilgarri kontrolatzeko aukera"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Errorea. Saiatu berriro."</string> <string name="controls_menu_add" msgid="4447246119229920050">"Gehitu aukerak"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Editatu aukerak"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Gehitu aplikazio bat"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Gehitu irteerak"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Taldea"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 gailu hautatu da"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Bolumena"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%% <xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Bozgorailuak eta pantailak"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Iradokitako gailuak"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Nola funtzionatzen dute iragarpenek?"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Iragarri"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Bluetooth bidezko gailu bateragarriak dituzten inguruko pertsonek iragartzen ari zaren multimedia-edukia entzun dezakete"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Pantaila itzali egingo da"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Gailu tolesgarria zabaltzen"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Gailu tolesgarria biratzen"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateriaren <xliff:g id="PERCENTAGE">%s</xliff:g> geratzen da"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Konektatu arkatza kargagailu batera"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Arkatzak bateria gutxi du"</string> + <string name="video_camera" msgid="7654002575156149298">"Bideokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Ezin duzu deitu profil honetatik"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Deiak laneko profiletik soilik egiteko baimena ematen dizute laneko gidalerroek"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Aldatu laneko profilera"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Itxi"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 9800da9b78aa..a057ad97b4c2 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"مرز پایین <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"مرز سمت چپ <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"مرز سمت راست <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"نماگرفتهای نمایه کاری در برنامه <xliff:g id="APP">%1$s</xliff:g> ذخیره میشوند"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"ضبطکننده صفحهنمایش"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"درحال پردازش ضبط صفحهنمایش"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"اعلان درحال انجام برای جلسه ضبط صفحهنمایش"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"خودکار"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"بدون صدا یا لرزش"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"بدون صدا و لرزش در پایین بخش مکالمه نشان داده میشود"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"بسته به تنظیمات ممکن است تلفن زنگ بزند یا لرزش داشته باشد"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"بسته به تنظیمات ممکن است تلفن زنگ بزند یا لرزش داشته باشد. مکالمههای <xliff:g id="APP_NAME">%1$s</xliff:g> بهطور پیشفرض در حبابک نشان داده میشوند."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"بسته به تنظیمات دستگاه ممکن است زنگ بزند یا بلرزد"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"بسته به تنظیمات دستگاه ممکن است زنگ بزند یا بلرزد. مکالمههای <xliff:g id="APP_NAME">%1$s</xliff:g> بهطور پیشفرض در حبابک نشان داده میشوند."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"سیستم را تنظیم کنید که تشخیص دهد اعلان صدا و لرزش داشته باشد یا نه"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>وضعیت:</b> به «پیشفرض» ارتقا یافت"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>وضعیت:</b> به «بیصدا» تنزل یافت"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"انتخاب برنامه برای افزودن کنترلها"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# کنترل اضافه شد.}one{# کنترل اضافه شد.}other{# کنترل اضافه شد.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"حذف شد"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"به موارد دلخواه اضافه شد"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"اضافهشده به موارد دلخواه، جایگاه <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"حذفشده از موارد دلخواه"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%2$s</xliff:g> پخش کنید"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"واگرد"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"برای پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g> به دستگاه نزدیکتر شوید"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"برای پخش در اینجا، به <xliff:g id="DEVICENAME">%1$s</xliff:g> نزدیکتر شوید"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"درحال پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"مشکلی پیش آمد. دوباره امتحان کنید."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"درحال بار کردن"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"رایانه لوحی"</string> <string name="controls_error_timeout" msgid="794197289772728958">"غیرفعال، برنامه را بررسی کنید"</string> <string name="controls_error_removed" msgid="6675638069846014366">"پیدا نشد"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"کنترل دردسترس نیست"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"خطا، دوباره امتحان کنید"</string> <string name="controls_menu_add" msgid="4447246119229920050">"افزودن کنترلها"</string> <string name="controls_menu_edit" msgid="890623986951347062">"ویرایش کنترلها"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"افزودن خروجی"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"گروه"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"۱ دستگاه انتخاب شد"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"میزان صدا"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"بلندگوها و نمایشگرها"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"دستگاههای پیشنهادی"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"همهفرتستی چطور کار میکند"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"همهفرستی"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"افرادی که در اطرافتان دستگاههای Bluetooth سازگار دارند میتوانند به رسانهای که همهفرستی میکنید گوش کنند"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ این صفحهنمایش خاموش خواهد شد"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"دستگاه تاشو درحال باز شدن"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"دستگاه تاشو درحال چرخش به اطراف"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> باتری باقی مانده است"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"قلم را به شارژر وصل کنید"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"باتری قلم ضعیف است"</string> + <string name="video_camera" msgid="7654002575156149298">"دوربین ویدیویی"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"نمیتوانید از این نمایه تماس بگیرید"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"خطمشی کاری شما فقط به برقراری تماس ازطریق نمایه کاری اجازه میدهد"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"رفتن به نمایه کاری"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"بستن"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index efdc9cb89e17..c4f55713f62e 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alareuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vasen reuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Oikea reuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Työprofiilin kuvakaappaukset tallennetaan sovellukseen: <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Näytön tallentaja"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Näytön tallennusta käsitellään"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pysyvä ilmoitus näytön tallentamisesta"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automaattinen"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ei ääntä tai värinää"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ei ääntä tai värinää ja näkyy alempana keskusteluosiossa"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Voi soida tai väristä puhelimen asetuksista riippuen"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Voi soida tai väristä puhelimen asetuksista riippuen. Näistä keskusteluista (<xliff:g id="APP_NAME">%1$s</xliff:g>) syntyy oletuksena kuplia."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Voi soida tai väristä laitteen asetuksista riippuen"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Voi soida tai väristä laitteen asetuksista riippuen. Keskusteluista (<xliff:g id="APP_NAME">%1$s</xliff:g>) luodaan oletuksena kuplia."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Järjestelmä valitsee, kuuluuko tästä ilmoituksesta ääntä tai väriseekö se"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Tila:</b> valittu oletusarvoiseksi"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Tila:</b> hiljennetty"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Valitse sovellus lisätäksesi säätimiä"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# säädin lisätty.}other{# säädintä lisätty.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Poistettu"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Lisätty suosikkeihin"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Lisätty suosikkeihin sijalle <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Poistettu suosikeista"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="APP_LABEL">%2$s</xliff:g>)"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Kumoa"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Siirry lähemmäs, jotta <xliff:g id="DEVICENAME">%1$s</xliff:g> voi toistaa tämän"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Siirry lähemmäs laitetta (<xliff:g id="DEVICENAME">%1$s</xliff:g>) toistaaksesi täällä"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Toistetaan: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Jotain meni pieleen. Yritä uudelleen."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Latautuminen"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tabletti"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Epäaktiivinen, tarkista sovellus"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ei löydy"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Ohjain ei ole käytettävissä"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Virhe, yritä uudelleen"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Lisää säätimiä"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Muokkaa säätimiä"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Lisää toistotapoja"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Ryhmä"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 laite valittu"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Äänenvoimakkuus"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Kaiuttimet ja näytöt"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ehdotetut laitteet"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Miten lähetys toimii"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Lähetys"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Lähistöllä olevat ihmiset, joilla on yhteensopiva Bluetooth-laite, voivat kuunnella lähettämääsi mediaa"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Tämä näyttö sammutetaan"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Taitettava laite taitetaan"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Taitettava laite käännetään ympäri"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akkua jäljellä <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Yhdistä näyttökynä laturiin"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Näyttökynän akku vähissä"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Tästä profiilista ei voi soittaa"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Työkäytäntö sallii sinun soittaa puheluita vain työprofiilista"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Vaihda työprofiiliin"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Sulje"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 5423c579bf2b..a65d2f367ca3 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inférieure : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite gauche : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite droite : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Les captures d\'écran du profil professionnel sont enregistrées dans l\'application <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fichiers"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Enregistreur d\'écran"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Trait. de l\'enregist. d\'écran…"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement d\'écran"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatique"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Aucun son ni vibration"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Aucun son ni vibration, et s\'affiche plus bas dans la section des conversations"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Peut sonner ou vibrer, selon les paramètres du téléphone"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Peut sonner ou vibrer, selon les paramètres du téléphone. Conversations des bulles de <xliff:g id="APP_NAME">%1$s</xliff:g> par défaut."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Peut sonner ou vibrer, selon les paramètres de l\'appareil"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Peut sonner ou vibrer, selon les paramètres de l\'appareil. Conversations des bulles de <xliff:g id="APP_NAME">%1$s</xliff:g> par défaut."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faire en sorte que le système détermine si cette notification devrait émettre un son ou vibrer"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>État :</b> élevé à la catégorie Par défaut"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>État :</b> abaissé à la catégorie Silencieux"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'application pour laquelle ajouter des commandes"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# commande ajoutée.}one{# commande ajoutée.}many{# de commandes ajoutées.}other{# commandes ajoutées.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Supprimé"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Ajouté aux favoris"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ajouté aux favoris, en position <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Supprimé des favoris"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Rapprochez-vous pour faire jouer le contenu sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pour faire jouer le contenu ici, rapprochez-vous de <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <string name="media_transfer_failed" msgid="7955354964610603723">"Un problème est survenu. Réessayez."</string> + <string name="media_transfer_failed" msgid="7955354964610603723">"Une erreur s\'est produite. Réessayez."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Chargement en cours…"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablette"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifiez l\'appli"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"La commande n\'est pas accessible"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Erreur. Veuillez réessayer."</string> <string name="controls_menu_add" msgid="4447246119229920050">"Ajouter des commandes"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Modifier des commandes"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ajouter des sorties"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Un appareil sélectionné"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Haut-parleurs et écrans"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Appareils suggérés"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Fonctionnement de la diffusion"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Diffusion"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les personnes à proximité disposant d\'appareils Bluetooth compatibles peuvent écouter le contenu multimédia que vous diffusez"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* Cet écran va s\'éteindre"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable en cours de dépliage"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable en train d\'être retourné"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Charge restante de la pile : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connectez votre stylet à un chargeur"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Pile du stylet faible"</string> + <string name="video_camera" msgid="7654002575156149298">"Mode vidéo"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Impossible de passer un appel à partir de ce profil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Votre politique de l\'entreprise vous autorise à passer des appels téléphoniques uniquement à partir de votre profil professionnel"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Passer au profil professionnel"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Fermer"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index c356d7393c12..c779b63cdee0 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inférieure : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite gauche : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite droite : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Les captures d\'écran du profil professionnel sont enregistrées dans l\'appli <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fichiers"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Enregistreur d\'écran"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Enregistrement de l\'écran…"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement de l\'écran"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatique"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ni son, ni vibreur"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ni son, ni vibreur ; s\'affiche plus bas dans la section des conversations"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Son ou vibreur, selon les paramètres du téléphone"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Son ou vibreur, selon les paramètres du téléphone. Les conversations provenant de <xliff:g id="APP_NAME">%1$s</xliff:g> s\'affichent sous forme de bulles par défaut."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Peut sonner ou vibrer en fonction des paramètres de l\'appareil"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Peut sonner ou vibrer en fonction des paramètres de l\'appareil. Les conversations provenant de <xliff:g id="APP_NAME">%1$s</xliff:g> s\'affichent sous forme de bulles par défaut."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Laisser le système déterminer si cette notification doit être accompagnée d\'un son ou d\'une vibration"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>État :</b> Élevée à la catégorie \"Par défaut\""</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>État :</b> Abaissée à la catégorie \"Silencieux\""</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'appli pour laquelle ajouter des commandes"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# commande ajoutée.}one{# commande ajoutée.}many{# commandes ajoutées.}other{# commandes ajoutées.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Supprimé"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Ajouté aux favoris"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ajouté aux favoris, en position <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Supprimé des favoris"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> depuis <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Rapprochez-vous de votre <xliff:g id="DEVICENAME">%1$s</xliff:g> pour y lire le contenu"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pour lancer la lecture ici, rapprochez-vous de <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>…"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Un problème est survenu. Réessayez."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Chargement…"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablette"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifier l\'appli"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Commande indisponible"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Erreur. Veuillez réessayer."</string> <string name="controls_menu_add" msgid="4447246119229920050">"Ajouter des commandes"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Modifier des commandes"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ajouter des sorties"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 appareil sélectionné"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Enceintes et écrans"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Appareils suggérés"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Fonctionnement des annonces"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Annonce"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les personnes à proximité équipées d\'appareils Bluetooth compatibles peuvent écouter le contenu multimédia que vous diffusez"</string> @@ -1016,15 +1017,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Au moins un appareil est disponible"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Appuyez de manière prolongée sur raccourci"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Annuler"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Retourner maintenant"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Retourner"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Déplier le téléphone pour un meilleur selfie"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Passer à l\'écran frontal pour un meilleur selfie ?"</string> - <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Utilisez la caméra arrière pour prendre une photo plus large avec une résolution supérieure."</string> - <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Cet écran sera désactivé"</b></string> + <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Utilisez la caméra arrière pour prendre une photo plus large d\'une résolution supérieure."</string> + <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Cet écran s\'éteindra"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable qui est déplié"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable qui est retourné"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de batterie restante"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connectez votre stylet à un chargeur"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"La batterie du stylet est faible"</string> + <string name="video_camera" msgid="7654002575156149298">"Caméra vidéo"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Impossible d\'appeler depuis ce profil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Votre règle professionnelle ne vous permet de passer des appels que depuis le profil professionnel"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Passer au profil professionnel"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Fermer"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 4bc51230c08b..9c270a9abcd5 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bordo inferior: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Bordo esquerdo: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Bordo dereito: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"As capturas de pantalla do perfil de traballo gárdanse na aplicación <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ficheiros"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Gravadora da pantalla"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Procesando gravación pantalla"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación en curso sobre unha sesión de gravación de pantalla"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sen son nin vibración"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sen son nin vibración, e aparecen máis abaixo na sección de conversas"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"O teléfono pode soar ou vibrar en función da súa configuración"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Poderían facer que o teléfono soe ou vibre en función da súa configuración. Conversas desde a burbulla da aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> de forma predeterminada."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Poderían facer que o dispositivo soe ou vibre en función da súa configuración"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Poderían facer que o dispositivo soe ou vibre en función da súa configuración. As conversas de <xliff:g id="APP_NAME">%1$s</xliff:g> móstranse en burbullas de forma predeterminada."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Fai que o sistema determine se a notificación debe emitir un son ou unha vibración"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Estado:</b> ascendeuse a Predeterminada"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Estado:</b> o nivel diminuíuse a Silencioso"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Escolle unha aplicación para engadir controis"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Engadiuse # control.}other{Engadíronse # controis.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Quitouse"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Está entre os controis favoritos"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Está entre os controis favoritos (posición: <xliff:g id="NUMBER">%d</xliff:g>)"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Non está entre os controis favoritos"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfacer"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Achega o dispositivo para reproducir o contido en: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproducir o contido aquí, achégate ao dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>)"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproducindo contido noutro dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>)"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Produciuse un erro. Téntao de novo."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Cargando"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tableta"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Comproba a app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Non se atopou"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"O control non está dispoñible"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Erro. Téntao de novo"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Engadir controis"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Editar controis"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Engadir saídas"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Seleccionouse 1 dispositivo"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altofalantes e pantallas"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos suxeridos"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funcionan as difusións?"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Difusión"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As persoas que estean preto de ti e que dispoñan de dispositivos Bluetooth compatibles poden escoitar o contido multimedia que difundas"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Desactivarase esta pantalla"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pregable abríndose"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pregable xirando"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batería restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta o lapis óptico a un cargador"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"O lapis óptico ten pouca batería"</string> + <string name="video_camera" msgid="7654002575156149298">"Videocámara"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Non se pode chamar desde este perfil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"A política do teu traballo só che permite facer chamadas de teléfono desde o perfil de traballo"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Cambiar ao perfil de traballo"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Pechar"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 3075f16860d2..3457430a65b2 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"નીચેની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ડાબી બાજુની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"જમણી બાજુની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ઑફિસના સ્ક્રીનશૉટ <xliff:g id="APP">%1$s</xliff:g> ઍપમાં સાચવવામાં આવે છે"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ફાઇલો"</string> <string name="screenrecord_name" msgid="2596401223859996572">"સ્ક્રીન રેકોર્ડર"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"સ્ક્રીન રેકૉર્ડિંગ ચાલુ છે"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"સ્ક્રીન રેકોર્ડિંગ સત્ર માટે ચાલુ નોટિફિકેશન"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ઑટોમૅટિક રીતે"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"કોઈપણ સાઉન્ડ અથવા વાઇબ્રેશન નથી"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"કોઈપણ સાઉન્ડ અથવા વાઇબ્રેશન નથી અને વાતચીત વિભાગમાં તે વધુ નીચેની દિશાએ દેખાય છે"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ફોન સેટિંગના આધારે રિંગ અથવા વાઇબ્રેટ થઈ શકે છે"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ફોન સેટિંગના આધારે રિંગ અથવા વાઇબ્રેટ થઈ શકે છે. ડિફૉલ્ટ તરીકે <xliff:g id="APP_NAME">%1$s</xliff:g> બબલની વાતચીત."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"ડિવાઇસના સેટિંગના આધારે રિંગ અથવા વાઇબ્રેટ થઈ શકે છે"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ડિવાઇસના સેટિંગના આધારે રિંગ અથવા વાઇબ્રેટ થઈ શકે છે. ડિફૉલ્ટ તરીકે <xliff:g id="APP_NAME">%1$s</xliff:g> બબલની વાતચીત."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"આ નોટિફિકેશન સાઉન્ડ અથવા વાઇબ્રેટ કરી શકશે કે નહીં તે સિસ્ટમને નક્કી કરવા દો"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>સ્ટેટસ:</b> ડિફૉલ્ટ તરીકે બઢતી આપવામાં આવી"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>સ્ટેટસ:</b> સાઇલન્ટ પર અવનત કરવામાં આવ્યું"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"નિયંત્રણો ઉમેરવા માટે ઍપ પસંદ કરો"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# નિયંત્રણ ઉમેર્યું.}one{# નિયંત્રણ ઉમેર્યું.}other{# નિયંત્રણ ઉમેર્યા.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"કાઢી નાખ્યું"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> ઉમેરીએ?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"જ્યારે તમે <xliff:g id="APPNAME">%s</xliff:g> ઉમેરો, ત્યારે તે આ પૅનલમાં નિયંત્રણો અને કન્ટેન્ટ ઉમેરી શકે છે. કેટલીક ઍપમાં, અહીં કયા નિયંત્રણો દેખાય તે તમે પસંદ કરી શકો છો."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"મનપસંદમાં ઉમેર્યું"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"મનપસંદમાં ઉમેર્યું, સ્થાન <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"મનપસંદમાંથી કાઢી નાખ્યું"</string> @@ -819,7 +819,7 @@ <string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"અન્ય"</string> <string name="controls_dialog_title" msgid="2343565267424406202">"ડિવાઇસનાં નિયંત્રણોમાં ઉમેરો"</string> <string name="controls_dialog_ok" msgid="2770230012857881822">"ઉમેરો"</string> - <string name="controls_dialog_message" msgid="342066938390663844">"<xliff:g id="APP">%s</xliff:g> દ્વારા સૂચન કરેલા"</string> + <string name="controls_dialog_message" msgid="342066938390663844">"<xliff:g id="APP">%s</xliff:g> દ્વારા સૂચવેલા"</string> <string name="controls_tile_locked" msgid="731547768182831938">"ડિવાઇસ લૉક કરેલું છે"</string> <string name="controls_settings_show_controls_dialog_title" msgid="3357852503553809554">"લૉક સ્ક્રીનમાંથી ડિવાઇસ બતાવીએ અને નિયંત્રિત કરીએ?"</string> <string name="controls_settings_show_controls_dialog_message" msgid="7666211700524587969">"તમે તમારા બાહ્ય ડિવાઇસ માટેના નિયંત્રણો લૉક સ્ક્રીન પર ઉમેરી શકો છો.\n\nતમારી ડિવાઇસ ઍપ કદાચ તમને તમારો ફોન કે ટૅબ્લેટ અનલૉક કર્યા વિના અમુક ડિવાઇસ નિયંત્રિત કરવાની મંજૂરી આપી શકે.\n\nતમે ગમે ત્યારે સેટિંગમાં જઈને ફેરફાર કરી શકો છો."</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> પર <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"છેલ્લો ફેરફાર રદ કરો"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવા માટે વધુ નજીક ખસેડો"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"અહીં ચલાવવા માટે, <xliff:g id="DEVICENAME">%1$s</xliff:g>ની નજીક લાવો"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવામાં આવી રહ્યું છે"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"કંઈક ખોટું થયું. ફરી પ્રયાસ કરો."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"લોડ થઈ રહ્યું છે"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ટૅબ્લેટ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"નિષ્ક્રિય, ઍપને ચેક કરો"</string> <string name="controls_error_removed" msgid="6675638069846014366">"મળ્યું નથી"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"નિયંત્રણ ઉપલબ્ધ નથી"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"ભૂલ, ફરીથી પ્રયાસ કરો"</string> <string name="controls_menu_add" msgid="4447246119229920050">"નિયંત્રણો ઉમેરો"</string> <string name="controls_menu_edit" msgid="890623986951347062">"નિયંત્રણોમાં ફેરફાર કરો"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ઍપ ઉમેરો"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"આઉટપુટ ઉમેરો"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"ગ્રૂપ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ડિવાઇસ પસંદ કર્યું"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"વૉલ્યૂમ"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"સ્પીકર અને ડિસ્પ્લે"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"સૂચવેલા ડિવાઇસ"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"બ્રોડકાસ્ટ પ્રક્રિયાની કામ કરવાની રીત"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"બ્રોડકાસ્ટ કરો"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"સુસંગત બ્લૂટૂથ ડિવાઇસ ધરાવતા નજીકના લોકો તમે જે મીડિયા બ્રોડકાસ્ટ કરી રહ્યાં છો તે સાંભળી શકે છે"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ આ સ્ક્રીન બંધ થઈ જશે"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ અનફોલ્ડ કરવામાં આવી રહ્યું છે"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ ફ્લિપ કરવામાં આવી રહ્યું છે"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> બૅટરી બાકી છે"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"તમારા સ્ટાઇલસને ચાર્જર સાથે કનેક્ટ કરો"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"સ્ટાઇલસની બૅટરીમાં ચાર્જ ઓછો છે"</string> + <string name="video_camera" msgid="7654002575156149298">"વીડિયો કૅમેરા"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"આ પ્રોફાઇલ પરથી કૉલ કરી શકતા નથી"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"તમારી ઑફિસની પૉલિસી તમને માત્ર ઑફિસની પ્રોફાઇલ પરથી જ ફોન કૉલ કરવાની મંજૂરી આપે છે"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"ઑફિસની પ્રોફાઇલ પર સ્વિચ કરો"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"બંધ કરો"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 6828e8d4893d..d96bc52c48e2 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"निचले किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"बाएं किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"दाएं किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"वर्क प्रोफ़ाइल से लिए गए स्क्रीनशॉट, <xliff:g id="APP">%1$s</xliff:g> ऐप्लिकेशन में सेव किए गए हैं"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"स्क्रीन रिकॉर्डर"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"स्क्रीन रिकॉर्डिंग को प्रोसेस किया जा रहा है"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रिकॉर्ड सेशन के लिए जारी सूचना"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"अपने-आप"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"किसी तरह की आवाज़ या वाइब्रेशन न हो"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"इससे किसी तरह की आवाज़ या वाइब्रेशन नहीं होता और बातचीत, सेक्शन में सबसे नीचे दिखती है"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"फ़ोन की सेटिंग के आधार पर, सूचना आने पर घंटी बज सकती है या वाइब्रेशन हो सकता है"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"फ़ोन की सेटिंग के आधार पर, सूचना आने पर घंटी बज सकती है या वाइब्रेशन हो सकता है. <xliff:g id="APP_NAME">%1$s</xliff:g> में होने वाली बातचीत, डिफ़ॉल्ट रूप से बबल के तौर पर दिखती है."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"डिवाइस की सेटिंग के आधार पर, सूचना आने पर घंटी बज सकती है या वाइब्रेशन हो सकता है"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"डिवाइस की सेटिंग के आधार पर, सूचना आने पर घंटी बज सकती है या वाइब्रेशन हो सकता है. <xliff:g id="APP_NAME">%1$s</xliff:g> पर होने वाली बातचीत, डिफ़ॉल्ट रूप से बबल के तौर पर दिखती है."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"सिस्टम को यह तय करने की अनुमति दें कि इस सूचना के मिलने पर आवाज़ हो या वाइब्रेशन हो"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>स्थिति:</b> लेवल बढ़ाकर, डिफ़ॉल्ट के तौर पर सेट किया गया"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>स्थिति:</b> लेवल घटाकर, साइलेंट पर सेट किया गया"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"कंट्रोल जोड़ने के लिए ऐप्लिकेशन चुनें"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# कंट्रोल जोड़ा गया.}one{# कंट्रोल जोड़ा गया.}other{# कंट्रोल जोड़े गए.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"हटाया गया"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"पसंदीदा बनाया गया"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"पसंदीदा बनाया गया, क्रम संख्या <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"पसंदीदा से हटाया गया"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> पर, <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"पहले जैसा करें"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर मीडिया चलाने के लिए, अपने डिवाइस को उसके पास ले जाएं"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"मीडिया ट्रांसफ़र करने के लिए, <xliff:g id="DEVICENAME">%1$s</xliff:g> के करीब जाएं"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर मीडिया चल रहा है"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"कोई गड़बड़ी हुई. फिर से कोशिश करें."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"लोड हो रहा है"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"टैबलेट"</string> <string name="controls_error_timeout" msgid="794197289772728958">"काम नहीं कर रहा, ऐप जांचें"</string> <string name="controls_error_removed" msgid="6675638069846014366">"कंट्रोल नहीं है"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"कंट्रोल मौजूद नहीं है"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"गड़बड़ी हुई, फिर से कोशिश करें"</string> <string name="controls_menu_add" msgid="4447246119229920050">"कंट्राेल जोड़ें"</string> <string name="controls_menu_edit" msgid="890623986951347062">"कंट्रोल में बदलाव करें"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट जोड़ें"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"ग्रुप"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिवाइस चुना गया"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"वॉल्यूम"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"स्पीकर और डिसप्ले"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"सुझाए गए डिवाइस"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ब्रॉडकास्ट करने की सुविधा कैसे काम करती है"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"ब्रॉडकास्ट करें"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"आपके आस-पास मौजूद लोग, ब्रॉडकास्ट किए जा रहे मीडिया को सुन सकते हैं. हालांकि, इसके लिए उनके पास ऐसे ब्लूटूथ डिवाइस होने चाहिए जिन पर मीडिया चलाया जा सके"</string> @@ -1016,15 +1017,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• कम से कम एक डिवाइस उपलब्ध है"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"शॉर्टकट को दबाकर रखें"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"रद्द करें"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"कैमरा अभी स्विच करें"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"अभी स्विच करें"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"बेहतर सेल्फ़ी के लिए फ़ोन को अनफ़ोल्ड करें"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"बेहतर सेल्फ़ी के लिए फ़्रंट डिसप्ले पर स्विच करें?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"वाइड ऐंगल में हाई रिज़ॉल्यूशन वाली फ़ोटो लेने के लिए, पीछे का कैमरा इस्तेमाल करें."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ यह स्क्रीन बंद हो जाएगी"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फ़ोल्ड किया जा सकने वाला डिवाइस अनफ़ोल्ड किया जा रहा है"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फ़ोल्ड किया जा सकने वाला डिवाइस पलटा जा रहा है"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> बैटरी बची है"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"अपने स्टाइलस को चार्ज करें"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"स्टाइलस की बैटरी कम है"</string> + <string name="video_camera" msgid="7654002575156149298">"वीडियो कैमरा"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"यह प्रोफ़ाइल होने पर कॉल नहीं की जा सकती"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"ऑफ़िस की नीति के तहत, वर्क प्रोफ़ाइल होने पर ही फ़ोन कॉल किए जा सकते हैं"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"वर्क प्रोफ़ाइल पर स्विच करें"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"बंद करें"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 484e3a4577c2..cb83de90ad54 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatski"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Bez zvuka ili vibracije"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Bez zvuka ili vibracije i prikazuje se pri dnu odjeljka razgovora"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Možda će zvoniti ili vibrirati, ovisno o postavkama telefona"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Možda će zvoniti ili vibrirati, ovisno o postavkama telefona. Razgovori iz aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g> prikazuju se u oblačiću prema zadanim postavkama."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Možda će zvoniti ili vibrirati, ovisno o postavkama uređaja"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Možda će zvoniti ili vibrirati, ovisno o postavkama uređaja. Razgovori iz aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g> prikazuju se u oblačiću prema zadanim postavkama."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Neka sustav odredi treba li obavijest najaviti zvukom ili vibracijom"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> promaknuta u zadanu"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> prebačena u bešumnu"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Odabir aplikacije za dodavanje kontrola"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Dodana je # kontrola.}one{Dodana je # kontrola.}few{Dodane su # kontrole.}other{Dodano je # kontrola.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Uklonjeno"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Želite li dodati aplikaciju <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Kada dodate aplikaciju <xliff:g id="APPNAME">%s</xliff:g>, može dodati kontrole i sadržaj na ovu ploču. U nekim aplikacijama možete odabrati koje se kontrole prikazuju ovdje."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Dodano u favorite"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Dodano u favorite, položaj <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Uklonjeno iz favorita"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite se radi reprodukcije na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Da biste reproducirali ovdje, približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Nešto nije u redu. Pokušajte ponovo."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Učitavanje"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Pogreška, pokušajte ponovo"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Uredi kontrole"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Dodavanje aplikacije"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodavanje izlaza"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Odabran je jedan uređaj"</string> @@ -1012,15 +1014,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Dostupan je najmanje jedan uređaj"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Prečac za dodirnuti i zadržati"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Odustani"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Okreni odmah"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Prebaci"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Otvorite telefon da biste snimili bolji selfie"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Prebaciti na prednji zaslon za bolji selfie?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Upotrijebite stražnji fotoaparat za širu fotografiju s višom razlučivošću."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ovaj će se zaslon isključiti"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rasklopljen sklopivi uređaj"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Okretanje sklopivog uređaja sa svih strana"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo je <xliff:g id="PERCENTAGE">%s</xliff:g> baterije"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Priključite pisaljku na punjač"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Slaba baterija pisaljke"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nije moguće uspostavljati pozive s ovog profila"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Vaša pravila za poslovne uređaje omogućuju vam upućivanje poziva samo s poslovnog profila"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Prijeđite na poslovni profil"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zatvori"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index cfa0577c29be..cabd8987c2d0 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alsó rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Bal oldali rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Jobb oldali rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"A munkahelyi képernyőképeket a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásba menti a rendszer"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fájlok"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Képernyőrögzítő"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Képernyőrögzítés feldolgozása"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Folyamatban lévő értesítés képernyőrögzítési munkamenethez"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatikus"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Nincs hang és rezgés"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Nincs hang és rezgés, továbbá lejjebb jelenik meg a beszélgetések szakaszában"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"A telefonbeállítások alapján csöröghet és rezeghet"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"A telefonbeállítások alapján csöröghet és rezeghet. A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazásban lévő beszélgetések alapértelmezés szerint buborékban jelennek meg."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Az eszközbeállítások alapján csöröghet és rezeghet"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Az eszközbeállítások alapján csöröghet és rezeghet. A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazásban lévő beszélgetések alapértelmezés szerint buborékban jelennek meg."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"A rendszer határozza meg, hogy ez az értesítés adjon-e ki hangot, illetve rezegjen-e"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Állapot:</b> alapértelmezettre állítva"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Állapot:</b> némára állítva"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Válasszon alkalmazást a vezérlők hozzáadásához"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# vezérlő hozzáadva.}other{# vezérlő hozzáadva.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Eltávolítva"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Hozzáadja a(z) <xliff:g id="APPNAME">%s</xliff:g> alkalmazást?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"A(z) <xliff:g id="APPNAME">%s</xliff:g> hozzáadását követően az alkalmazás vezérlőelemeket és tartalmakat adhat hozzá ehhez a panelhez. Egyes alkalmazásokban kiválasztható, hogy mely vezérlőelemek jelenjenek meg itt."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Hozzáadva a kedvencekhez"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Hozzáadva a kedvencekhez <xliff:g id="NUMBER">%d</xliff:g>. helyen"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Eltávolítva a kedvencek közül"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> lejátszása innen: <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Visszavonás"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Menjen közelebb, ha itt szeretné lejátszani: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ha szeretné itt lejátszani, helyezkedjen közelebb a következőhöz: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Lejátszás folyamatban a(z) <xliff:g id="DEVICENAME">%1$s</xliff:g> eszközön"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Hiba történt. Próbálkozzon újra."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Betöltés…"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"táblagép"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktív, ellenőrizze az appot"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nem található"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Nem hozzáférhető vezérlő"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Hiba történt. Próbálja újra."</string> <string name="controls_menu_add" msgid="4447246119229920050">"Vezérlők hozzáadása"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Vezérlők szerkesztése"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Alkalmazás hozzáadása"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Kimenetek hozzáadása"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Csoport"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 eszköz kiválasztva"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hangerő"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hangfalak és kijelzők"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Javasolt eszközök"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"A közvetítés működése"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Közvetítés"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"A közelben tartózkodó, kompatibilis Bluetooth-eszközzel rendelkező személyek meghallgathatják az Ön közvetített médiatartalmait"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ A képernyő kikapcsol"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Összehajtható eszköz kihajtása"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Összehajtható eszköz körbeforgatása"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akkumulátor töltöttségi szintje: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Tegye töltőre az érintőceruzát"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Az érintőceruza töltöttsége alacsony"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nem lehet hívást kezdeményezni ebből a profilból"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"A munkahelyi házirend csak munkaprofilból kezdeményezett telefonhívásokat engedélyez"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Váltás munkaprofilra"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Bezárás"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 8f7ecf600138..5b3cefb179bb 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ներքևի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Ձախ կողմի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Աջ կողմի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Աշխատանքային սքրինշոթները պահվում են «<xliff:g id="APP">%1$s</xliff:g>» հավելվածում"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ֆայլեր"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Էկրանի տեսագրիչ"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Էկրանի տեսագրության մշակում"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Էկրանի տեսագրման աշխատաշրջանի ընթացիկ ծանուցում"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Ավտոմատ"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Առանց ձայնի կամ թրթռոցի"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Առանց ձայնի և թրթռոցի, հայտնվում է զրույցների ցանկի ներքևում"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Կարող է զնգալ կամ թրթռալ (հեռախոսի կարգավորումներից կախված)"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Կարող է զնգալ կամ թրթռալ (հեռախոսի կարգավորումներից կախված)։ <xliff:g id="APP_NAME">%1$s</xliff:g>-ի զրույցներն ըստ կանխադրման հայտնվում են ամպիկների տեսքով։"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Կարող է զնգալ կամ թրթռալ՝ կախված սարքի կարգավորումներից"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Կարող է զնգալ կամ թրթռալ՝ կախված սարքի կարգավորումներից։ <xliff:g id="APP_NAME">%1$s</xliff:g>-ի զրույցներն ըստ կանխադրման հայտնվում են ամպիկների տեսքով։"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Թող համակարգն ավտոմատ որոշի՝ արդյոք այս ծանուցումը ձայնով, թե թրթռոցով է պետք մատուցել"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Կարգավիճակը․</b> բարձրացվել է և դարձել կանխադրված"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Կարգավիճակը․</b> իջեցվել է և դարձել անձայն"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Ընտրեք հավելված` կառավարման տարրեր ավելացնելու համար"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Ավելացվեց կառավարման # տարր։}one{Ավելացվեց կառավարման # տարր։}other{Ավելացվեց կառավարման # տարր։}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Հեռացված է"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Ավելացված է ընտրանիում"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ավելացված է ընտրանիում, դիրքը՝ <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Հեռացված է ընտրանուց"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="APP_LABEL">%2$s</xliff:g> հավելվածից"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Հետարկել"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ավելի մոտ եկեք՝ <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքում նվագարկելու համար"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Այստեղ նվագարկելու համար մոտեցեք <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքին"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Նվագարկվում է «<xliff:g id="DEVICENAME">%1$s</xliff:g>» սարքում"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Սխալ առաջացավ։ Նորից փորձեք։"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Բեռնվում է"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"պլանշետ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Ակտիվ չէ, ստուգեք հավելվածը"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Չի գտնվել"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Կառավարման տարրը հասանելի չէ"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Սխալ առաջացավ։ Նորից փորձեք։"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Ավելացնել կառավարման տարրեր"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Փոփոխել կառավարման տարրերը"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ավելացրեք մուտքագրման սարքեր"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Խումբ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ընտրված է 1 սարք"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Ձայնի ուժգնություն"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Բարձրախոսներ և էկրաններ"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Առաջարկվող սարքեր"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Ինչպես է աշխատում հեռարձակումը"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Հեռարձակում"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Ձեր մոտակայքում գտնվող՝ համատեղելի Bluetooth սարքերով մարդիկ կարող են լսել մեդիա ֆայլերը, որոնք դուք հեռարձակում եք։"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Այս էկրանը կանջատվի"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ծալովի սարք՝ բացված վիճակում"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Ծալովի սարք՝ շրջված վիճակում"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Մարտկոցի լիցքը՝ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ձեր ստիլուսը միացրեք լիցքավորիչի"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Ստիլուսի մարտկոցի լիցքի ցածր մակարդակ"</string> + <string name="video_camera" msgid="7654002575156149298">"Տեսախցիկ"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Հնարավոր չէ զանգել այս պրոֆիլից"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Ձեր աշխատանքային կանոնների համաձայն՝ դուք կարող եք զանգեր կատարել աշխատանքային պրոֆիլից"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Անցնել աշխատանքային պրոֆիլ"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Փակել"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index a427a2c56f55..a00839f42785 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Batas bawah <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Batas kiri <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Batas kanan <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Screenshot dengan profil kerja disimpan di aplikasi <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"File"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Perekam Layar"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Memproses perekaman layar"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifikasi yang sedang berjalan untuk sesi rekaman layar"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Otomatis"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Tidak ada suara atau getaran"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Tidak ada suara atau getaran dan ditampilkan lebih rendah di bagian percakapan"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Dapat berdering atau bergetar berdasarkan setelan ponsel"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Dapat berdering atau bergetar berdasarkan setelan ponsel. Percakapan dari balon <xliff:g id="APP_NAME">%1$s</xliff:g> secara default."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Dapat berdering atau bergetar berdasarkan setelan perangkat"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Dapat berdering atau bergetar berdasarkan setelan perangkat. Percakapan <xliff:g id="APP_NAME">%1$s</xliff:g> ditampilkan sebagai balon secara default."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Biarkan sistem menentukan apakah notifikasi ini akan berbunyi atau bergetar"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Dipromosikan menjadi Default"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Didemosikan menjadi Senyap"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Pilih aplikasi untuk menambahkan kontrol"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrol ditambahkan.}other{# kontrol ditambahkan.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Dihapus"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Difavoritkan"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Difavoritkan, posisi <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Batal difavoritkan"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> dari <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Urungkan"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Dekatkan untuk memutar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Untuk memutar di sini, dekatkan ke <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Diputar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Terjadi error. Coba lagi."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Memuat"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Nonaktif, periksa aplikasi"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrol tidak tersedia"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Error, coba lagi"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Tambahkan kontrol"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Edit kontrol"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tambahkan output"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 perangkat dipilih"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speaker & Layar"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Perangkat yang Disarankan"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cara kerja siaran"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Siaran"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Orang di dekat Anda dengan perangkat Bluetooth yang kompatibel dapat mendengarkan media yang sedang Anda siarkan"</string> @@ -1018,13 +1019,19 @@ <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Batal"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Balik sekarang"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Bentangkan ponsel untuk selfie yang lebih baik"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Gunakan layar depan untuk selfie yang lebih baik?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Balik ke layar depan untuk selfie yang lebih bagus?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Gunakan kamera belakang untuk foto dengan resolusi lebih tinggi dan lebih lebar."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Layar ini akan dinonaktifkan"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Perangkat foldable sedang dibentangkan"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Perangkat foldable sedang dibalik"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Baterai tersisa <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Hubungkan stilus ke pengisi daya"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Baterai stilus lemah"</string> + <string name="video_camera" msgid="7654002575156149298">"Kamera video"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Tidak dapat melakukan panggilan dari profil ini"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Kebijakan kantor mengizinkan Anda melakukan panggilan telepon hanya dari profil kerja"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Beralih ke profil kerja"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Tutup"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index c12fb4822cf9..319988c1a6b8 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Neðri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vinstri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Hægri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Vinnuskjámyndir eru vistaðar í forritinu <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Skrár"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Skjáupptaka"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Vinnur úr skjáupptöku"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Áframhaldandi tilkynning fyrir skjáupptökulotu"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Sjálfvirk"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ekkert hljóð eða titringur"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ekkert hljóð eða titringur og birtist neðar í samtalshluta"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Gæti hringt eða titrað eftir stillingum símans"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Gæti hringt eða titrað eftir stillingum símans. Samtöl á <xliff:g id="APP_NAME">%1$s</xliff:g> birtast sjálfkrafa í blöðru."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Gæti hringt eða titrað en það fer eftir stillingum tækisins"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Gæti hringt eða titrað en það fer eftir stillingum tækisins. Samtöl frá <xliff:g id="APP_NAME">%1$s</xliff:g> birtast sjálfkrafa í blöðru."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Láta kerfið ákvarða hvort hljóð eða titringur fylgir þessari tilkynningu"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Staða:</b> gerð sjálfgefin"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Staða:</b> var gerð þögul"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Veldu forrit til að bæta við stýringum"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# stýringu bætt við.}one{# stýringu bætt við.}other{# stýringum bætt við.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Fjarlægt"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Eftirlæti"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Eftirlæti, staða <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Fjarlægt úr eftirlæti"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> í <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Afturkalla"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Færðu nær til að spila í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Farðu nær <xliff:g id="DEVICENAME">%1$s</xliff:g> til að spila hér"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Í spilun í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Eitthvað fór úrskeiðis. Reyndu aftur."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Hleður"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"spjaldtölva"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Óvirkt, athugaðu forrit"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Fannst ekki"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Stýring er ekki tiltæk"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Villa, reyndu aftur"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Bæta við stýringum"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Breyta stýringum"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Bæta við úttaki"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Hópur"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 tæki valið"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hljóðstyrkur"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hátalarar og skjáir"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Tillögur að tækjum"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Svona virkar útsending"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Útsending"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Fólk nálægt þér með samhæf Bluetooth-tæki getur hlustað á efnið sem þú sendir út"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Slökkt verður á þessum skjá"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Samanbrjótanlegt tæki opnað"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Samanbrjótanlegu tæki snúið við"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> hleðsla eftir á rafhlöðu"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Tengdu pennann við hleðslutæki"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Rafhlaða pennans er að tæmast"</string> + <string name="video_camera" msgid="7654002575156149298">"Kvikmyndatökuvél"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Ekki er hægt að hringja úr þessu sniði"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Vinnureglur gera þér aðeins kleift að hringja símtöl úr vinnusniði"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Skipta yfir í vinnusnið"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Loka"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 5c52bc13974e..fffb8d69bef4 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inferiore, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite sinistro, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite destro, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Gli screenshot acquisiti nel profilo di lavoro sono salvati nell\'app <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"File"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Registrazione dello schermo"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Elaboraz. registraz. schermo"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifica costante per una sessione di registrazione dello schermo"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatico"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Nessun suono o vibrazione"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Nessun suono o vibrazione e appare più in basso nella sezione delle conversazioni"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Può suonare o vibrare in base alle impostazioni del telefono"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Può suonare o vibrare in base alle impostazioni del telefono. Le conversazioni di <xliff:g id="APP_NAME">%1$s</xliff:g> appaiono come bolla per impostazione predefinita."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Potrebbero essere attivati lo squillo o la vibrazione in base alle impostazioni del dispositivo"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Potrebbero essere attivati lo squillo o la vibrazione in base alle impostazioni del dispositivo. Conversazioni dalla bolla <xliff:g id="APP_NAME">%1$s</xliff:g> per impostaz. predefinita."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Fai stabilire al sistema se questa notifica deve emettere suoni o vibrazioni"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Stato:</b> promossa a Predefinita"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Stato:</b> retrocessa a Silenziosa"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Scegli un\'app per aggiungere controlli"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# controllo aggiunto.}many{# controlli aggiunti.}other{# controlli aggiunti.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Rimosso"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Vuoi aggiungere <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Se la aggiungi, l\'app <xliff:g id="APPNAME">%s</xliff:g> può aggiungere controlli e contenuti a questo riquadro. In alcune app puoi scegliere quali controlli visualizzare qui."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Aggiunto ai preferiti"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Preferito, posizione <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Rimosso dai preferiti"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> da <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Annulla"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Avvicinati per riprodurre su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Per riprodurre qui i contenuti, avvicinati a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"In riproduzione su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Si è verificato un errore. Riprova."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Caricamento in corso…"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inattivo, controlla l\'app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Controllo non trovato"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Il controllo non è disponibile"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Errore, riprova"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Aggiungi controlli"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Modifica controlli"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Aggiungi app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Aggiungi uscite"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selezionato"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speaker e display"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivi consigliati"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Come funziona la trasmissione"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Annuncio"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Le persone vicine a te che hanno dispositivi Bluetooth compatibili possono ascoltare i contenuti multimediali che stai trasmettendo"</string> @@ -1018,13 +1016,19 @@ <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Annulla"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Gira ora"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Apri il telefono per un selfie migliore"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Girare su display frontale per un selfie migliore?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Passare al display frontale per un selfie migliore?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Utilizza la fotocamera posteriore per una foto più ampia con maggiore risoluzione."</string> - <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Questo schermo verrà disattivato"</b></string> + <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Questo schermo verrà spento"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pieghevole che viene aperto"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pieghevole che viene capovolto"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteria rimanente"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connetti lo stilo a un caricabatterie"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Batteria stilo in esaurimento"</string> + <string name="video_camera" msgid="7654002575156149298">"Videocamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Impossibile chiamare da questo profilo"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Le norme di lavoro ti consentono di fare telefonate soltanto dal profilo di lavoro"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Passa a profilo di lavoro"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Chiudi"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index de95f2e372ae..dfe8a6459499 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"באופן אוטומטי"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"ללא צליל או רטט"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ללא צליל או רטט ומופיעה למטה בקטע התראות השיחה"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ייתכן שיופעל צלצול או רטט בהתאם להגדרות הטלפון"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ייתכן שיופעל צלצול או רטט בהתאם להגדרות הטלפון. שיחות מהאפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מופיעות בבועות כברירת מחדל."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"ייתכן שיופעל צלצול או רטט בהתאם להגדרות במכשיר"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ייתכן שיופעל צלצול או רטט בהתאם להגדרות במכשיר. שיחות מהאפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מופיעות בבועות כברירת מחדל."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"אפשר לתת למערכת לקבוע אם ההתראה הזאת צריכה להיות מלווה בצליל או ברטט"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>הסטטוס:</b> הועלה בדרגה ל\'ברירת מחדל\'"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>הסטטוס:</b> הורד בדרגה ל\'שקט\'"</string> @@ -800,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"יש לבחור אפליקציה כדי להוסיף פקדים"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{נוסף אמצעי בקרה אחד (#).}one{נוספו # אמצעי בקרה.}two{נוספו # אמצעי בקרה.}other{נוספו # אמצעי בקרה.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"הוסר"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"סומן כמועדף"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"סומן כמועדף, במיקום <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"הוסר מהמועדפים"</string> @@ -852,8 +856,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> מ-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ביטול"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"צריך להתקרב כדי להפעיל מדיה במכשיר <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"כדי להפעיל במכשיר הזה, יש להתקרב אל <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"פועלת ב-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"משהו השתבש. יש לנסות שוב."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"בטעינה"</string> @@ -867,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"שגיאה, יש לנסות שוב"</string> <string name="controls_menu_add" msgid="4447246119229920050">"הוספת פקדים"</string> <string name="controls_menu_edit" msgid="890623986951347062">"עריכת פקדים"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"הוספת מכשירי פלט"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"קבוצה"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"נבחר מכשיר אחד"</string> @@ -1012,15 +1017,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• יש לפחות מכשיר אחד זמין"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"מקש קיצור ללחיצה ארוכה"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"ביטול"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"הפכת את המכשיר"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"אני רוצה להפוך"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"כדי לצלם תמונת סלפי טובה יותר, פותחים את הטלפון"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"להפוך למסך הקדמי כדי לצלם תמונת סלפי טובה יותר?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"במצלמה האחורית אפשר לצלם תמונה רחבה יותר ברזולוציה גבוהה יותר."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ המסך יכבה"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"מכשיר מתקפל עובר למצב לא מקופל"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"מכשיר מתקפל עובר למצב מהופך"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"רמת הטעינה שנותרה בסוללה: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"כדאי לחבר את הסטיילוס למטען"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"הסוללה של הסטיילוס חלשה"</string> + <string name="video_camera" msgid="7654002575156149298">"מצלמת וידאו"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"אי אפשר להתקשר מהפרופיל הזה"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"המדיניות של מקום העבודה מאפשרת לך לבצע שיחות טלפון רק מפרופיל העבודה"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"מעבר לפרופיל עבודה"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"סגירה"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 3d4551c4b747..7db687ad6c2e 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"下部の境界線 <xliff:g id="PERCENT">%1$d</xliff:g> パーセント"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左の境界線 <xliff:g id="PERCENT">%1$d</xliff:g> パーセント"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右の境界線 <xliff:g id="PERCENT">%1$d</xliff:g> パーセント"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"仕事用のスクリーンショットは <xliff:g id="APP">%1$s</xliff:g> アプリに保存されます"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ファイル"</string> <string name="screenrecord_name" msgid="2596401223859996572">"スクリーン レコーダー"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"画面の録画を処理しています"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"画面の録画セッション中の通知"</string> @@ -449,7 +447,7 @@ <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"字幕のオーバーレイ"</string> <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"有効にする"</string> <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"無効にする"</string> - <string name="sound_settings" msgid="8874581353127418308">"サウンドとバイブレーション"</string> + <string name="sound_settings" msgid="8874581353127418308">"音とバイブレーション"</string> <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"設定"</string> <string name="screen_pinning_title" msgid="9058007390337841305">"アプリは固定されています"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"固定を解除するまで画面が常に表示されるようになります。[戻る] と [最近] を同時に押し続けると固定が解除されます。"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"自動"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"着信音もバイブレーションも無効になります"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"着信音もバイブレーションも無効になり会話セクションの下に表示されます"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"スマートフォンの設定を基に着信音またはバイブレーションが有効になります"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"スマートフォンの設定を基に着信音またはバイブレーションが有効になります。デフォルトでは <xliff:g id="APP_NAME">%1$s</xliff:g> からの会話がバブルとして表示されます。"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"デバイスの設定を基に着信音またはバイブレーションが有効になります"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"デバイスの設定を基に着信音またはバイブレーションが有効になります。デフォルトでは <xliff:g id="APP_NAME">%1$s</xliff:g> からの会話がふきだしで表示されます。"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"この通知を音またはバイブレーションで知らせるかどうかの自動判断"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>ステータス:</b> ランクがデフォルトに上がりました"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>ステータス:</b> ランクがサイレントに下がりました"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"コントロールを追加するアプリの選択"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# 件のコントロールを追加しました。}other{# 件のコントロールを追加しました。}}"</string> <string name="controls_removed" msgid="3731789252222856959">"削除済み"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> を追加しますか?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> を追加することで、コントロールやコンテンツをこのパネルに追加できます。一部のアプリでは、ここに表示されるコントロールを選択できます。"</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"お気に入りに追加済み"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"お気に入りに追加済み、位置: <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"お気に入りから削除済み"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> を <xliff:g id="APP_LABEL">%2$s</xliff:g> で再生"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"元に戻す"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生するにはもっと近づけてください"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"このデバイスで再生するには、<xliff:g id="DEVICENAME">%1$s</xliff:g> にもっと近づけてください"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生しています"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"エラーが発生しました。もう一度お試しください。"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"読み込んでいます"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"タブレット"</string> <string name="controls_error_timeout" msgid="794197289772728958">"無効: アプリをご確認ください"</string> <string name="controls_error_removed" msgid="6675638069846014366">"見つかりませんでした"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"コントロールを使用できません"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"エラー: もう一度お試しください"</string> <string name="controls_menu_add" msgid="4447246119229920050">"コントロールを追加"</string> <string name="controls_menu_edit" msgid="890623986951347062">"コントロールを編集"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"アプリを追加"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"出力の追加"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"グループ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"選択したデバイス: 1 台"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"音量"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"スピーカーとディスプレイ"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"デバイスの候補"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ブロードキャストの仕組み"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"ブロードキャスト"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Bluetooth 対応デバイスを持っている付近のユーザーは、あなたがブロードキャストしているメディアを聴けます"</string> @@ -1016,15 +1014,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• 利用できるデバイスが 1 台以上ある"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"ショートカットの長押しが必要です"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"キャンセル"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"切り替えましょう"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"切り替える"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"高画質で撮るにはスマートフォンを開いてください"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"前面ディスプレイに切り替えて綺麗に撮りましょう"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"高解像度で広い範囲を撮影するには、背面カメラを使用してください。"</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱この画面は OFF になります"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"折りたたみ式デバイスが広げられている"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"折りたたみ式デバイスがひっくり返されている"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"バッテリー残量 <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"タッチペンを充電器に接続してください"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"タッチペンのバッテリー残量が少なくなっています"</string> + <string name="video_camera" msgid="7654002575156149298">"ビデオカメラ"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"このプロファイルからは通話を発信できません"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"仕事用ポリシーでは、通話の発信を仕事用プロファイルからのみに制限できます"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"仕事用プロファイルに切り替える"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"閉じる"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 61280d3d425f..f4c934cbad09 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ავტომატური"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"ხმისა და ვიბრაციის გარეშე"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ხმისა და ვიბრაციის გარეშე, ჩნდება მიმოწერების სექციის ქვედა ნაწილში"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"დარეკვა ან ვიბრაცია ტელეფონის პარამეტრების მიხედვით"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"დარეკვა ან ვიბრაცია ტელეფონის პარამეტრების მიხედვით. მიმოწერები <xliff:g id="APP_NAME">%1$s</xliff:g>-ის ბუშტიდან, ნაგულისხმევად."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"დარეკვა ან ვიბრაცია მოწყობილობის პარამეტრების მიხედვით"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"დარეკვა ან ვიბრაცია მოწყობილობის პარამეტრების მიხედვით. მიმოწერები <xliff:g id="APP_NAME">%1$s</xliff:g>-ის ბუშტიდან, ნაგულისხმევად."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"სისტემისთვის ისეთი უფლების მინიჭება, რომ მან განსაზღვროს, ამ შეტყობინებამ ხმოვანი სიგნალი უნდა აამოქმედოს თუ ვიბრაცია"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>სტატუსი:</b> ნაგულისხმევად გარდაქმნილი"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>სტატუსი:</b> „უხმო“ სტატუსზე გადასული"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"აირჩიეთ აპი მართვის საშუალებების დასამატებლად"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{დაემატა მართვის # საშუალება.}other{დაემატა მართვის # საშუალება.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"ამოიშალა"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"გსურთ <xliff:g id="APPNAME">%s</xliff:g>-ის დამატება?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"როდესაც <xliff:g id="APPNAME">%s</xliff:g>-ს ამატებთ, მან შეიძლება დაამატოს მართვის საშუალებები და კონტენტი მოცემულ არეში. ზოგიერთ აპში შეგიძლიათ აირჩიოთ, რომელი მართვის საშუალებები უნდა გამოჩნდეს აქ."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"რჩეულებშია"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"რჩეულებშია, პოზიციაზე <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"რჩეულებიდან ამოღებულია"</string> @@ -850,11 +852,10 @@ <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"გახსენით <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g>-დან"</string> - <string name="media_transfer_undo" msgid="1895606387620728736">"მოქმედების გაუქმება"</string> + <string name="media_transfer_undo" msgid="1895606387620728736">"მოქმედ.გაუქმება"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"მიიტანეთ უფრო ახლოს, რომ დაუკრათ <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> - <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"მიმდინარეობს დაკვრა <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"აქ სათამაშოდ, მიუახლოვდით <xliff:g id="DEVICENAME">%1$s</xliff:g>-ს"</string> + <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"უკრავს <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"რაღაც შეცდომა მოხდა. ცადეთ ხელახლა."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"იტვირთება"</string> <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ტაბლეტი"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"შეცდომა, ისევ ცადეთ"</string> <string name="controls_menu_add" msgid="4447246119229920050">"მართვის საშუალებების დამატება"</string> <string name="controls_menu_edit" msgid="890623986951347062">"მართვის საშუალებათა რედაქტირება"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"აპის დამატება"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"მედია-გამოსავლების დამატება"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"ჯგუფი"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"არჩეულია 1 მოწყობილობა"</string> @@ -1012,15 +1014,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• ხელმისაწვდომია მინიმუმ ერთი მოწყობილობა"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"შეხების დაamp; მოცდის მალსახმობი"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"გაუქმება"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"გადაატრიალეთ ახლა"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"ახლა გადატრიალება"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"გაშალეთ ტელეფონი უკეთესი სელფისთვის"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"გადააბრუნეთ წინა ეკრანზე უკეთესი სელფის მისაღებად?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"გამოვიყენოთ წინა ეკრანი უკეთესი სელფის მისაღებად?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"გამოიყენეთ უკანა კამერა უფრო ფართო ფოტოს გადასაღებად მაღალი გარჩევადობით."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ეს ეკრანი გამოირთვება"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"დასაკეცი მოწყობილობა იხსნება"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"დასაკეცი მოწყობილობა ტრიალებს"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"დარჩენილია ბატარეის <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"დააკავშირეთ თქვენი სტილუსი დამტენს"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"სტილუსის ბატარეა დაცლის პირასაა"</string> + <string name="video_camera" msgid="7654002575156149298">"ვიდეოკამერა"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"ამ პროფილიდან დარეკვა ვერ ხერხდება"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"თქვენი სამსახურის წესები საშუალებას გაძლევთ, სატელეფონო ზარები განახორციელოთ მხოლოდ სამსახურის პროფილიდან"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"სამსახურის პროფილზე გადართვა"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"დახურვა"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 1e96422b7946..a3b330ce2905 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Төменгі шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Сол жақ шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Оң жақ шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Жұмыс профилінен алынған скриншоттар <xliff:g id="APP">%1$s</xliff:g> қолданбасында сақталады."</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Экран жазғыш"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Экран жазғыш бейнесін өңдеу"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды бейнеге жазудың ағымдағы хабарландыруы"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматты"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Дыбыс не діріл болмайды."</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Дыбыс не діріл болмайды, әңгімелер бөлімінің төмен жағында тұрады."</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Телефон параметрлеріне байланысты дыбыстық сигнал не діріл болуы мүмкін."</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Телефон параметрлеріне байланысты дыбыстық сигнал не діріл болуы мүмкін. <xliff:g id="APP_NAME">%1$s</xliff:g> әңгімелері әдепкісінше қалқып шығады."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Құрылғы параметрлеріне байланысты шырылдауы не дірілдеуі мүмкін"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Құрылғы параметрлеріне байланысты шырылдауы не дірілдеуі мүмкін. <xliff:g id="APP_NAME">%1$s</xliff:g> чаттары әдепкісінше қалқымалы етіп көрсетіледі."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Хабарландыру дыбысының немесе дірілдің қосылуын жүйе анықтайтын болады"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Күйі:</b> \"Әдепкі\" санатына көтерілген"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Күйі:</b> \"Үнсіз\" санатына төмендетілген"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Басқару элементтері қосылатын қолданбаны таңдаңыз"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# басқару элементі қосылды.}other{# басқару элементі қосылды.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Өшірілді"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Таңдаулыларға қосылды"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Таңдаулыларға қосылды, <xliff:g id="NUMBER">%d</xliff:g>-позиция"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Таңдаулылардан алып тасталды"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> қолданбасында \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Қайтару"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында музыка ойнату үшін оған жақындаңыз."</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Осы жерде ойнату үшін <xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысына жақындаңыз."</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында ойнатылуда."</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Бірдеңе дұрыс болмады. Қайталап көріңіз."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Жүктеліп жатыр"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Өшірулі. Қолданба тексеріңіз."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Табылмады"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Басқару виджеті қолжетімсіз"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Қате шықты. Қайталап көріңіз."</string> <string name="controls_menu_add" msgid="4447246119229920050">"Басқару элементтерін қосу"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Басқару элементтерін өзгерту"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Шығыс сигналдарды қосу"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 құрылғы таңдалды."</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Дыбыс деңгейі"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Динамиктер мен дисплейлер"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ұсынылған құрылғылар"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Тарату қалай жүзеге асады"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Тарату"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Үйлесімді Bluetooth құрылғылары бар маңайдағы адамдар сіз таратып жатқан медиамазмұнды тыңдай алады."</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Бұл экран өшіріледі."</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Бүктемелі құрылғы ашылып жатыр."</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Бүктемелі құрылғы аударылып жатыр."</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Қалған батарея заряды: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Стилусты зарядтағышқа жалғаңыз."</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Стилус батареясының заряды аз"</string> + <string name="video_camera" msgid="7654002575156149298">"Бейнекамера"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Бұл профильден қоңырау шалу мүмкін емес"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Жұмыс саясатыңызға сәйкес тек жұмыс профилінен қоңырау шалуға болады."</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Жұмыс профиліне ауысу"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Жабу"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 25598777ed21..05d0429b809c 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"បន្ទាត់បែងចែកខាងក្រោម <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"បន្ទាត់បែងចែកខាងឆ្វេង <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"បន្ទាត់បែងចែកខាងស្ដាំ <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"រូបថតអេក្រង់ក្នុងកម្រងព័ត៌មានការងារត្រូវបានរក្សាទុកនៅក្នុងកម្មវិធី <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ឯកសារ"</string> <string name="screenrecord_name" msgid="2596401223859996572">"មុខងារថតវីដេអូអេក្រង់"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"កំពុងដំណើរការការថតអេក្រង់"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"ការជូនដំណឹងដែលកំពុងដំណើរការសម្រាប់រយៈពេលប្រើការថតសកម្មភាពអេក្រង់"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ស្វ័យប្រវត្តិ"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"គ្មានសំឡេង ឬការញ័រទេ"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"គ្មានសំឡេងឬការញ័រ និងបង្ហាញទាបជាងនៅក្នុងផ្នែកសន្ទនា"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"អាចរោទ៍ ឬញ័រ ដោយផ្អែកលើការកំណត់ទូរសព្ទ"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"អាចរោទ៍ ឬញ័រ ដោយផ្អែកលើការកំណត់ទូរសព្ទ។ ការសន្ទនាពីពពុះ <xliff:g id="APP_NAME">%1$s</xliff:g> តាមលំនាំដើម។"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"អាចរោទ៍ ឬញ័រ ដោយផ្អែកលើការកំណត់ឧបករណ៍"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"អាចរោទ៍ ឬញ័រ ដោយផ្អែកលើការកំណត់ឧបករណ៍។ ការសន្ទនាពីផ្ទាំងអណ្ដែត <xliff:g id="APP_NAME">%1$s</xliff:g> តាមលំនាំដើម។"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ឱ្យប្រព័ន្ធកំណត់ថាតើការជូនដំណឹងនេះគួរតែបន្លឺសំឡេង ឬញ័រ"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>ស្ថានភាព៖</b> បានដំឡើងទៅលំនាំដើម"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>ស្ថានភាព៖</b> បានបញ្ចុះទៅស្ងាត់"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"ជ្រើសរើសកម្មវិធីដែលត្រូវបញ្ចូលផ្ទាំងគ្រប់គ្រង"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{បានបញ្ចូលការគ្រប់គ្រង #។}other{បានបញ្ចូលការគ្រប់គ្រង #។}}"</string> <string name="controls_removed" msgid="3731789252222856959">"បានដកចេញ"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"បានដាក់ជាសំណព្វ"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"បានដាក់ជាសំណព្វ ទីតាំងទី <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"បានដកចេញពីសំណព្វ"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ពី <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ត្រឡប់វិញ"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"រំកិលឱ្យកាន់តែជិត ដើម្បីចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ដើម្បីចាក់នៅទីនេះ សូមខិតទៅជិត <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"កំពុងចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"មានអ្វីមួយខុសប្រក្រតី។ សូមព្យាយាមម្ដងទៀត។"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"កំពុងផ្ទុក"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ថេប្លេត"</string> <string name="controls_error_timeout" msgid="794197289772728958">"អសកម្ម ពិនិត្យមើលកម្មវិធី"</string> <string name="controls_error_removed" msgid="6675638069846014366">"រកមិនឃើញទេ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"មិនអាចគ្រប់គ្រងបានទេ"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"មានបញ្ហា សូមព្យាយាមម្តងទៀត"</string> <string name="controls_menu_add" msgid="4447246119229920050">"បញ្ចូលផ្ទាំងគ្រប់គ្រង"</string> <string name="controls_menu_edit" msgid="890623986951347062">"កែផ្ទាំងគ្រប់គ្រង"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"បញ្ចូលឧបករណ៍មេឌៀ"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"ក្រុម"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"បានជ្រើសរើសឧបករណ៍ 1"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"កម្រិតសំឡេង"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ឧបករណ៍បំពងសំឡេង និងផ្ទាំងអេក្រង់"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ឧបករណ៍ដែលបានណែនាំ"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"របៀបដែលការផ្សាយដំណើរការ"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"ការផ្សាយ"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"មនុស្សនៅជិតអ្នកដែលមានឧបករណ៍ប៊្លូធូសត្រូវគ្នាអាចស្តាប់មេឌៀដែលអ្នកកំពុងផ្សាយបាន"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ អេក្រង់នេះនឹងបិទ"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ឧបករណ៍អាចបត់បានកំពុងត្រូវបានលា"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ឧបករណ៍អាចបត់បានកំពុងត្រូវបានលា"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ថ្មនៅសល់ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ភ្ជាប់ប៊ិករបស់អ្នកជាមួយឆ្នាំងសាក"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"ថ្មប៊ិកនៅសល់តិច"</string> + <string name="video_camera" msgid="7654002575156149298">"កាមេរ៉ាវីដេអូ"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"មិនអាចហៅទូរសព្ទពីកម្រងព័ត៌មាននេះបានទេ"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"គោលការណ៍ការងាររបស់អ្នកអនុញ្ញាតឱ្យអ្នកធ្វើការហៅទូរសព្ទបានតែពីកម្រងព័ត៌មានការងារប៉ុណ្ណោះ"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"ប្ដូរទៅកម្រងព័ត៌មានការងារ"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"បិទ"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 12d96c342647..e5437804f00a 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ಕೆಳಗಿನ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ಎಡಭಾಗದ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ಬಲಭಾಗದ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"<xliff:g id="APP">%1$s</xliff:g> ಆ್ಯಪ್ನಲ್ಲಿ ಉಳಿಸಲಾದ ಕೆಲಸದ ಸ್ಕ್ರೀನ್ಶಾಟ್ಗಳು"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ಫೈಲ್ಗಳು"</string> <string name="screenrecord_name" msgid="2596401223859996572">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡರ್"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಆಗುತ್ತಿದೆ"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೆಶನ್ಗಾಗಿ ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಅಧಿಸೂಚನೆ"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ಸ್ವಯಂಚಾಲಿತ"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"ಯಾವುದೇ ಧ್ವನಿ ಅಥವಾ ವೈಬ್ರೇಷನ್ ಆಗುವುದಿಲ್ಲ"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ಯಾವುದೇ ಧ್ವನಿ ಅಥವಾ ವೈಬ್ರೇಷನ್ ಆಗುವುದಿಲ್ಲ, ಸಂಭಾಷಣೆ ವಿಭಾಗದ ಕೆಳಭಾಗದಲ್ಲಿ ಗೋಚರಿಸುತ್ತದೆ"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಆಧರಿಸಿ ಫೋನ್ ರಿಂಗ್ ಅಥವಾ ವೈಬ್ರೇಟ್ ಆಗುತ್ತದೆ"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಆಧರಿಸಿ ಫೋನ್ ರಿಂಗ್ ಅಥವಾ ವೈಬ್ರೇಟ್ ಆಗುತ್ತದೆ. ಡಿಫಾಲ್ಟ್ ಆಗಿ, <xliff:g id="APP_NAME">%1$s</xliff:g> ನ ಬಬಲ್ ಸಂಭಾಷಣೆಗಳು."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಆಧರಿಸಿ ಸಾಧನ ರಿಂಗ್ ಅಥವಾ ವೈಬ್ರೇಟ್ ಆಗುತ್ತದೆ"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಆಧರಿಸಿ ಫೋನ್ ರಿಂಗ್ ಅಥವಾ ವೈಬ್ರೇಟ್ ಆಗುತ್ತದೆ. ಡಿಫಾಲ್ಟ್ ಆಗಿ, <xliff:g id="APP_NAME">%1$s</xliff:g> ಬಬಲ್ ಸಂಭಾಷಣೆಗಳು."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ಈ ಅಧಿಸೂಚನೆಯು ಶಬ್ದ ಮಾಡಬೇಕೇ ಅಥವಾ ವೈಬ್ರೇಟ್ ಮಾಡಬೇಕೇ ಎಂಬುದನ್ನು ನಿರ್ಧರಿಸುವ ಅವಕಾಶವನ್ನು ಸಿಸ್ಟಂಗೆ ನೀಡಿ"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>ಸ್ಥಿತಿ:</b> ಡೀಫಾಲ್ಟ್ಗೆ ಬಡ್ತಿ ಹೊಂದಿದೆ"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>ಸ್ಥಿತಿ:</b> ಸೈಲೆಂಟ್ಗೆ ಕೆಳದರ್ಜೆಗೆ ಇಳಿದಿದೆ"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಲು ಆ್ಯಪ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ನಿಯಂತ್ರಣವನ್ನು ಸೇರಿಸಲಾಗಿದೆ.}one{# ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ.}other{# ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> ಅನ್ನು ಸೇರಿಸಬೇಕೆ?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"ನೀವು <xliff:g id="APPNAME">%s</xliff:g> ಅನ್ನು ಸೇರಿಸಿದಾಗ, ಅದು ಈ ಪ್ಯಾನೆಲ್ಗೆ ನಿಯಂತ್ರಣಗಳು ಮತ್ತು ವಿಷಯವನ್ನು ಸೇರಿಸಬಹುದು. ಕೆಲವು ಆ್ಯಪ್ಗಳಲ್ಲಿ, ಇಲ್ಲಿ ಯಾವ ನಿಯಂತ್ರಣಗಳು ಕಾಣಿಸಬೇಕು ಎಂಬುದನ್ನು ನೀವು ಆಯ್ಕೆಮಾಡಬಹುದು."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"ಮೆಚ್ಚಲಾಗಿರುವುದು"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ಮೆಚ್ಚಲಾಗಿರುವುದು, ಸ್ಥಾನ <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ಮೆಚ್ಚಿನದಲ್ಲದ್ದು"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%2$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ರದ್ದುಗೊಳಿಸಿ"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು ಅದರ ಹತ್ತಿರಕ್ಕೆ ಸರಿಯಿರಿ"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ಇಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು, <xliff:g id="DEVICENAME">%1$s</xliff:g> ಸಮೀಪಕ್ಕೆ ಸರಿಯಿರಿ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಆಗುತ್ತಿದೆ"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"ಏನೋ ತಪ್ಪಾಗಿದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"ಲೋಡ್ ಆಗುತ್ತಿದೆ"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ಟ್ಯಾಬ್ಲೆಟ್"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ಕಂಡುಬಂದಿಲ್ಲ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ನಿಯಂತ್ರಣ ಲಭ್ಯವಿಲ್ಲ"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"ದೋಷ, ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ"</string> <string name="controls_menu_add" msgid="4447246119229920050">"ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಿ"</string> <string name="controls_menu_edit" msgid="890623986951347062">"ನಿಯಂತ್ರಣಗಳನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ಆ್ಯಪ್ ಅನ್ನು ಸೇರಿಸಿ"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ಔಟ್ಪುಟ್ಗಳನ್ನು ಸೇರಿಸಿ"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"ಗುಂಪು"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ಸಾಧನವನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ವಾಲ್ಯೂಮ್"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ಸ್ಪೀಕರ್ಗಳು ಮತ್ತು ಡಿಸ್ಪ್ಲೇಗಳು"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ಸೂಚಿಸಿದ ಸಾಧನಗಳು"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ಪ್ರಸಾರವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"ಪ್ರಸಾರ"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ಹೊಂದಾಣಿಕೆಯಾಗುವ ಬ್ಲೂಟೂತ್ ಸಾಧನಗಳನ್ನು ಹೊಂದಿರುವ ಸಮೀಪದಲ್ಲಿರುವ ಜನರು ನೀವು ಪ್ರಸಾರ ಮಾಡುತ್ತಿರುವ ಮಾಧ್ಯಮವನ್ನು ಆಲಿಸಬಹುದು"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ಈ ಸ್ಕ್ರೀನ್ ಆಫ್ ಆಗುತ್ತದೆ"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಅನ್ಫೋಲ್ಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಸುತ್ತಲೂ ತಿರುಗಿಸಲಾಗುತ್ತಿದೆ"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ಬ್ಯಾಟರಿ ಉಳಿದಿದೆ"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ನಿಮ್ಮ ಸ್ಟೈಲಸ್ ಅನ್ನು ಚಾರ್ಜರ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"ಸ್ಟೈಲಸ್ ಬ್ಯಾಟರಿ ಕಡಿಮೆಯಿದೆ"</string> + <string name="video_camera" msgid="7654002575156149298">"ವೀಡಿಯೊ ಕ್ಯಾಮರಾ"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"ಈ ಪ್ರೊಫೈಲ್ನಿಂದ ಕರೆ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"ನಿಮ್ಮ ಕೆಲಸದ ನೀತಿಯು ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ನಿಂದ ಮಾತ್ರ ಫೋನ್ ಕರೆಗಳನ್ನು ಮಾಡಲು ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ಗೆ ಬದಲಿಸಿ"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"ಮುಚ್ಚಿರಿ"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index e42e10ef753a..ad5b52335aae 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"하단 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"왼쪽 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"오른쪽 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"직장 프로필의 스크린샷은 <xliff:g id="APP">%1$s</xliff:g> 앱에 저장됩니다."</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"파일"</string> <string name="screenrecord_name" msgid="2596401223859996572">"화면 녹화"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"화면 녹화 처리 중"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"화면 녹화 세션에 관한 지속적인 알림"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"자동"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"소리 또는 진동 없음"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"소리나 진동이 울리지 않으며 대화 섹션 하단에 표시됨"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"휴대전화 설정에 따라 벨소리나 진동이 울릴 수 있음"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"휴대전화 설정에 따라 벨소리나 진동이 울릴 수 있습니다. 기본적으로 <xliff:g id="APP_NAME">%1$s</xliff:g>의 대화는 대화창으로 표시됩니다."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"기기 설정에 따라 벨소리나 진동이 울릴 수 있음"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"기기 설정에 따라 벨소리나 진동이 울릴 수 있습니다. 기본적으로 <xliff:g id="APP_NAME">%1$s</xliff:g>의 대화는 대화창으로 표시됩니다."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"시스템에서 알림 시 소리 또는 진동을 사용할지 결정하도록 허용합니다."</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>상태:</b> 기본으로 높임"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>상태:</b> 무음으로 낮춤"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"컨트롤을 추가할 앱을 선택하세요"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{설정이 #개 추가되었습니다.}other{설정이 #개 추가되었습니다.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"삭제됨"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"즐겨찾기에 추가됨"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"즐겨찾기에 추가됨, 위치 <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"즐겨찾기에서 삭제됨"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>에서 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"실행취소"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생하려면 기기를 더 가까이로 옮기세요."</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"여기에서 재생하려면 <xliff:g id="DEVICENAME">%1$s</xliff:g>에 더 가까이 이동하세요."</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생 중"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"문제가 발생했습니다. 다시 시도해 주세요."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"로드 중"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"태블릿"</string> <string name="controls_error_timeout" msgid="794197289772728958">"비활성. 앱을 확인하세요."</string> <string name="controls_error_removed" msgid="6675638069846014366">"찾을 수 없음"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"컨트롤을 사용할 수 없음"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"오류. 다시 시도하세요."</string> <string name="controls_menu_add" msgid="4447246119229920050">"컨트롤 추가"</string> <string name="controls_menu_edit" msgid="890623986951347062">"컨트롤 수정"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"출력 추가"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"그룹"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"기기 1대 선택됨"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"볼륨"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"스피커 및 디스플레이"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"추천 기기"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"브로드캐스팅 작동 원리"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"브로드캐스트"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"호환되는 블루투스 기기를 가진 근처의 사용자가 내가 브로드캐스트 중인 미디어를 수신 대기할 수 있습니다."</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ 이 화면이 꺼집니다."</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"폴더블 기기를 펼치는 모습"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"폴더블 기기를 뒤집는 모습"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"배터리 <xliff:g id="PERCENTAGE">%s</xliff:g> 남음"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"스타일러스를 충전기에 연결하세요"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"스타일러스 배터리 부족"</string> + <string name="video_camera" msgid="7654002575156149298">"비디오 카메라"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"이 프로필에서 전화를 걸 수 없음"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"직장 정책이 직장 프로필에서만 전화를 걸도록 허용합니다."</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"직장 프로필로 전환"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"닫기"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index ec9e96200816..5c6166af7ada 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ылдый жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Сол жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Оң жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Жумуш скриншоттору <xliff:g id="APP">%1$s</xliff:g> колдонмосунда сакталат"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"экрандан видео жаздырып алуу"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Экрандан жаздырылып алынган видео иштетилүүдө"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды жаздыруу сеансы боюнча учурдагы билдирме"</string> @@ -426,7 +424,7 @@ <string name="monitoring_button_view_policies" msgid="3869724835853502410">"Саясаттарды карап көрүү"</string> <string name="monitoring_button_view_controls" msgid="8316440345340701117">"Башкаруу элементтерин көрүү"</string> <string name="monitoring_description_named_management" msgid="505833016545056036">"Бул түзмөк <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> уюмуна таандык.\n\nАдминистраторуңуз бул түзмөктөгү жөндөөлөрдү, корпоративдик ресурстарды пайдалануу мүмкүнчүлүгүн берген параметрлерди жана колдонмолорду, түзмөгүңүзгө байланыштуу маалыматтарды (мисалы, түзмөгүңүздүн жайгашкан жери сыяктуу) көзөмөлдөп башкара алат.\n\nТолугураак маалымат алуу үчүн IT администраторуңузга кайрылыңыз."</string> - <string name="monitoring_financed_description_named_management" msgid="6108439201399938668">"<xliff:g id="ORGANIZATION_NAME_0">%1$s</xliff:g> бул түзмөк менен байланышкан маалыматты көрүп, колдонмолорду башкарып, анын жөндөөлөрүн өзгөртө алат.\n\nЭгер суроолоруңуз болсо, <xliff:g id="ORGANIZATION_NAME_1">%2$s</xliff:g> уюмуна кайрылыңыз."</string> + <string name="monitoring_financed_description_named_management" msgid="6108439201399938668">"<xliff:g id="ORGANIZATION_NAME_0">%1$s</xliff:g> бул түзмөк менен байланышкан маалыматты көрүп, колдонмолорду башкарып, анын параметрлерин өзгөртө алат.\n\nЭгер суроолоруңуз болсо, <xliff:g id="ORGANIZATION_NAME_1">%2$s</xliff:g> уюмуна кайрылыңыз."</string> <string name="monitoring_description_management" msgid="4308879039175729014">"Бул түзмөк уюмуңузга таандык.\n\nАдминистраторуңуз бул түзмөктөгү жөндөөлөрдү, корпоративдик ресурстарды пайдалануу мүмкүнчүлүгүн берген параметрлерди жана колдонмолорду, түзмөгүңүзгө байланыштуу маалыматтарды (мисалы, түзмөгүңүздүн жайгашкан жери сыяктуу) көзөмөлдөп башкара алат.\n\nТолугураак маалымат алуу үчүн IT администраторуңузга кайрылыңыз."</string> <string name="monitoring_description_management_ca_certificate" msgid="7785013130658110130">"Ишканаңыз бул түзмөккө тастыктоочу борборду орнотту. Коопсуз тармагыңыздын трафиги көзөмөлдөнүп же өзгөртүлүшү мүмкүн."</string> <string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"Ишканаңыз жумуш профилиңизге тастыктоочу борборду орнотту. Коопсуз тармагыңыздын трафиги көзөмөлдөнүп же өзгөртүлүшү мүмкүн."</string> @@ -443,7 +441,7 @@ <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string> <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Ишеним агенти кулпусун ачты"</string> <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string> - <string name="accessibility_volume_settings" msgid="1458961116951564784">"Добуштун жөндөөлөрү"</string> + <string name="accessibility_volume_settings" msgid="1458961116951564784">"Добуштун параметрлери"</string> <string name="volume_odi_captions_tip" msgid="8825655463280990941">"Автоматтык коштомо жазуулар"</string> <string name="accessibility_volume_close_odi_captions_tip" msgid="8924753283621160480">"Коштомо жазуулар кеңеши"</string> <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"Коштомо жазуулардын үстүнө коюу"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматтык"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Үнү чыкпайт жана дирилдебейт"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Үнү чыкпайт же дирилдебейт жана сүйлөшүүлөр тизмесинин ылдый жагында көрүнөт"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Телефондун параметрлерине жараша шыңгырап же дирилдеши мүмкүн"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Телефондун параметрлерине жараша шыңгырап же дирилдеши мүмкүн. <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосундагы жазышуулар демейки жөндөө боюнча калкып чыкма билдирмелер түрүндө көрүнөт."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Түзмөктүн параметрлерине жараша шыңгырап же дирилдеши мүмкүн"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Түзмөктүн параметрлерине жараша шыңгырап же дирилдеши мүмкүн. <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосундагы сүйлөшүүлөр демейки шартта калкып чыкма билдирмелер болуп көрүнөт."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Билдирменин үнүн чыгартууну же басууну тутумга тапшырыңыз"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Абалы:</b> Демейкиге өзгөрдү"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Абалы:</b> Үнсүз абалга төмөндөдү"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Башкаруу элементтери кошула турган колдонмону тандоо"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# көзөмөл кошулду.}other{# көзөмөл кошулду.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Өчүрүлдү"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> кошулсунбу?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> колдонмосун кошсоңуз, ал бул панелге башкаруу элементтерин жана контентти кошо алат. Айрым колдонмолордо бул жерде көрүнүүчү башкаруу элементтерин тандай аласыз."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Сүйүктүүлөргө кошулду"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Сүйүктүүлөргө <xliff:g id="NUMBER">%d</xliff:g>-позицияга кошулду"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Сүйүктүүлөрдөн чыгарылды"</string> @@ -814,7 +814,7 @@ <string name="controls_favorite_removed" msgid="5276978408529217272">"Бардык башкаруу элементтери өчүрүлдү"</string> <string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"Өзгөртүүлөр сакталган жок"</string> <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"Башка колдонмолорду көрүү"</string> - <string name="controls_favorite_load_error" msgid="5126216176144877419">"Башкаруу элементтери жүктөлгөн жок. <xliff:g id="APP">%s</xliff:g> колдонмосуна өтүп, колдонмонун жөндөөлөрү өзгөрбөгөнүн текшериңиз."</string> + <string name="controls_favorite_load_error" msgid="5126216176144877419">"Башкаруу элементтери жүктөлгөн жок. <xliff:g id="APP">%s</xliff:g> колдонмосуна өтүп, колдонмонун параметрлери өзгөрбөгөнүн текшериңиз."</string> <string name="controls_favorite_load_none" msgid="7687593026725357775">"Шайкеш башкаруу элементтери жеткиликсиз"</string> <string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"Башка"</string> <string name="controls_dialog_title" msgid="2343565267424406202">"Түзмөктү башкаруу элементтерине кошуу"</string> @@ -854,22 +854,21 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын <xliff:g id="APP_LABEL">%2$s</xliff:g> колдонмосунан ойнотуу"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Кайтаруу"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүндө ойнотуу үчүн жакындатыңыз"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ушул жерде ойнотуу үчүн <xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүнө жакындаңыз"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> аркылуу ойнотулууда"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Бир жерден ката кетти. Кайра аракет кылыңыз."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Жүктөлүүдө"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Жигерсиз. Колдонмону текшериңиз"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Табылган жок"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Башкара албайсыз"</string> - <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүн пайдалана албайсыз. Аны <xliff:g id="APPLICATION">%2$s</xliff:g> колдонмосунан башкарууга мүмкүн же мүмкүн эместигин, ошондой эле колдонмонун жөндөөлөрүнүн өзгөрүлбөгөнүн текшериңиз."</string> + <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүн пайдалана албайсыз. Аны <xliff:g id="APPLICATION">%2$s</xliff:g> колдонмосунан башкарууга мүмкүн же мүмкүн эместигин, ошондой эле колдонмонун параметрлеринин өзгөрүлбөгөнүн текшериңиз."</string> <string name="controls_open_app" msgid="483650971094300141">"Колдонмону ачуу"</string> <string name="controls_error_generic" msgid="352500456918362905">"Абалы жүктөлгөн жок"</string> <string name="controls_error_failed" msgid="960228639198558525">"Ката, кайталап көрүңүз"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Башкаруу элементтерин кошуу"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Башкаруу элементтерин түзөтүү"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Колдонмо кошуу"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Медиа түзмөктөрдү кошуу"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 түзмөк тандалды"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Үндүн катуулугу"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Динамиктер жана дисплейлер"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Сунушталган түзмөктөр"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Кабарлоо кантип иштейт"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Кабарлоо"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Шайкеш Bluetooth түзмөктөрү болгон жакын жердеги кишилер кабарлап жаткан медиаңызды уга алышат"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Бул экран өчөт"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ачылып турган бүктөлмө түзмөк"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Оодарылып жаткан бүктөлмө түзмөк"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Батареянын кубаты: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Стилусту кубаттаңыз"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Стилустун батареясы отурайын деп калды"</string> + <string name="video_camera" msgid="7654002575156149298">"Видео камера"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Бул профилден чала албайсыз"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Жумуш саясатыңызга ылайык, жумуш профилинен гана чалууларды аткара аласыз"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Жумуш профилине которулуу"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Жабуу"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 0b8a9de5e1cc..c07470a37e18 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ຂອບເຂດທາງລຸ່ມ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ຂອບເຂດທາງຊ້າຍ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ຂອບເຂດທາງຂວາ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ຮູບໜ້າຈໍວຽກຖືກບັນທຶກຢູ່ໃນແອັບ <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ໄຟລ໌"</string> <string name="screenrecord_name" msgid="2596401223859996572">"ໂປຣແກຣມບັນທຶກໜ້າຈໍ"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ກຳລັງປະມວນຜົນການບັນທຶກໜ້າຈໍ"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"ການແຈ້ງເຕືອນສຳລັບເຊດຊັນການບັນທຶກໜ້າຈໍໃດໜຶ່ງ"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ອັດຕະໂນມັດ"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"ບໍ່ມີສຽງ ຫຼື ການສັ່ນເຕືອນ"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ບໍ່ມີສຽງ ຫຼື ການສັ່ນເຕືອນ ແລະ ປາກົດຢູ່ທາງລຸ່ມຂອງພາກສ່ວນການສົນທະນາ"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ອາດສົ່ງສຽງ ຫຼື ສັ່ນເຕືອນໂດຍອ້າງອີງຈາກການຕັ້ງຄ່າໂທລະສັບ"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ອາດສົ່ງສຽງ ຫຼື ສັ່ນເຕືອນໂດຍອ້າງອີງຈາກການຕັ້ງຄ່າໂທລະສັບ. ການສົນທະນາຈາກ <xliff:g id="APP_NAME">%1$s</xliff:g> ຈະສະແດງເປັນຟອງຕາມຄ່າເລີ່ມຕົ້ນ."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"ອາດສົ່ງສຽງ ຫຼື ສັ່ນເຕືອນໂດຍອ້າງອີງຈາກການຕັ້ງຄ່າອຸປະກອນ"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ອາດສົ່ງສຽງ ຫຼື ສັ່ນເຕືອນໂດຍອ້າງອີງຈາກການຕັ້ງຄ່າອຸປະກອນ. ການສົນທະນາຈາກ <xliff:g id="APP_NAME">%1$s</xliff:g> ຈະເປັນ bubble ຕາມຄ່າເລີ່ມຕົ້ນ."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ໃຫ້ລະບົບກຳນົດວ່າການແຈ້ງເຕືອນນິ້ຄວນມີສຽງ ຫຼື ສັ່ນເຕືອນຫຼືບໍ່"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>ສະຖານະ:</b> ເລື່ອນລະດັບເປັນຄ່າເລີ່ມຕົ້ນແລ້ວ"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>ສະຖານະ:</b> ຫຼຸດລະດັບເປັນປິດສຽງແລ້ວ"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"ເລືອກແອັບເພື່ອເພີ່ມການຄວບຄຸມ"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{ເພີ່ມ # ການຄວບຄຸມແລ້ວ.}other{ເພີ່ມ # ການຄວບຄຸມແລ້ວ.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"ລຶບອອກແລ້ວ"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"ເພີ່ມ <xliff:g id="APPNAME">%s</xliff:g> ບໍ?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"ເມື່ອທ່ານເພີ່ມ <xliff:g id="APPNAME">%s</xliff:g>, ມັນຈະສາມາດເພີ່ມການຄວບຄຸມ ແລະ ເນື້ອຫາໃສ່ແຜງນີ້ໄດ້. ໃນບາງແອັບ, ທ່ານສາມາດເລືອກວ່າຈະໃຫ້ສ່ວນຄວບຄຸມໃດສະແດງຂຶ້ນຢູ່ບ່ອນນີ້ໄດ້."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"ເພີ່ມລາຍການທີ່ມັກແລ້ວ"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ເພີ່ມລາຍການທີ່ມັກແລ້ວ, ຕຳແໜ່ງ <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ຍົກເລີກລາຍການທີ່ມັກແລ້ວ"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ຍົກເລີກ"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"ຍ້າຍໄປໃກ້ຂຶ້ນເພື່ອຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ເພື່ອຫຼິ້ນຢູ່ບ່ອນນີ້, ໃຫ້ເຂົ້າໃກ້ <xliff:g id="DEVICENAME">%1$s</xliff:g> ຫຼາຍຂຶ້ນ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"ກຳລັງຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"ມີບາງຢ່າງຜິດພາດເກີດຂຶ້ນ. ກະລຸນາລອງໃໝ່."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"ກຳລັງໂຫຼດ"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ແທັບເລັດ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ບໍ່ພົບ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ບໍ່ສາມາດໃຊ້ການຄວບຄຸມໄດ້"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"ຜິດພາດ, ກະລຸນາລອງໃໝ່"</string> <string name="controls_menu_add" msgid="4447246119229920050">"ເພີ່ມການຄວບຄຸມ"</string> <string name="controls_menu_edit" msgid="890623986951347062">"ແກ້ໄຂການຄວບຄຸມ"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ເພີ່ມແອັບ"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ເພີ່ມເອົ້າພຸດ"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"ກຸ່ມ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"ເລືອກ 1 ອຸປະກອນແລ້ວ"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ລະດັບສຽງ"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ລຳໂພງ ແລະ ຈໍສະແດງຜົນ"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ອຸປະກອນທີ່ແນະນຳ"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ການອອກອາກາດເຮັດວຽກແນວໃດ"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"ອອກອາກາດ"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ຄົນທີ່ຢູ່ໃກ້ທ່ານທີ່ມີອຸປະກອນ Bluetooth ທີ່ເຂົ້າກັນໄດ້ຈະສາມາດຟັງມີເດຍທີ່ທ່ານກຳລັງອອກອາກາດຢູ່ໄດ້"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ໜ້າຈໍນີ້ຈະປິດ"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ອຸປະກອນທີ່ພັບໄດ້ກຳລັງກາງອອກ"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ອຸປະກອນທີ່ພັກໄດ້ກຳລັງປີ້ນໄປມາ"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ແບັດເຕີຣີເຫຼືອ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ເຊື່ອມຕໍ່ປາກກາຂອງທ່ານກັບສາຍສາກ"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"ແບັດເຕີຣີປາກກາເຫຼືອໜ້ອຍ"</string> + <string name="video_camera" msgid="7654002575156149298">"ກ້ອງວິດີໂອ"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"ບໍ່ສາມາດໂທຈາກໂປຣໄຟລ໌ນີ້ໄດ້"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"ນະໂຍບາຍບ່ອນເຮັດວຽກຂອງທ່ານອະນຸຍາດໃຫ້ທ່ານໂທລະສັບໄດ້ຈາກໂປຣໄຟລ໌ບ່ອນເຮັດວຽກເທົ່ານັ້ນ"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"ສະຫຼັບໄປໃຊ້ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"ປິດ"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 901773259470..737e652e511d 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Apatinė riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kairioji riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Dešinioji riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ekrano kopijos, užfiksuotos naudojantis darbo profiliu, išsaugomos programoje „<xliff:g id="APP">%1$s</xliff:g>“"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Failai"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Ekrano vaizdo įrašytuvas"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Apdorojam. ekrano vaizdo įraš."</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Šiuo metu rodomas ekrano įrašymo sesijos pranešimas"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatinis"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Neskamba ir nevibruoja"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Neskamba, nevibruoja ir rodoma apatinėje pokalbių skilties dalyje"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Gali skambėti arba vibruoti, atsižvelgiant į telefono nustatymus"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Gali skambėti arba vibruoti, atsižvelgiant į telefono nustatymus. Pokalbiai iš „<xliff:g id="APP_NAME">%1$s</xliff:g>“ debesėlio pagal numatytuosius nustatymus."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Gali skambėti arba vibruoti, atsižvelgiant į įrenginio nustatymus"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Gali skambėti arba vibruoti, atsižvelgiant į įrenginio nustatymus. Pokalbiai iš „<xliff:g id="APP_NAME">%1$s</xliff:g>“ debesėlio pagal numatytuosius nustatymus."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Nustatykite, kad sistema aptiktų, ar šis pranešimas turi skambėti, ar vibruoti"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Būsena:</b> pakeista į numatytąjį lygį"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Būsena:</b> pakeista į begarsį lygį"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Pasirinkite programą, kad pridėtumėte valdiklių"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Pridėtas # valdiklis.}one{Pridėtas # valdiklis.}few{Pridėti # valdikliai.}many{Pridėta # valdiklio.}other{Pridėta # valdiklių.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Pašalinta"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Pridėti „<xliff:g id="APPNAME">%s</xliff:g>“?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Pridėjus programą „<xliff:g id="APPNAME">%s</xliff:g>“, ji gali pridėti valdiklių ir turinio prie šio skydelio. Kai kuriose programose galite pasirinkti, kurie valdikliai čia rodomi."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Įtraukta į mėgstamiausius"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Įtraukta į mėgstamiausius, padėtis: <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Pašalinta iš mėgstamiausių"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Leisti „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%2$s</xliff:g>“"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Anuliuoti"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Prieikite arčiau, kad galėtumėte leisti įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Jei norite leisti čia, eikite arčiau įrenginio „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Leidžiama įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Kažkas ne taip. Bandykite dar kartą."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Įkeliama"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"planšetinis kompiuteris"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktyvu, patikrinkite progr."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nerasta"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Valdiklis nepasiekiamas"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Klaida, bandykite dar kartą"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Pridėti valdiklių"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Redaguoti valdiklius"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Pridėti programą"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Išvesčių pridėjimas"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupė"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Pasirinktas 1 įrenginys"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Garsumas"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Garsiakalbiai ir ekranai"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Siūlomi įrenginiai"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kaip veikia transliacija"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Transliacija"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Netoliese esantys žmonės, turintys suderinamus „Bluetooth“ įrenginius, gali klausyti jūsų transliuojamos medijos"</string> @@ -1025,4 +1023,12 @@ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Lankstomasis įrenginys apverčiamas"</string> <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Liko akumuliatoriaus įkrovos: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Prijunkite rašiklį prie kroviklio"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Senka rašiklio akumuliatorius"</string> + <string name="video_camera" msgid="7654002575156149298">"Vaizdo kamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Negalima skambinti iš šio profilio"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Pagal jūsų darbo politiką galite skambinti telefonu tik iš darbo profilio"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Perjungti į darbo profilį"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Uždaryti"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index c4cdc06a10c3..920057372d9c 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Apakšmala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kreisā mala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Labā mala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Darba profila ekrānuzņēmumi tiek saglabāti lietotnē <xliff:g id="APP">%1$s</xliff:g>."</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Faili"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Ekrāna ierakstītājs"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekrāna ieraksta apstrāde"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Aktīvs paziņojums par ekrāna ierakstīšanas sesiju"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automātiski"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Nav skaņas signāla vai vibrācijas"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Nav skaņas signāla vai vibrācijas, kā arī atrodas tālāk sarunu sadaļā"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Atkarībā no tālruņa iestatījumiem var zvanīt vai vibrēt"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Atkarībā no tālruņa iestatījumiem var zvanīt vai vibrēt. Sarunas no lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> pēc noklusējuma tiek parādītas burbulī."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Atkarībā no iestatījumiem var zvanīt vai vibrēt"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Atkarībā no ierīces iestatījumiem var zvanīt vai vibrēt. Sarunas no lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> pēc noklusējuma tiek parādītas burbulī."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Iestatiet, lai sistēma noteiktu, vai šim paziņojumam būs skaņa vai vibrācija"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Statuss:</b> svarīgums paaugstināts līdz noklusējumam"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Statuss:</b> svarīgums pazemināts, un paziņojums tiks rādīts bez skaņas"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Izvēlieties lietotni, lai pievienotu vadīklas"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Pievienota # vadīkla.}zero{Pievienotas # vadīklas.}one{Pievienota # vadīkla.}other{Pievienotas # vadīklas.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Noņemta"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Vai pievienot lietotni <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Ja pievienosiet lietotni <xliff:g id="APPNAME">%s</xliff:g>, tā varēs pievienot vadīklas un saturu šim panelim. Dažās lietotnēs varat izvēlēties, kuras vadīklas šeit rādīt."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Pievienota izlasei"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Pievienota izlasei, <xliff:g id="NUMBER">%d</xliff:g>. pozīcija"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Noņemta no izlases"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” no lietotnes <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Atsaukt"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Pārvietojiet savu ierīci tuvāk, lai atskaņotu mūziku ierīcē “<xliff:g id="DEVICENAME">%1$s</xliff:g>”."</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Lai atskaņotu šeit, jums jāatrodas tuvāk šai ierīcei: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Notiek atskaņošana ierīcē <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Radās kļūda. Mēģiniet vēlreiz."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Notiek ielāde"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"planšetdators"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktīva, pārbaudiet lietotni"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Netika atrasta"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Vadīkla nav pieejama"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Radās kļūda. Mēģiniet vēlreiz."</string> <string name="controls_menu_add" msgid="4447246119229920050">"Pievienot vadīklas"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Rediģēt vadīklas"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Pievienot lietotni"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Izejas ierīču pievienošana"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Atlasīta viena ierīce"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Skaļums"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Skaļruņi un displeji"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ieteiktās ierīces"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kā darbojas apraide"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Apraide"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Tuvumā esošās personas ar saderīgām Bluetooth ierīcēm var klausīties jūsu apraidīto multivides saturu."</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Šis ekrāns tiks izslēgts."</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Salokāma ierīce tiek atlocīta"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Salokāma ierīce tiek apgriezta"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Atlikušais uzlādes līmenis: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Pievienojiet skārienekrāna pildspalvu lādētājam"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Zems skārienekrāna pildspalvas akumulatora līmenis"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nevar zvanīt no šī profila"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Saskaņā ar jūsu darba politiku tālruņa zvanus drīkst veikt tikai no darba profila"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Pārslēgties uz darba profilu"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Aizvērt"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index cbdee207e050..5052c2c99987 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматски"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звук или вибрации"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звук или вибрации и се појавува подолу во делот со разговори"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Може да ѕвони или вибрира во зависност од поставките на телефонот"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Може да ѕвони или вибрира во зависност од поставките на телефонот. Стандардно, разговорите од <xliff:g id="APP_NAME">%1$s</xliff:g> се во балончиња."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Може да ѕвони или вибрира во зависност од поставките на уредот"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Може да ѕвони или вибрира во зависност од поставките на уредот. Стандардно, разговорите од <xliff:g id="APP_NAME">%1$s</xliff:g> се во балончиња."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Дозволете системот да определи дали известувањево треба да испушти звук или да вибрира"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Статус:</b> поставено на „Стандардно“"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Статус:</b> намалено на „Тивко“"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Изберете апликација за да додадете контроли"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Додадена е # контрола.}one{Додадени се # контрола.}other{Додадени се # контроли.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Отстранета"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Да се додаде <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Кога ќе ја додадете <xliff:g id="APPNAME">%s</xliff:g>, таа ќе може да додава контроли и содржини на таблава. Кај некои апликации, може да изберете кои контроли ќе се прикажуваат тука."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Омилена"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Омилена, позиција <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Неомилена"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Врати"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Приближете се за да пуштите на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"За да пуштате овде, приближете се до <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Пуштено на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Нешто не е во ред. Обидете се повторно."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Се вчитува"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Грешка, обидете се повторно"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Додајте контроли"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Изменете ги контролите"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Додајте апликација"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Додајте излези"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Избран е 1 уред"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Екранов ќе се исклучи"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Преклопувачки уред се отклопува"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Преклопувачки уред се врти"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Преостаната батерија: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Поврзете го пенкалото со полнач"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Слаба батерија на пенкало"</string> + <string name="video_camera" msgid="7654002575156149298">"Видеокамера"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Не можете да се јавите од профилов"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Вашето работно правило ви дозволува да упатувате повици само од работниот профил"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Префрли се на работен профил"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Затвори"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index ac5c0747a47b..f4e461da0c14 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"സ്വയമേവ"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"ശബ്ദമോ വൈബ്രേഷനോ ഇല്ല"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ശബ്ദമോ വൈബ്രേഷനോ ഇല്ല, സംഭാഷണ വിഭാഗത്തിന് താഴെയായി ദൃശ്യമാകും"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ഫോൺ ക്രമീകരണം അടിസ്ഥാനമാക്കി റിംഗ്/വൈബ്രേറ്റ് ചെയ്യും"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ഫോൺ ക്രമീകരണം അടിസ്ഥാനമാക്കി റിംഗ്/വൈബ്രേറ്റ് ചെയ്തേക്കാം. <xliff:g id="APP_NAME">%1$s</xliff:g>-ൽ നിന്നുള്ള സംഭാഷണങ്ങൾ ഡിഫോൾട്ടായി ബബിൾ ചെയ്യുന്നു."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"ഉപകരണ ക്രമീകരണം അടിസ്ഥാനമാക്കി റിംഗ് ചെയ്യും അല്ലെങ്കിൽ വൈബ്രേറ്റ് ചെയ്യും"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ഉപകരണ ക്രമീകരണം അടിസ്ഥാനമാക്കി റിംഗ് ചെയ്യും അല്ലെങ്കിൽ വൈബ്രേറ്റ് ചെയ്യും. <xliff:g id="APP_NAME">%1$s</xliff:g> എന്ന ആപ്പിൽ നിന്നുള്ള സംഭാഷണങ്ങൾ ഡിഫോൾട്ടായി ബബിൾ ചെയ്യുന്നു."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ഈ അറിയിപ്പ് വരുമ്പോൾ ശബ്ദിക്കുകയാണോ വൈബ്രേറ്റ് ചെയ്യുകയാണോ വേണ്ടതെന്ന് നിർണ്ണയിക്കാൻ സിസ്റ്റത്തെ അനുവദിക്കുക"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>നില:</b> ഡിഫോൾട്ടാക്കി പ്രമോട്ട് ചെയ്തു"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>നില:</b> നിശബ്ദമാക്കി തരം താഴ്ത്തി"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"നിയന്ത്രണങ്ങൾ ചേർക്കാൻ ആപ്പ് തിരഞ്ഞെടുക്കുക"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# നിയന്ത്രണം ചേർത്തു.}other{# നിയന്ത്രണങ്ങൾ ചേർത്തു.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"നീക്കം ചെയ്തു"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> ചേർക്കണോ?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"നിങ്ങൾ <xliff:g id="APPNAME">%s</xliff:g> ചേർത്താൽ, അതിന് ഈ പാനലിലേക്ക് നിയന്ത്രണങ്ങളും ഉള്ളടക്കവും ചേർക്കാനാകും. ചില ആപ്പുകളിൽ, ഇവിടെ ഏത് നിയന്ത്രണങ്ങൾ ദൃശ്യമാകണമെന്ന് നിങ്ങൾക്ക് തിരഞ്ഞെടുക്കാനാകും."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"പ്രിയപ്പെട്ടതാക്കി"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"പ്രിയപ്പെട്ടതാക്കി, സ്ഥാനം <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"പ്രിയപ്പെട്ടതല്ലാതാക്കി"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%2$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"പഴയപടിയാക്കുക"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യാൻ അടുത്തേക്ക് നീക്കുക"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ഇവിടെ പ്ലേ ചെയ്യാൻ <xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിനടുത്തേക്ക് നീക്കുക"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യുന്നു"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"എന്തോ കുഴപ്പമുണ്ടായി. വീണ്ടും ശ്രമിക്കുക."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"ലോഡ് ചെയ്യുന്നു"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"പിശക്, വീണ്ടും ശ്രമിക്കുക"</string> <string name="controls_menu_add" msgid="4447246119229920050">"നിയന്ത്രണങ്ങൾ ചേർക്കുക"</string> <string name="controls_menu_edit" msgid="890623986951347062">"നിയന്ത്രണങ്ങൾ എഡിറ്റ് ചെയ്യുക"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ആപ്പ് ചേർക്കുക"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ഔട്ട്പുട്ടുകൾ ചേർക്കുക"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"ഗ്രൂപ്പ്"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"ഒരു ഉപകരണം തിരഞ്ഞെടുത്തു"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ഈ സ്ക്രീൻ ഓഫാകും"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം അൺഫോൾഡ് ആകുന്നു"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം, കറങ്ങുന്ന വിധത്തിൽ ഫ്ലിപ്പ് ആകുന്നു"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ബാറ്ററി ചാർജ് ശേഷിക്കുന്നു"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"നിങ്ങളുടെ സ്റ്റൈലസ് ചാർജറുമായി കണക്റ്റ് ചെയ്യുക"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"സ്റ്റൈലസിന്റെ ബാറ്ററി ചാർജ് കുറവാണ്"</string> + <string name="video_camera" msgid="7654002575156149298">"വീഡിയോ ക്യാമറ"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"ഈ പ്രൊഫൈലിൽ നിന്ന് കോൾ ചെയ്യാനാകില്ല"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"ഔദ്യോഗിക പ്രൊഫൈലിൽ നിന്ന് മാത്രം ഫോൺ കോളുകൾ ചെയ്യാനാണ് നിങ്ങളുടെ ഔദ്യോഗിക നയം അനുവദിക്കുന്നത്"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"ഔദ്യോഗിക പ്രൊഫൈലിലേക്ക് മാറുക"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"അടയ്ക്കുക"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index cb77336df77e..f4ace180e69c 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Доод талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Зүүн талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Баруун талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ажлын дэлгэцийн агшнуудыг <xliff:g id="APP">%1$s</xliff:g> апп дээр хадгалсан"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлс"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Дэлгэцийн үйлдэл бичигч"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Дэлгэц бичлэг боловсруулж байна"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Дэлгэц бичих горимын үргэлжилж буй мэдэгдэл"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Автомат"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Дуу эсвэл чичиргээ байхгүй"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Дуу эсвэл чичиргээ байхгүй бөгөөд харилцан ярианы хэсгийн доод талд харагдана"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Утасны тохиргоонд тулгуурлан хонх дуугаргах эсвэл чичирхийлж болзошгүй"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Утасны тохиргоонд тулгуурлан хонх дуугаргах эсвэл чичирхийлж болзошгүй. <xliff:g id="APP_NAME">%1$s</xliff:g>-н харилцан яриаг өгөгдмөл тохиргооны дагуу бөмбөлөг болгоно."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Төхөөрөмжийн тохиргоонд тулгуурлан хонх дуугаргах эсвэл чичиргэж болзошгүй"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Төхөөрөмжийн тохиргоонд тулгуурлан хонх дуугаргах эсвэл чичиргэж болзошгүй. <xliff:g id="APP_NAME">%1$s</xliff:g>-н харилцан яриаг өгөгдмөлөөр бөмбөлөг болгоно."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Энэ мэдэгдэл дуу гаргах эсвэл чичрэх эсэхийг системээр тодорхойлуулаарай"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Төлөв:</b> Өгөгдмөл болгож дэвшүүлсэн"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Төлөв:</b> Чимээгүй болгож зэрэглэлийг нь бууруулсан"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Хяналтууд нэмэхийн тулд аппыг сонгоно уу"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# хяналт нэмсэн.}other{# хяналт нэмсэн.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Хассан"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Дуртай гэж тэмдэглэсэн"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"<xliff:g id="NUMBER">%d</xliff:g>-р байршилд дуртай гэж тэмдэглэсэн"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Дургүй гэж тэмдэглэсэн"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%2$s</xliff:g> дээр тоглуулах"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Болих"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулахын тулд төхөөрөмжөө ойртуулна уу"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Энд тоглуулахын тулд <xliff:g id="DEVICENAME">%1$s</xliff:g> руу ойртуулна уу"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулж байна"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Алдаа гарлаа. Дахин оролдоно уу."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Ачаалж байна"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Идэвхгүй байна, аппыг шалгана уу"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Олдсонгүй"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Хяналт боломжгүй байна"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Алдаа гарав, дахин оролдоно уу"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Хяналт нэмэх"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Хяналтыг өөрчлөх"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Гаралт нэмэх"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Бүлэг"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 төхөөрөмж сонгосон"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Дууны түвшин"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Чанга яригч ба дэлгэц"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Санал болгосон төхөөрөмжүүд"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Нэвтрүүлэлт хэрхэн ажилладаг вэ?"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Нэвтрүүлэлт"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Тохиромжтой Bluetooth төхөөрөмжүүдтэй таны ойролцоох хүмүүс таны нэвтрүүлж буй медиаг сонсох боломжтой"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Энэ дэлгэц унтарна"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Эвхэгддэг төхөөрөмжийг дэлгэж байна"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Эвхэгддэг төхөөрөмжийг хөнтөрч байна"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> батарей үлдлээ"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Мэдрэгч үзгээ цэнэглэгчтэй холбоорой"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Мэдрэгч үзэгний батарей бага байна"</string> + <string name="video_camera" msgid="7654002575156149298">"Видео камер"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Энэ профайлаас залгах боломжгүй"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Таны ажлын бодлого танд зөвхөн ажлын профайлаас утасны дуудлага хийхийг зөвшөөрдөг"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Ажлын профайл руу сэлгэх"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Хаах"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 4f0b83fc63ae..96ecc999d567 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"खालील सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"डाव्या सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"उजव्या सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ऑफिससंबंधित स्क्रीनशॉट <xliff:g id="APP">%1$s</xliff:g> अॅपमध्ये सेव्ह केले आहेत"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"फाइल"</string> <string name="screenrecord_name" msgid="2596401223859996572">"स्क्रीन रेकॉर्डर"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"स्क्रीन रेकॉर्डिंग प्रोसेस सुरू"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रेकॉर्ड सत्रासाठी सुरू असलेली सूचना"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ऑटोमॅटिक"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"आवाज किंवा व्हायब्रेशन नाही"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"आवाज किंवा व्हायब्रेशन नाही आणि संभाषण विभागात सर्वात तळाशी दिसते"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"फोन सेटिंग्जनुसार फोन रिंग किंवा व्हायब्रेट होऊ शकतो"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"फोन सेटिंग्जच्या आधारावर रिंग किंवा व्हायब्रेट होऊ शकतो. <xliff:g id="APP_NAME">%1$s</xliff:g> मधील संभाषणे बाय डीफॉल्ट बबल होतात."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"डिव्हाइस सेटिंग्जनुसार रिंग किंवा व्हायब्रेट होऊ शकतो"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"डिव्हाइस सेटिंग्जनुसार रिंग किंवा व्हायब्रेट होऊ शकतो. <xliff:g id="APP_NAME">%1$s</xliff:g> मधील संभाषणे बाय डीफॉल्ट बबल होतात."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ही सूचना मिळाल्यावर आवाज व्हावा की व्हायब्रेशन व्हावे ते सिस्टममध्ये नमूद करा"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>स्थिती</b> ही डीफॉल्ट म्हणून प्रमोट केली गेली"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>स्थिती</b> ला सायलंट म्हणून डीमोट केले गेले"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"नियंत्रणे जोडण्यासाठी ॲप निवडा"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# नियंत्रण जोडले आहे.}other{# नियंत्रणे जोडली आहेत.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"काढून टाकले"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> जोडायचे आहे का?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"तुम्ही <xliff:g id="APPNAME">%s</xliff:g> जोडता, तेव्हा ते या पॅनलमध्ये नियंत्रणे आणि आशय जोडू शकते. येथे कोणती नियंत्रणे दाखवावीत ते तुम्ही काही अॅप्समध्ये निवडू शकता."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"आवडले"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"आवडले, स्थान <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"नावडले"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> मध्ये <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"पहिल्यासारखे करा"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले करण्यासाठी जवळ जा"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"येथे प्ले करण्यासाठी, <xliff:g id="DEVICENAME">%1$s</xliff:g> च्या जवळ जा"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले होत आहे"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"काहीतरी चूक झाली. पुन्हा प्रयत्न करा."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"लोड करत आहे"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"टॅबलेट"</string> <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय, ॲप तपासा"</string> <string name="controls_error_removed" msgid="6675638069846014366">"आढळले नाही"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"नियंत्रण उपलब्ध नाही"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"एरर, पुन्हा प्रयत्न करा"</string> <string name="controls_menu_add" msgid="4447246119229920050">"नियंत्रणे जोडा"</string> <string name="controls_menu_edit" msgid="890623986951347062">"नियंत्रणे संपादित करा"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"अॅप जोडा"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट जोडा"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"गट"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिव्हाइस निवडले"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"व्हॉल्यूम"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"स्पीकर आणि डिस्प्ले"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"सुचवलेली डिव्हाइस"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ब्रॉडकास्टिंग कसे काम करते"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"ब्रॉडकास्ट करा"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"कंपॅटिबल ब्लूटूथ डिव्हाइस असलेले तुमच्या जवळपासचे लोक हे तुम्ही ब्रॉडकास्ट करत असलेला मीडिया ऐकू शकतात"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ही स्क्रीन बंद होईल"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड करता येण्यासारखे डिव्हाइस अनफोल्ड केले जात आहे"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड करता येण्यासारखे डिव्हाइस आजूबाजूला फ्लिप केले जात आहे"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> बॅटरी शिल्लक आहे"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"तुमचे स्टायलस चार्जरशी कनेक्ट करा"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"स्टायलस बॅटरी कमी आहे"</string> + <string name="video_camera" msgid="7654002575156149298">"व्हिडिओ कॅमेरा"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"या प्रोफाइलवरून कॉल करू शकत नाही"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"तुमचे कामाशी संबंधित धोरण तुम्हाला फक्त कार्य प्रोफाइलवरून फोन कॉल करन्याची अनुमती देते"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"कार्य प्रोफाइलवर स्विच करा"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"बंद करा"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index f08f6a654658..2782925b4381 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Sempadan bawah <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sempadan kiri <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sempadan kanan <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Tangkapan skrin tugasan disimpan dalam apl <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fail"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Perakam Skrin"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Memproses rakaman skrin"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pemberitahuan breterusan untuk sesi rakaman skrin"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatik"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Tiada bunyi atau getaran"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Tiada bunyi atau getaran dan muncul di sebelah bawah dalam bahagian perbualan"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Mungkin berbunyi atau bergetar berdasarkan tetapan telefon"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Mungkin berbunyi atau bergetar berdasarkan tetapan telefon. Perbualan daripada gelembung <xliff:g id="APP_NAME">%1$s</xliff:g> secara lalai."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Mungkin berbunyi atau bergetar berdasarkan tetapan peranti"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Mungkin berbunyi atau bergetar berdasarkan tetapan peranti. Perbualan daripada gelembung <xliff:g id="APP_NAME">%1$s</xliff:g> secara lalai."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Minta sistem menentukan jika pemberitahuan ini patut menghasilkan bunyi atau getaran"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Dinaikkan Taraf kepada Lalai"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Diturunkan Taraf kepada Senyap"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Pilih apl untuk menambahkan kawalan"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kawalan ditambah.}other{# kawalan ditambah.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Dialih keluar"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Digemari"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Digemari, kedudukan <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Dinyahgemari"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> daripada <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Buat asal"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Alihkan lebih dekat untuk bermain pada<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Untuk bermain di sini, bergerak lebih dekat kepada <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Dimainkan pada <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Kesilapan telah berlaku. Cuba lagi."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Memuatkan"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Tidak aktif, semak apl"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kawalan tidak tersedia"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Ralat, cuba lagi"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Tambah kawalan"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Edit kawalan"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tambah output"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Kumpulan"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 peranti dipilih"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Kelantangan"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Pembesar Suara & Paparan"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Peranti yang Dicadangkan"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cara siaran berfungsi"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Siarkan"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Orang berdekatan anda dengan peranti Bluetooth yang serasi boleh mendengar media yang sedang anda siarkan"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Skrin ini akan dimatikan"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Peranti boleh lipat dibuka"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Peranti boleh lipat diterbalikkan"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateri tinggal <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Sambungkan stilus anda kepada pengecas"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Bateri stilus lemah"</string> + <string name="video_camera" msgid="7654002575156149298">"Kamera video"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Tidak dapat membuat panggilan daripada profil ini"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Dasar kerja anda membenarkan anda membuat panggilan telefon hanya daripada profil kerja"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Tukar kepada profil kerja"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Tutup"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 9b6e95cbcd53..f87565469f93 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"အောက်ခြေအနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ဘယ်ဘက်အနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ညာဘက်အနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"အလုပ်နှင့်ဆိုင်သော ဖန်သားပြင်ဓာတ်ပုံများကို <xliff:g id="APP">%1$s</xliff:g> အက်ပ်တွင် သိမ်းသည်"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ဖိုင်များ"</string> <string name="screenrecord_name" msgid="2596401223859996572">"ဖန်သားပြင် ရိုက်ကူးမှု"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"စကရင်ရိုက်ကူးမှု အပြီးသတ်နေသည်"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"ဖန်သားပြင် ရိုက်ကူးသည့် စက်ရှင်အတွက် ဆက်တိုက်လာနေသော အကြောင်းကြားချက်"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"အလိုအလျောက်"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"အသံ သို့မဟုတ် တုန်ခါမှုမရှိပါ"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"အသံ သို့မဟုတ် တုန်ခါမှုမရှိပါ၊ စကားဝိုင်းကဏ္ဍ၏ အောက်ပိုင်းတွင် မြင်ရသည်"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ဖုန်းဆက်တင်များပေါ် အခြေခံပြီး အသံမြည်နိုင်သည် သို့မဟုတ် တုန်ခါနိုင်သည်"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ဖုန်းဆက်တင်ပေါ် အခြေခံပြီး အသံမြည် (သို့) တုန်ခါနိုင်သည်။ <xliff:g id="APP_NAME">%1$s</xliff:g> မှ စကားဝိုင်းများကို ပူဖောင်းကွက်ဖြင့် အလိုအလျောက်ပြသည်။"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"စက်ပစ္စည်း ဆက်တင်များပေါ် အခြေခံပြီး အသံမြည်နိုင်သည် (သို့) တုန်ခါနိုင်သည်"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"စက်ပစ္စည်းဆက်တင်များပေါ် အခြေခံပြီး အသံမြည်နိုင်သည် (သို့) တုန်ခါနိုင်သည်။ မူရင်းသတ်မှတ်ချက်အဖြစ် <xliff:g id="APP_NAME">%1$s</xliff:g> မှ စကားဝိုင်းများကို ပူဖောင်းကွက်ဖြင့် ပြသည်။"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ဤအကြောင်းကြားချက်က အသံ သို့မဟုတ် တုန်ခါမှု ပေးရန် သင့်/မသင့်ကို စနစ်က ဆုံးဖြတ်ပါစေ"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>အခြေအနေ-</b> မူရင်းသို့ ချိန်ညှိထားသည်"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>အခြေအနေ-</b> အသံတိတ်ခြင်းသို့ ပြန်ချိန်ညှိထားသည်"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"ထိန်းချုပ်မှုများထည့်ရန် အက်ပ်ရွေးခြင်း"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{ထိန်းချုပ်ခလုတ် # ခု ထည့်ထားသည်။}other{ထိန်းချုပ်ခလုတ် # ခု ထည့်ထားသည်။}}"</string> <string name="controls_removed" msgid="3731789252222856959">"ဖယ်ရှားထားသည်"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"အကြိုက်ဆုံးတွင် ထည့်ထားသည်"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"အကြိုက်ဆုံးတွင် ထည့်ထားသည်၊ အဆင့် <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"အကြိုက်ဆုံးမှ ဖယ်ရှားထားသည်"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%2$s</xliff:g> တွင် ဖွင့်ပါ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"နောက်ပြန်ရန်"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင်ဖွင့်ရန် အနီးသို့ရွှေ့ပါ"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ဤနေရာတွင် ဖွင့်ရန် <xliff:g id="DEVICENAME">%1$s</xliff:g> အနီးသို့ ရွှေ့ပါ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင် ဖွင့်နေသည်"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"တစ်ခုခုမှားသွားသည်။ ထပ်စမ်းကြည့်ပါ။"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"ဖွင့်နေသည်"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"တက်ဘလက်"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"မတွေ့ပါ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ထိန်းချုပ်မှု မရနိုင်ပါ"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"မှားသွားသည်၊ ပြန်စမ်းကြည့်ပါ"</string> <string name="controls_menu_add" msgid="4447246119229920050">"ထိန်းချုပ်မှုများ ထည့်ရန်"</string> <string name="controls_menu_edit" msgid="890623986951347062">"ထိန်းချုပ်မှုများ ပြင်ရန်"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"မီဒီယာအထွက်များ ထည့်ရန်"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"အုပ်စု"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"စက်ပစ္စည်း ၁ ခုကို ရွေးချယ်ထားသည်"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"အသံအတိုးအကျယ်"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"စပီကာနှင့် ဖန်သားပြင်များ"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"အကြံပြုထားသော စက်ပစ္စည်းများ"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ထုတ်လွှင့်မှုဆောင်ရွက်ပုံ"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"ထုတ်လွှင့်ခြင်း"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"အနီးရှိတွဲသုံးနိုင်သော ဘလူးတုသ်သုံးစက် အသုံးပြုသူများက သင်ထုတ်လွှင့်နေသော မီဒီယာကို နားဆင်နိုင်သည်"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ဤဖန်သားပြင်ကို ပိတ်လိုက်မည်"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ခေါက်နိုင်သောစက်ကို ဖြန့်လိုက်သည်"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ခေါက်နိုင်သောစက်ကို တစ်ဘက်သို့ လှန်လိုက်သည်"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ဘက်ထရီ <xliff:g id="PERCENTAGE">%s</xliff:g> ကျန်သေးသည်"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"စတိုင်လပ်စ်ကို အားသွင်းကိရိယာနှင့် ချိတ်ဆက်ခြင်း"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"စတိုင်လပ်စ် ဘက်ထရီ အားနည်းနေသည်"</string> + <string name="video_camera" msgid="7654002575156149298">"ဗီဒီယိုကင်မရာ"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"ဤပရိုဖိုင်မှ ခေါ်ဆို၍ မရပါ"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"သင့်အလုပ်မူဝါဒသည် သင့်အား အလုပ်ပရိုဖိုင်မှသာ ဖုန်းခေါ်ဆိုခွင့် ပြုသည်"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"အလုပ်ပရိုဖိုင်သို့ ပြောင်းရန်"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"ပိတ်ရန်"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 7cc0a1a9a1ff..5c1258b9bc93 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Nedre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Venstre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Høyre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Jobbrelaterte skjermdumper lagres i <xliff:g id="APP">%1$s</xliff:g>-appen"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Skjermopptaker"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Behandler skjermopptaket"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Vedvarende varsel for et skjermopptak"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatisk"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ingen lyd eller vibrering"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ingen lyd eller vibrering, og vises lavere i samtaledelen"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan ringe eller vibrere basert på telefoninnstillingene"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan ringe eller vibrere basert på telefoninnstillingene. Samtaler fra <xliff:g id="APP_NAME">%1$s</xliff:g> lager bobler som standard."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Kan ringe eller vibrere basert på enhetsinnstillingene"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kan ringe eller vibrere basert på enhetsinnstillingene. Samtaler fra <xliff:g id="APP_NAME">%1$s</xliff:g> vises som standard som bobler."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"La systemet velge om dette varselet skal lage lyd eller vibrere"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Oppgradert til standard"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Nedgradert til lydløst"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Velg en app for å legge til kontroller"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontroll er lagt til.}other{# kontroller er lagt til.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Fjernet"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favoritt"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favoritt, posisjon <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Fjernet som favoritt"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> fra <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Angre"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Flytt nærmere for å spille av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"For å spille av her, gå nærmere <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Spilles av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Noe gikk galt. Prøv på nytt."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Laster inn"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"nettbrett"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Sjekk appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ikke funnet"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrollen er utilgjengelig"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"En feil oppsto. Prøv på nytt"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Legg til kontroller"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Endre kontroller"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Legg til utenheter"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet er valgt"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volum"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Høyttalere og skjermer"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Foreslåtte enheter"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Slik fungerer kringkasting"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Kringkasting"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Folk i nærheten med kompatible Bluetooth-enheter kan lytte til mediene du kringkaster"</string> @@ -1018,13 +1019,19 @@ <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Avbryt"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Vend nå"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Brett ut telefonen for å ta bedre selfier"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Vil du bytte til frontskjermen for bedre selfier?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Vil du bruke frontkameraet for bedre selfier?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Bruk det bakovervendte kameraet for å ta bredere bilder med høyere oppløsning."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Denne skjermen slås av"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En sammenleggbar enhet blir brettet ut"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En sammenleggbar enhet blir snudd"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteri gjenstår"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Koble pekepennen til en lader"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Det er lite batteri i pekepennen"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Kan ikke ringe fra denne profilen"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Som følge av jobbreglene dine kan du bare starte telefonanrop fra jobbprofilen."</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Bytt til jobbprofilen"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Lukk"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index f8deac686a94..e2c8b4b4dc00 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"फेदबाट <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"बायाँ किनाराबाट <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"दायाँ किनाराबाट <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"कार्य प्रोफाइल प्रयोग गरी लिइएका स्क्रिनसटहरू <xliff:g id="APP">%1$s</xliff:g> एपमा सेभ गरिन्छन्"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"स्क्रिन रेकर्डर"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"स्क्रिन रेकर्डिङको प्रक्रिया अघि बढाइँदै"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"कुनै स्क्रिन रेकर्ड गर्ने सत्रका लागि चलिरहेको सूचना"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"स्वचालित"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"बज्दैन पनि, भाइब्रेट पनि हुँदैन"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"बज्दैन पनि, भाइब्रेट पनि हुँदैन र वार्तालाप खण्डको तलतिर देखा पर्छ"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"फोनको सेटिङका आधारमा घन्टी बज्न वा भाइब्रेट हुन सक्छ"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"फोनको सेटिङका आधारमा घन्टी बज्न वा भाइब्रेट हुन सक्छ। <xliff:g id="APP_NAME">%1$s</xliff:g> का वार्तालापहरू डिफल्ट रूपमा बबलमा देखाइन्छन्।"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"डिभाइसको सेटिङका आधारमा घन्टी बज्न वा भाइब्रेट हुन सक्छ"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"डिभाइसको सेटिङका आधारमा घन्टी बज्न वा कम्पन हुन सक्छ। <xliff:g id="APP_NAME">%1$s</xliff:g> मार्फत गरिएका वार्तालापहरू स्वतः बबलमा देखिन्छन्।"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"सिस्टमलाई यो सूचना आउँदा ध्वनि बज्नु पर्छ वा कम्पन हुनु पर्छ भन्ने कुराको निधो गर्न दिनुहोस्"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>स्थिति:</b> सूचनालाई महत्त्वपूर्ण ठानी डिफल्ट मोडमा सेट गरिएको छ"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>स्थिति:</b> सूचनालाई कम महत्त्वपूर्ण ठानी साइलेन्ट मोडमा सेट गरिएको छ"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"कन्ट्रोल थप्नु पर्ने एप छान्नुहोस्"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# कन्ट्रोल हालियो।}other{# वटा कन्ट्रोल हालियो।}}"</string> <string name="controls_removed" msgid="3731789252222856959">"हटाइएको"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> हाल्ने हो?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"तपाईंले <xliff:g id="APPNAME">%s</xliff:g> हाल्नुभयो भने यसले यो प्यानलमा सेटिङ र सामग्री हाल्न सक्छ। तपाईं केही एपहरूमा यहाँ कुन कुन सेटिङ देखाउने भन्ने कुरा छनौट गर्न सक्नुहुन्छ।"</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"मनपराइएको"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"मन पराइएका कुराहरूको <xliff:g id="NUMBER">%d</xliff:g> औँ स्थानमा"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"मन पर्ने कुराहरूको सूचीमा नराखिएको"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%2$s</xliff:g> मा बजाउनुहोस्"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"अन्डू गर्नुहोस्"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गर्न आफ्नो डिभाइस नजिकै लैजानुहोस्"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"यो डिभाइसमा प्ले गर्न <xliff:g id="DEVICENAME">%1$s</xliff:g> को अझ नजिक जानुहोस्"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गरिँदै छ"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"केही चिज गडबड भयो। फेरि प्रयास गर्नुहोस्।"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"लोड हुँदै छ"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ट्याब्लेट"</string> <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय छ, एप जाँच गर्नु…"</string> <string name="controls_error_removed" msgid="6675638069846014366">"फेला परेन"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"नियन्त्रण उपलब्ध छैन"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"त्रुटि भयो, फेरि प्रयास गर्नु…"</string> <string name="controls_menu_add" msgid="4447246119229920050">"कन्ट्रोल थप्नुहोस्"</string> <string name="controls_menu_edit" msgid="890623986951347062">"कन्ट्रोल सम्पादन गर्नुहोस्"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"एप हाल्नुहोस्"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट यन्त्रहरू थप्नुहोस्"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"समूह"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"१ यन्त्र चयन गरियो"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"भोल्युम"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"स्पिकर तथा डिस्प्लेहरू"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"सिफारिस गरिएका डिभाइसहरू"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"प्रसारण गर्ने सुविधाले कसरी काम गर्छ"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"प्रसारण"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"कम्प्याटिबल ब्लुटुथ डिभाइस भएका नजिकैका मान्छेहरू तपाईंले प्रसारण गरिरहनुभएको मिडिया सुन्न सक्छन्"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ यो स्क्रिन अफ हुने छ"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड गर्न मिल्ने डिभाइस अनफोल्ड गरेको देखाइएको एनिमेसन"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड गर्न मिल्ने डिभाइस यताउता पल्टाएर देखाइएको एनिमेसन"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ब्याट्री बाँकी छ"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"आफ्नो स्टाइलस चार्जरमा कनेक्ट गर्नुहोस्"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"स्टाइलसको ब्याट्री लो छ"</string> + <string name="video_camera" msgid="7654002575156149298">"भिडियो क्यामेरा"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"यो प्रोफाइलबाट कल गर्न सकिँदैन"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"तपाईंको कामसम्बन्धी नीतिअनुसार कार्य प्रोफाइलबाट मात्र फोन कल गर्न सकिन्छ"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"कार्य प्रोफाइल प्रयोग गर्नुहोस्"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"बन्द गर्नुहोस्"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 7a0b6d3230fc..56d530300ed2 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ondergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linkergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Rechtergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Werkscreenshots worden opgeslagen in de <xliff:g id="APP">%1$s</xliff:g>-app"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Bestanden"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Schermopname"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Schermopname verwerken"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Doorlopende melding voor een schermopname-sessie"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatisch"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Geen geluid of trilling"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Geen geluid of trilling en wordt lager in het gedeelte met gesprekken getoond"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan overgaan of trillen op basis van de telefooninstellingen"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan overgaan of trillen op basis van de telefooninstellingen. Gesprekken uit <xliff:g id="APP_NAME">%1$s</xliff:g> worden standaard als bubbels getoond."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Kan overgaan of trillen op basis van de apparaatinstellingen"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kan overgaan of trillen op basis van de apparaatinstellingen. Gesprekken uit <xliff:g id="APP_NAME">%1$s</xliff:g> worden standaard als bubbels weergegeven."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Het systeem laten bepalen of deze melding geluid moet maken of moet trillen"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> opgeschaald naar Standaard"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> verlaagd naar Stil"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Kies de app waaraan je bedieningselementen wilt toevoegen"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# bedieningselement toegevoegd.}other{# bedieningselementen toegevoegd.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Verwijderd"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> toevoegen?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Als je <xliff:g id="APPNAME">%s</xliff:g> toevoegt, kan deze app bedieningselementen en content aan dit deelvenster toevoegen. In sommige apps kun je kiezen welke bedieningselementen hier worden getoond."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Gemarkeerd als favoriet"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Gemarkeerd als favoriet, positie <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Verwijderd als favoriet"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Ongedaan maken"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Houd dichter bij <xliff:g id="DEVICENAME">%1$s</xliff:g> om af te spelen"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ga dichter naar <xliff:g id="DEVICENAME">%1$s</xliff:g> toe om hier af te spelen"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Afspelen op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Er is iets misgegaan. Probeer het opnieuw."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Laden"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactief, check de app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Niet gevonden"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Beheeroptie niet beschikbaar"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Fout, probeer het opnieuw"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Bedieningselementen toevoegen"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Bedieningselementen bewerken"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"App toevoegen"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Uitvoer toevoegen"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Groep"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Eén apparaat geselecteerd"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers en schermen"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Voorgestelde apparaten"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Hoe uitzenden werkt"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Uitzending"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Mensen bij jou in de buurt met geschikte bluetooth-apparaten kunnen luisteren naar de media die je uitzendt"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Dit scherm gaat uit"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Opvouwbaar apparaat wordt uitgevouwen"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Opvouwbaar apparaat wordt gedraaid"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Nog <xliff:g id="PERCENTAGE">%s</xliff:g> batterijlading"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Verbind je stylus met een oplader"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Batterij van stylus bijna leeg"</string> + <string name="video_camera" msgid="7654002575156149298">"Videocamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Je kunt niet bellen vanuit dit profiel"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Op basis van je werkbeleid kun je alleen bellen vanuit het werkprofiel"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Overschakelen naar werkprofiel"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Sluiten"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 6a3a282fc436..5396edef8054 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ନିମ୍ନ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ବାମ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ଡାହାଣ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ୱାର୍କ ସ୍କ୍ରିନସଟଗୁଡ଼ିକୁ <xliff:g id="APP">%1$s</xliff:g> ଆପରେ ସେଭ କରାଯାଇଛି"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ଫାଇଲଗୁଡ଼ିକ"</string> <string name="screenrecord_name" msgid="2596401223859996572">"ସ୍କ୍ରିନ୍ ରେକର୍ଡର୍"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ସ୍କ୍ରିନ ରେକର୍ଡିଂର ପ୍ରକ୍ରିୟାକରଣ"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"ଏକ ସ୍କ୍ରିନ୍ ରେକର୍ଡ୍ ସେସନ୍ ପାଇଁ ଚାଲୁଥିବା ବିଜ୍ଞପ୍ତି"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ସ୍ୱଚାଳିତ"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"କୌଣସି ସାଉଣ୍ଡ କିମ୍ବା ଭାଇବ୍ରେସନ୍ ନାହିଁ"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"କୌଣସି ସାଉଣ୍ଡ କିମ୍ବା ଭାଇବ୍ରେସନ୍ ନାହିଁ ଏବଂ ବାର୍ତ୍ତାଳାପ ବିଭାଗର ନିମ୍ନରେ ଦେଖାଯାଏ"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ଫୋନ ସେଟିଂସ ଆଧାରରେ ରିଙ୍ଗ କିମ୍ବା ଭାଇବ୍ରେଟ ହୋଇପାରେ"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ଫୋନ୍ ସେଟିଂସ୍ ଆଧାରରେ ରିଙ୍ଗ କିମ୍ବା ଭାଇବ୍ରେଟ୍ ହୋଇପାରେ। <xliff:g id="APP_NAME">%1$s</xliff:g>ରୁ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ଡିଫଲ୍ଟ ଭାବରେ ବବଲ୍ ହୁଏ।"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"ଡିଭାଇସ ସେଟିଂସ ଆଧାରରେ ରିଂ କିମ୍ବା ଭାଇବ୍ରେଟ ହୋଇପାରେ"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ଡିଭାଇସ ସେଟିଂସ ଆଧାରରେ ରିଂ କିମ୍ବା ଭାଇବ୍ରେଟ ହୋଇପାରେ। ଡିଫଲ୍ଟ ଭାବରେ <xliff:g id="APP_NAME">%1$s</xliff:g>ରୁ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ବବଲ ଭାବେ ଦେଖାଯାଏ।"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ଏହି ବିଜ୍ଞପ୍ତି ପ୍ରାପ୍ତ ହେବା ସମୟରେ ସାଉଣ୍ଡ ହେବା ଉଚିତ ନା ଭାଇବ୍ରେସନ୍ ତାହା ସିଷ୍ଟମକୁ ସ୍ଥିର କରିବାକୁ ଦିଅନ୍ତୁ"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>ସ୍ଥିତି:</b> ଡିଫଲ୍ଟକୁ ପ୍ରମୋଟ୍ କରାଯାଇଛି"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>ସ୍ଥିତି:</b> ନୀରବକୁ ଡିମୋଟ୍ କରାଯାଇଛି"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଯୋଗ କରିବାକୁ ଆପ୍ ବାଛନ୍ତୁ"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{#ଟି ନିୟନ୍ତ୍ରଣ ଯୋଗ କରାଯାଇଛି।}other{#ଟି ନିୟନ୍ତ୍ରଣ ଯୋଗ କରାଯାଇଛି।}}"</string> <string name="controls_removed" msgid="3731789252222856959">"କାଢ଼ି ଦିଆଯାଇଛି"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"ପସନ୍ଦ କରାଯାଇଛି"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ପସନ୍ଦ କରାଯାଇଛି, ସ୍ଥିତି <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ନାପସନ୍ଦ କରାଯାଇଛି"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ରୁ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ପୂର୍ବବତ୍ କରନ୍ତୁ"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ପ୍ଲେ କରିବା ପାଇଁ ପାଖକୁ ମୁଭ କରନ୍ତୁ"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ଏଠାରେ ପ୍ଲେ କରିବା ପାଇଁ <xliff:g id="DEVICENAME">%1$s</xliff:g> ପାଖକୁ ମୁଭ କରନ୍ତୁ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ପ୍ଲେ ହେଉଛି"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"କିଛି ତ୍ରୁଟି ହୋଇଛି। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"ଲୋଡ ହେଉଛି"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ଟାବଲେଟ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ମିଳିଲା ନାହିଁ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ନିୟନ୍ତ୍ରଣ ଉପଲବ୍ଧ ନାହିଁ"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"ତ୍ରୁଟି ହୋଇଛି, ପୁଣି ଚେଷ୍ଟା କର"</string> <string name="controls_menu_add" msgid="4447246119229920050">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ଯୋଗ କରନ୍ତୁ"</string> <string name="controls_menu_edit" msgid="890623986951347062">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଏଡିଟ କରନ୍ତୁ"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ଆଉଟପୁଟ୍ ଯୋଗ କରନ୍ତୁ"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"ଗୋଷ୍ଠୀ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1ଟି ଡିଭାଇସ୍ ଚୟନ କରାଯାଇଛି"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ଭଲ୍ୟୁମ"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ସ୍ପିକର ଏବଂ ଡିସପ୍ଲେଗୁଡ଼ିକ"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ପ୍ରସ୍ତାବିତ ଡିଭାଇସଗୁଡ଼ିକ"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ବ୍ରଡକାଷ୍ଟିଂ କିପରି କାମ କରେ"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"ବ୍ରଡକାଷ୍ଟ"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ଆପଣଙ୍କ ଆଖପାଖର କମ୍ପାଟିବଲ ବ୍ଲୁଟୁଥ ଡିଭାଇସ ଥିବା ଲୋକମାନେ ଆପଣ ବ୍ରଡକାଷ୍ଟ କରୁଥିବା ମିଡିଆ ଶୁଣିପାରିବେ"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ଏହି ସ୍କ୍ରିନ ବନ୍ଦ ହୋଇଯିବ"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଅନଫୋଲ୍ଡ କରାଯାଉଛି"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଫ୍ଲିପ କରାଯାଉଛି"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ବେଟେରୀ ଚାର୍ଜ ବାକି ଅଛି"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ଏକ ଚାର୍ଜର ସହ ଆପଣଙ୍କ ଷ୍ଟାଇଲସକୁ କନେକ୍ଟ କରନ୍ତୁ"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"ଷ୍ଟାଇଲସ ବେଟେରୀର ଚାର୍ଜ କମ ଅଛି"</string> + <string name="video_camera" msgid="7654002575156149298">"ଭିଡିଓ କେମେରା"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"ଏହି ପ୍ରୋଫାଇଲରୁ କଲ କରାଯାଇପାରିବ ନାହିଁ"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"ଆପଣଙ୍କ ୱାର୍କ ନୀତି ଆପଣଙ୍କୁ କେବଳ ୱାର୍କ ପ୍ରୋଫାଇଲରୁ ଫୋନ କଲ କରିବାକୁ ଅନୁମତି ଦିଏ"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"ୱାର୍କ ପ୍ରୋଫାଇଲକୁ ସ୍ୱିଚ କରନ୍ତୁ"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"ବନ୍ଦ କରନ୍ତୁ"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index c4dd6b455667..10c89f2f35aa 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ਹੇਠਾਂ ਦੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ਖੱਬੇ ਪਾਸੇ ਵਾਲੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ਸੱਜੇ ਪਾਸੇ ਵਾਲੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ਕਾਰਜ ਸਕ੍ਰੀਨਸ਼ਾਟਾਂ ਨੂੰ <xliff:g id="APP">%1$s</xliff:g> ਐਪ ਵਿੱਚ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ਫ਼ਾਈਲਾਂ"</string> <string name="screenrecord_name" msgid="2596401223859996572">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਰ"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਜਾਰੀ ਹੈ"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"ਕਿਸੇ ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਸੈਸ਼ਨ ਲਈ ਚੱਲ ਰਹੀ ਸੂਚਨਾ"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ਸਵੈਚਲਿਤ"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"ਕੋਈ ਧੁਨੀ ਜਾਂ ਥਰਥਰਾਹਟ ਨਹੀਂ"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ਕੋਈ ਧੁਨੀ ਜਾਂ ਥਰਥਰਾਹਟ ਨਹੀਂ ਅਤੇ ਸੂਚਨਾਵਾਂ ਗੱਲਬਾਤ ਸੈਕਸ਼ਨ ਵਿੱਚ ਹੇਠਲੇ ਪਾਸੇ ਦਿਸਦੀਆਂ ਹਨ"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ਫ਼ੋਨ ਸੈਟਿੰਗਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਘੰਟੀ ਵੱਜ ਸਕਦੀ ਹੈ ਜਾਂ ਥਰਥਰਾਹਟ ਹੋ ਸਕਦੀ ਹੈ"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ਫ਼ੋਨ ਸੈਟਿੰਗਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਘੰਟੀ ਵੱਜ ਸਕਦੀ ਹੈ ਜਾਂ ਥਰਥਰਾਹਟ ਹੋ ਸਕਦੀ ਹੈ। ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਤੌਰ \'ਤੇ <xliff:g id="APP_NAME">%1$s</xliff:g> ਤੋਂ ਗੱਲਾਂਬਾਤਾਂ ਬਬਲ ਵਜੋਂ ਦਿਸਦੀਆਂ ਹਨ।"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"ਡੀਵਾਈਸ ਸੈਟਿੰਗਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਘੰਟੀ ਵੱਜ ਸਕਦੀ ਹੈ ਜਾਂ ਥਰਥਰਾਹਟ ਹੋ ਸਕਦੀ ਹੈ"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ਡੀਵਾਈਸ ਸੈਟਿੰਗਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਘੰਟੀ ਵੱਜ ਸਕਦੀ ਹੈ ਜਾਂ ਥਰਥਰਾਹਟ ਹੋ ਸਕਦੀ ਹੈ। ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਤੌਰ \'ਤੇ <xliff:g id="APP_NAME">%1$s</xliff:g> ਬਬਲ ਤੋਂ ਗੱਲਾਂਬਾਤਾਂ।"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ਸਿਸਟਮ ਨੂੰ ਨਿਰਧਾਰਤ ਕਰਨ ਦਿਓ ਕਿ ਇਸ ਸੂਚਨਾ ਲਈ ਕੋਈ ਧੁਨੀ ਵਜਾਉਣੀ ਚਾਹੀਦੀ ਹੈ ਜਾਂ ਥਰਥਰਾਹਟ ਕਰਨੀ ਚਾਹੀਦੀ ਹੈ"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>ਸਥਿਤੀ:</b> ਦਰਜਾ ਵਧਾ ਕੇ ਪੂਰਵ-ਨਿਰਧਾਰਤ \'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>ਸਥਿਤੀ:</b> ਦਰਜਾ ਘਟਾ ਕੇ ਸ਼ਾਂਤ \'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਐਪ ਚੁਣੋ"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ।}one{# ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ।}other{# ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕੀਤੇ ਗਏ।}}"</string> <string name="controls_removed" msgid="3731789252222856959">"ਹਟਾਇਆ ਗਿਆ"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"ਮਨਪਸੰਦ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ਮਨਪਸੰਦ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ, ਸਥਾਨ <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ਮਨਪਸੰਦ ਵਿੱਚੋਂ ਹਟਾਇਆ ਗਿਆ"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ਤੋਂ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ਅਣਕੀਤਾ ਕਰੋ"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਉਣ ਲਈ ਨੇੜੇ ਲਿਜਾਓ"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ਇੱਥੇ ਚਲਾਉਣ ਲਈ, <xliff:g id="DEVICENAME">%1$s</xliff:g> ਦੇ ਨੇੜੇ ਲਿਆਓ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"ਕੋਈ ਗੜਬੜ ਹੋ ਗਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"ਲੋਡ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ਟੈਬਲੈੱਟ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ਨਹੀਂ ਮਿਲਿਆ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ਕੰਟਰੋਲ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"ਗੜਬੜ, ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string> <string name="controls_menu_add" msgid="4447246119229920050">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰੋ"</string> <string name="controls_menu_edit" msgid="890623986951347062">"ਕੰਟਰੋਲਾਂ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ਆਊਟਪੁੱਟ ਸ਼ਾਮਲ ਕਰੋ"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"ਗਰੁੱਪ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ਡੀਵਾਈਸ ਨੂੰ ਚੁਣਿਆ ਗਿਆ"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ਅਵਾਜ਼"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ਸਪੀਕਰ ਅਤੇ ਡਿਸਪਲੇਆਂ"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ਸੁਝਾਏ ਗਏ ਡੀਵਾਈਸ"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ਪ੍ਰਸਾਰਨ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"ਪ੍ਰਸਾਰਨ"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ਅਨੁਰੂਪ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸਾਂ ਨਾਲ ਨਜ਼ਦੀਕੀ ਲੋਕ ਤੁਹਾਡੇ ਵੱਲੋਂ ਪ੍ਰਸਾਰਨ ਕੀਤੇ ਜਾ ਰਹੇ ਮੀਡੀਆ ਨੂੰ ਸੁਣ ਸਕਦੇ ਹਨ"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ਇਹ ਸਕ੍ਰੀਨ ਬੰਦ ਹੋ ਜਾਵੇਗੀ"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਖੋਲ੍ਹਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਆਲੇ-ਦੁਆਲੇ ਫਲਿੱਪ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ਬੈਟਰੀ ਬਾਕੀ"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ਆਪਣੇ ਸਟਾਈਲਸ ਨੂੰ ਚਾਰਜਰ ਨਾਲ ਕਨੈਕਟ ਕਰੋ"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"ਸਟਾਈਲਸ ਦੀ ਬੈਟਰੀ ਘੱਟ ਹੈ"</string> + <string name="video_camera" msgid="7654002575156149298">"ਵੀਡੀਓ ਕੈਮਰਾ"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"ਇਸ ਪ੍ਰੋਫਾਈਲ ਤੋਂ ਕਾਲ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"ਤੁਹਾਡੀ ਕਾਰਜ ਨੀਤੀ ਤੁਹਾਨੂੰ ਸਿਰਫ਼ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਤੋਂ ਹੀ ਫ਼ੋਨ ਕਾਲਾਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਜਾਓ"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"ਬੰਦ ਕਰੋ"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 08a7ba15cdfc..4abddab65602 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Przycięcie dolnej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Przycięcie lewej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Przycięcie prawej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Służbowe zrzuty ekranu są zapisywane w aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Pliki"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Nagrywanie ekranu"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Przetwarzam nagrywanie ekranu"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Stałe powiadomienie o sesji rejestrowania zawartości ekranu"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatycznie"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Bez dźwięku i wibracji"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Brak dźwięku i wibracji, wyświetla się niżej w sekcji rozmów"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Może włączać dzwonek lub wibracje w zależności od ustawień telefonu"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Może włączać dzwonek lub wibracje w zależności od ustawień telefonu. Rozmowy z aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g> są domyślnie wyświetlane jako dymki."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Może włączać dzwonek lub wibracje w zależności od ustawień urządzenia"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Może włączyć dzwonek lub wibracje w zależności od ustawień urządzenia. Rozmowy z aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g> są domyślnie wyświetlane jako dymki."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Pozwól systemowi decydować, czy o powiadomieniu powinien informować dźwięk czy wibracja"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Stan:</b> zmieniony na Domyślny"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Stan:</b> zmieniono na Ciche"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Wybierz aplikację, do której chcesz dodać elementy sterujące"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Dodano # element sterujący.}few{Dodano # elementy sterujące.}many{Dodano # elementów sterujących.}other{Dodano # elementu sterującego.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Usunięto"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Dodano do ulubionych"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Dodano do ulubionych, pozycja <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Usunięto z ulubionych"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> w aplikacji <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Cofnij"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Przysuń się bliżej, aby odtwarzać na urządzeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Aby zagrać tutaj, przysuń bliżej urządzenie <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Odtwarzam na ekranie <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Coś poszło nie tak. Spróbuj ponownie."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Wczytuję"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Nieaktywny, sprawdź aplikację"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nie znaleziono"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Element jest niedostępny"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Błąd, spróbuj ponownie"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj elementy sterujące"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Edytuj elementy sterujące"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodaj urządzenia wyjściowe"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Wybrano 1 urządzenie"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Głośność"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Głośniki i wyświetlacze"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Proponowane urządzenia"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jak działa transmitowanie"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Transmisja"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osoby w pobliżu ze zgodnymi urządzeniami Bluetooth mogą słuchać transmitowanych przez Ciebie multimediów"</string> @@ -1020,11 +1021,17 @@ <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Rozłóż telefon, aby uzyskać lepszej jakości selfie"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Przełączyć na przedni wyświetlacz?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Użyj tylnego aparatu, aby zrobić szersze zdjęcie o większej rozdzielczości."</string> - <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* Ekran się wyłączy"</b></string> + <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* Ten ekran się wyłączy"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Składane urządzenie jest rozkładane"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Składane urządzenie jest obracane"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Pozostało <xliff:g id="PERCENTAGE">%s</xliff:g> baterii"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Podłącz rysik do ładowarki"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Słaba bateria w rysiku"</string> + <string name="video_camera" msgid="7654002575156149298">"Kamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nie można nawiązać połączenia z tego profilu"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Zasady obowiązujące w firmie zezwalają na nawiązywanie połączeń telefonicznych tylko w profilu służbowym"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Przełącz na profil służbowy"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zamknij"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index fa5e83f37c8c..f1adbfd12441 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Som e vibração desativados"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"O som e a vibração estão desativados, e o balão aparece na parte inferior da seção de conversa"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Pode vibrar ou tocar com base nas configurações do smartphone"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pode vibrar ou tocar com base nas configurações do smartphone. As conversas do app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem em balões por padrão."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Pode vibrar ou tocar com base nas configurações do dispositivo"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Pode vibrar ou tocar com base nas configurações do dispositivo. As conversas do app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem em balões por padrão."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faça com que o sistema determine se a notificação resultará em som ou vibração"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> promovida a Padrão"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> rebaixada a Silenciosa"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Escolha um app para adicionar controles"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# controle adicionado.}one{# controle adicionado.}many{# de controles adicionados.}other{# controles adicionados.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Removido"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Adicionar o app <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Quando você adiciona o app <xliff:g id="APPNAME">%s</xliff:g>, ele pode incluir controles e conteúdo neste painel. Em alguns casos, é possível escolher quais controles aparecem aqui."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Adicionado como favorito"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Adicionado como favorito (posição <xliff:g id="NUMBER">%d</xliff:g>)"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Removido dos favoritos"</string> @@ -852,9 +854,8 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime os dispositivos para tocar a mídia neste: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> - <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Mídia aberta no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para abrir aqui, aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Tocando no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Algo deu errado. Tente novamente."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Carregando"</string> <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Erro. Tente novamente"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controles"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Adicionar app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adicionar saídas"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Esta tela vai ser desativada"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateria restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecte sua stylus a um carregador"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da stylus fraca"</string> + <string name="video_camera" msgid="7654002575156149298">"Filmadora"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Não é possível fazer uma ligação por este perfil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Sua política de trabalho só permite fazer ligações pelo perfil de trabalho"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Alternar para o perfil de trabalho"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Fechar"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index fbedb3aa1cba..4aa89ea4533c 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sem som ou vibração"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sem som ou vibração e aparece na parte inferior na secção de conversas."</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Pode tocar ou vibrar com base nas definições do telemóvel."</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pode tocar ou vibrar com base nas definições do telemóvel. As conversas da app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem como um balão por predefinição."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Pode tocar ou vibrar com base nas definições do dispositivo"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Pode tocar ou vibrar com base nas definições do dispositivo. As conversas da app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem como um balão por predefinição."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faça com que o sistema determine se esta notificação deve emitir um som ou uma vibração"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Estado:</b> promovida para Predefinida"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Estado:</b> despromovida para Silenciosa"</string> @@ -800,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Escolha uma app para adicionar controlos"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# controlo adicionado.}many{# controlos adicionados.}other{# controlos adicionados.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Removido"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Adicionado aos favoritos"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Adicionados aos favoritos, posição <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Removido dos favoritos"</string> @@ -852,8 +856,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Anular"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime-se para reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproduzir aqui, aproxime-se do <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"A reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Algo correu mal. Tente novamente."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"A carregar"</string> @@ -867,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Erro. Tente novamente."</string> <string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controlos"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Editar controlos"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adicione saídas"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string> @@ -1021,4 +1026,12 @@ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável a ser virado ao contrário"</string> <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de bateria restante"</string> <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ligue a caneta stylus a um carregador"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da caneta stylus fraca"</string> + <string name="video_camera" msgid="7654002575156149298">"Câmara de vídeo"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Não é possível ligar a partir deste perfil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"A sua Política de Trabalho só lhe permite fazer chamadas telefónicas a partir do perfil de trabalho"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Mudar para perfil de trabalho"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Fechar"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index fa5e83f37c8c..f1adbfd12441 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Som e vibração desativados"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"O som e a vibração estão desativados, e o balão aparece na parte inferior da seção de conversa"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Pode vibrar ou tocar com base nas configurações do smartphone"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pode vibrar ou tocar com base nas configurações do smartphone. As conversas do app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem em balões por padrão."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Pode vibrar ou tocar com base nas configurações do dispositivo"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Pode vibrar ou tocar com base nas configurações do dispositivo. As conversas do app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem em balões por padrão."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faça com que o sistema determine se a notificação resultará em som ou vibração"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> promovida a Padrão"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> rebaixada a Silenciosa"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Escolha um app para adicionar controles"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# controle adicionado.}one{# controle adicionado.}many{# de controles adicionados.}other{# controles adicionados.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Removido"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Adicionar o app <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Quando você adiciona o app <xliff:g id="APPNAME">%s</xliff:g>, ele pode incluir controles e conteúdo neste painel. Em alguns casos, é possível escolher quais controles aparecem aqui."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Adicionado como favorito"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Adicionado como favorito (posição <xliff:g id="NUMBER">%d</xliff:g>)"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Removido dos favoritos"</string> @@ -852,9 +854,8 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime os dispositivos para tocar a mídia neste: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> - <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Mídia aberta no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para abrir aqui, aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Tocando no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Algo deu errado. Tente novamente."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Carregando"</string> <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Erro. Tente novamente"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controles"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Adicionar app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adicionar saídas"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Esta tela vai ser desativada"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateria restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecte sua stylus a um carregador"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da stylus fraca"</string> + <string name="video_camera" msgid="7654002575156149298">"Filmadora"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Não é possível fazer uma ligação por este perfil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Sua política de trabalho só permite fazer ligações pelo perfil de trabalho"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Alternar para o perfil de trabalho"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Fechar"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index deef0762f4d8..894e22e12c22 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Marginea de jos la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Marginea stângă la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Marginea dreaptă la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Capturile de ecran pentru serviciu sunt salvate în aplicația <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fișiere"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Recorder pentru ecran"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Se procesează înregistrarea"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificare în curs pentru o sesiune de înregistrare a ecranului"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automat"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Fără sunet sau vibrații"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Fără sunet sau vibrații și apare în partea de jos a secțiunii de conversație"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Poate să sune sau să vibreze, în funcție de setările telefonului"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Poate să sune sau să vibreze, în funcție de setările telefonului. Conversațiile din balonul <xliff:g id="APP_NAME">%1$s</xliff:g> în mod prestabilit."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Poate să sune sau să vibreze, în funcție de setările dispozitivului"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Poate să sune sau să vibreze, în funcție de setările dispozitivului. Conversațiile din balonul <xliff:g id="APP_NAME">%1$s</xliff:g> în mod prestabilit."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Solicită-i sistemului să stabilească dacă această notificare e sonoră sau cu vibrații."</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Stare:</b> promovată la prestabilită"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Stare:</b> setată ca Silențioasă"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Alege aplicația pentru a adăuga comenzi"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{S-a adăugat # comandă.}few{S-au adăugat # comenzi.}other{S-au adăugat # de comenzi.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Eliminată"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Adaugi <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Când adaugi <xliff:g id="APPNAME">%s</xliff:g>, aplicația poate să adauge comenzi și conținut pe acest panou. În anumite aplicații, poți să alegi comenzile care se afișează aici."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Marcată ca preferată"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Marcată ca preferată, poziția <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"S-a anulat marcarea ca preferată"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Redă <xliff:g id="SONG_NAME">%1$s</xliff:g> în <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Anulează"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Apropie-te pentru a reda pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pentru a reda conținutul aici, apropie-te de <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Se redă pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"A apărut o eroare. Încearcă din nou."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Se încarcă"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tabletă"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verifică aplicația"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nu s-a găsit"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Comanda este indisponibilă"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Eroare, încearcă din nou"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Adaugă comenzi"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Editează comenzile"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Adaugă o aplicație"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adaugă ieșiri"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"S-a selectat un dispozitiv"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volum"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Difuzoare și afișaje"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispozitive sugerate"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cum funcționează transmisia"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Transmite"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Persoanele din apropiere cu dispozitive Bluetooth compatibile pot asculta conținutul pe care îl transmiți"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Acest ecran se va dezactiva"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispozitiv pliabil care este desfăcut"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispozitiv pliabil care este întors"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> baterie rămasă"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conectează-ți creionul la un încărcător"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Nivelul bateriei creionului este scăzut"</string> + <string name="video_camera" msgid="7654002575156149298">"Cameră video"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nu poți iniția apeluri din acest profil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Politica privind activitatea îți permite să efectuezi apeluri telefonice numai din profilul de serviciu"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Comută la profilul de serviciu"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Închide"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 97d0d70c4a8c..e55572c09471 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Граница снизу: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Граница слева: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Граница справа: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Скриншоты сохраняются в приложении \"<xliff:g id="APP">%1$s</xliff:g>\" в рабочем профиле"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлы"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Запись видео с экрана"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обработка записи с экрана…"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущее уведомление для записи видео с экрана"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматически"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звука и вибрации"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звука или вибрации, появляется в нижней части списка разговоров"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Звонок или вибрация в зависимости от настроек телефона"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Звонок или вибрация в зависимости от настроек телефона. Разговоры из приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" по умолчанию появляются в виде всплывающего чата"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Звонок или вибрация в зависимости от настроек устройства"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Звонок или вибрация в зависимости от настроек устройства. Разговоры из приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" по умолчанию появляются в виде всплывающего чата."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Система будет сама определять, включать ли звуковой сигнал или вибрацию для уведомления"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Статус:</b> повышено до уровня \"По умолчанию\""</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Статус:</b> понижено до уровня \"Без звука\""</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Чтобы добавить виджеты управления, выберите приложение"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Добавлен # элемент управления.}one{Добавлен # элемент управления.}few{Добавлено # элемента управления.}many{Добавлено # элементов управления.}other{Добавлено # элемента управления.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Удалено"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Добавить приложение \"<xliff:g id="APPNAME">%s</xliff:g>\"?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Приложение \"<xliff:g id="APPNAME">%s</xliff:g>\" может добавить на эту панель элементы управления и контент. Некоторые приложения позволяют выбирать, какие элементы будут здесь показаны."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Добавлено в избранное"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Добавлено в избранное на позицию <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Не добавлено в избранное"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" из приложения \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Отменить"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Чтобы начать трансляцию на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", подойдите к нему ближе."</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Чтобы начать воспроизведение здесь, подойдите ближе к устройству (<xliff:g id="DEVICENAME">%1$s</xliff:g>)."</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Воспроизводится на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\"."</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Произошла ошибка. Повторите попытку."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Загрузка…"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Нет ответа. Проверьте приложение."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не найдено."</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Управление недоступно"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Ошибка. Повторите попытку."</string> <string name="controls_menu_add" msgid="4447246119229920050">"Добавить виджеты"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Изменить виджеты"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Добавить приложение"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Добавление устройств вывода"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Группа"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрано 1 устройство"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Громкость"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Колонки и дисплеи"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Рекомендуемые устройства"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Как работают трансляции"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Трансляция"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Находящиеся рядом с вами люди с совместимыми устройствами Bluetooth могут слушать медиафайлы, которые вы транслируете."</string> @@ -1018,13 +1016,19 @@ <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Отмена"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Перевернуть сейчас"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Разложите телефон, чтобы селфи получилось лучше"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Перевернули телефон передним экраном к себе?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Перейти на передний экран?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Используйте основную камеру с широкоугольным объективом и высоким разрешением."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Этот экран отключится"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складное устройство в разложенном виде"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перевернутое складное устройство"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Уровень заряда батареи: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Поставьте стилус на зарядку."</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Низкий заряд батареи стилуса"</string> + <string name="video_camera" msgid="7654002575156149298">"Видеокамера"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Невозможно совершить звонок из этого профиля"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Согласно правилам вашей организации вы можете совершать телефонные звонки только из рабочего профиля."</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Перейти в рабочий профиль"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Закрыть"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 63acd771f61f..8ea1695d4c8c 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"පහළ සීමාව සියයට <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"වම් සීමාව සියයට <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"දකුණු සීමාව සියයට <xliff:g id="PERCENT">%1$d</xliff:g>"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"වැඩ තිර රූ <xliff:g id="APP">%1$s</xliff:g> යෙදුම තුළ සුරැකෙයි"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ගොනු"</string> <string name="screenrecord_name" msgid="2596401223859996572">"තිර රෙකෝඩරය"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"තිර පටිගත කිරීම සකසමින්"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"තිර පටිගත කිරීමේ සැසියක් සඳහා කෙරෙන දැනුම් දීම"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ස්වයංක්රිය"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"හඬක් හෝ කම්පනයක් නැත"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"හඬක් හෝ කම්පනයක් නැති අතර සංවාද කොටසේ පහළම දිස් වේ"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"දුරකථන සැකසීම් මත පදනම්ව නාද කිරීමට හෝ කම්පනය කිරීමට හැකිය"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"දුරකථන සැකසීම් මත පදනම්ව නාද කිරීමට හෝ කම්පනය කිරීමට හැකිය. <xliff:g id="APP_NAME">%1$s</xliff:g> වෙතින් සංවාද පෙරනිමියෙන් බුබුළු දමයි"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"උපාංග සැකසීම් මත පදනම්ව නාද වීමට හෝ කම්පනය විය හැක"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"උපාංග සැකසීම් මත පදනම්ව නාද වීමට හෝ කම්පනය විය හැක. <xliff:g id="APP_NAME">%1$s</xliff:g> වෙතින් සංවාද පෙරනිමියෙන් බුබුළු දමයි."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"මෙම දැනුම් දීම ශබ්දයක් හෝ කම්පනයක් ඇති කළ යුතු ද යන්න පද්ධතිය මගින් තීරණය කර තිබේද"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>තත්ත්වය:</b> පෙරනිමි වෙත ප්රවර්ධනය කරන ලදි"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>තත්ත්වය:</b> නිශ්ශබ්ද වෙත පහත දමන ලදි"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"පාලන එක් කිරීමට යෙදුම තෝරා ගන්න"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# පාලනයක් එක් කර ඇත.}one{පාලන #ක් එක් කර ඇත.}other{පාලන #ක් එක් කර ඇත.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"ඉවත් කළා"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"ප්රියතම කළා"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ප්රියතම කළා, තත්ත්ව <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ප්රියතම වෙතින් ඉවත් කළා"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> වෙතින් වාදනය කරන්න"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"පසුගමනය කරන්න"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කිරීමට වඩාත් ළං වන්න"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"මෙහි වාදනය කිරීම සඳහා, <xliff:g id="DEVICENAME">%1$s</xliff:g> වෙත සමීප වන්න"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කරමින්"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"යම් දෙයක් වැරදිණි. නැවත උත්සාහ කරන්න."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"පූරණය වේ"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ටැබ්ලටය"</string> <string name="controls_error_timeout" msgid="794197289772728958">"අක්රියයි, යෙදුම පරීක්ෂා කරන්න"</string> <string name="controls_error_removed" msgid="6675638069846014366">"හමු නොවිණි"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"පාලනය ලබා ගත නොහැකිය"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"දෝෂයකි, නැවත උත්සාහ කරන්න"</string> <string name="controls_menu_add" msgid="4447246119229920050">"පාලන එක් කරන්න"</string> <string name="controls_menu_edit" msgid="890623986951347062">"පාලන සංස්කරණය කරන්න"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ප්රතිදාන එක් කරන්න"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"සමූහය"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"උපාංග 1ක් තෝරන ලදී"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"හඬ පරිමාව"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ස්පීකර් සහ සංදර්ශක"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"යෝජිත උපාංග"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"විකාශනය ක්රියා කරන ආකාරය"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"විකාශනය"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ගැළපෙන බ්ලූටූත් උපාංග සහිත ඔබ අවට සිටින පුද්ගලයින්ට ඔබ විකාශනය කරන මාධ්යයට සවන් දිය හැකිය"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ මෙම තිරය ක්රියා විරහිත වනු ඇත"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"දිග හැරෙමින් පවතින නැමිය හැකි උපාංගය"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"වටා පෙරළෙමින් තිබෙන නැමිය හැකි උපාංගය"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> බැටරිය ඉතිරිව ඇත"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ඔබේ පන්හිඳ චාජරයකට සම්බන්ධ කරන්න"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"පන්හිඳ බැටරිය අඩුයි"</string> + <string name="video_camera" msgid="7654002575156149298">"වීඩියෝ කැමරාව"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"මෙම පැතිකඩෙන් ඇමතීමට නොහැක"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"ඔබේ වැඩ ප්රතිපත්තිය ඔබට කාර්යාල පැතිකඩෙන් පමණක් දුරකථන ඇමතුම් ලබා ගැනීමට ඉඩ සලසයි"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"කාර්යාල පැතිකඩ වෙත මාරු වන්න"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"වසන්න"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 5a5d87a320ce..e321fcbff274 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"<xliff:g id="PERCENT">%1$d</xliff:g> %% dolnej hranice"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> %% ľavej hranice"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> %% pravej hranice"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Pracovné snímky obrazovky sú uložené v aplikácii <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Rekordér obrazovky"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Spracúva sa záznam obrazovky"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Zobrazuje sa upozornenie týkajúce sa relácie záznamu obrazovky"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automaticky"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Žiadny zvuk ani vibrácie"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Žiadny zvuk ani vibrácie a zobrazuje sa nižšie v sekcii konverzácií"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Môže zvoniť či vibrovať podľa nastavení telefónu"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Môže zvoniť alebo vibrovať podľa nastavení telefónu. Predvolene sa zobrazia konverzácie z bubliny <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Môže zvoniť či vibrovať podľa nastavení v zariadení"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Môže zvoniť alebo vibrovať podľa nastavení v zariadení. Predvolene sa zobrazia konverzácie z bubliny aplikácie <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Nechajte systém určiť, či má toto upozornenie vydávať zvuk alebo vibrovať"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Stav:</b> Preradené vyššie do kategórie Predvolené"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Preradené nižšie do kategórie Tiché"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikáciu, ktorej ovládače si chcete pridať"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Bol pridaný # ovládací prvok.}few{Boli pridané # ovládacie prvky.}many{# controls added.}other{Bolo pridaných # ovládacích prvkov.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Odstránené"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Chcete pridať aplikáciu <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Keď pridáte aplikáciu <xliff:g id="APPNAME">%s</xliff:g>, bude môcť pridať ovládanie a obsah na tento panel. V prípade niektorých aplikácií môžete vybrať, ktoré ovládacie prvky sa tu majú zobraziť."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Pridané medzi obľúbené"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Pridané medzi obľúbené, pozícia <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Odstránené z obľúbených"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Späť"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ak chcete prehrávať v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>, priblížte sa k nemu"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ak tu chcete prehrávať obsah, priblížte zariadenie k zariadeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Prehráva sa v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Niečo sa pokazilo. Skúste to znova."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Načítava sa"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktívne, preverte aplikáciu"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nenájdené"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Ovládač nie je k dispozícii"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Chyba, skúste to znova"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Pridať ovládače"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Upraviť ovládače"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Pridať aplikáciu"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Pridanie výstupov"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 vybrané zariadenie"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hlasitosť"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Reproduktory a obrazovky"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Navrhované zariadenia"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Ako vysielanie funguje"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Vysielanie"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Ľudia v okolí s kompatibilnými zariadeniami s rozhraním Bluetooth si môžu vypočuť médiá, ktoré vysielate"</string> @@ -1016,15 +1014,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• K dispozícii je minimálne jedno zariadenie"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Pridržte skratku"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Zrušiť"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Prevráťte"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Otočiť"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Ak chcete lepšie selfie, rozložte telefón"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Prevrátiť na pred. obrazovku pre lepšie selfie?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Otočiť na prednú obrazovku pre lepšie selfie?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Pomocou zadného fotoaparátu vytvorte širšiu fotku s vyšším rozlíšením."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Táto obrazovka sa vypne"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozloženie skladacieho zariadenia"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Prevrátenie skladacieho zariadenia"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Zostáva <xliff:g id="PERCENTAGE">%s</xliff:g> batérie"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Pripojte dotykové pero k nabíjačke"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Stav batérie dotykového pera je nízky"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Z tohto profilu nemôžete volať"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Pracovné pravidlá vám umožňujú telefonovať iba v pracovnom profile"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Prepnúť na pracovný profil"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zavrieť"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index c16bf230a9b3..a5989bdba1d9 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Meja spodaj <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Meja levo <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Meja desno <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Posnetki zaslona v delovnem profilu so shranjeni v aplikaciji <xliff:g id="APP">%1$s</xliff:g>."</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Datoteke"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Snemalnik zaslona"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obdelava videoposnetka zaslona"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Nenehno obveščanje o seji snemanja zaslona"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Samodejno"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Brez zvočnega opozarjanja ali vibriranja."</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Brez zvočnega opozarjanja ali vibriranja, prikaz nižje v razdelku Pogovor."</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Zvonjenje ali vibriranje je omogočeno na podlagi nastavitev telefona."</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Zvonjenje ali vibriranje je omogočeno na podlagi nastavitev telefona. Pogovori v aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> so privzeto prikazani v oblačkih."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Zvonjenje ali vibriranje je omogočeno na podlagi nastavitev naprave."</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Zvonjenje ali vibriranje je omogočeno na podlagi nastavitev naprave. Pogovori v aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> so privzeto prikazani v oblačkih."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Naj sistem določi, ali ob prejemu tega obvestila naprava predvaja zvok ali zavibrira"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Stanje:</b> Uvrščeno med privzeta obvestila"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Stanje:</b> Uvrščeno med obvestila brez zvoka"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Izberite aplikacijo za dodajanje kontrolnikov"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrolnik je dodan.}one{# kontrolnik je dodan.}two{# kontrolnika sta dodana.}few{# kontrolniki so dodani.}other{# kontrolnikov je dodanih.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Odstranjeno"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Dodano med priljubljene"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Dodano med priljubljene, položaj <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Odstranjeno iz priljubljenih"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Razveljavi"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Za predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g> bolj približajte telefon"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Če želite predvajati tukaj, se približajte napravi <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Prišlo je do napake. Poskusite znova."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Nalaganje"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablični računalnik"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, poglejte aplikacijo"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ni mogoče najti"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrolnik ni na voljo"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Napaka, poskusite znova"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Dodajte kontrolnike"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Uredite kontrolnike"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodajanje izhodov"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izbrana je ena naprava"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Glasnost"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Zvočniki in zasloni"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Predlagane naprave"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako deluje oddajanje"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Oddajanje"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osebe v bližini z združljivo napravo Bluetooth lahko poslušajo predstavnost, ki jo oddajate."</string> @@ -1016,15 +1017,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Na voljo mora biti vsaj ena naprava."</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Pridržite bližnjico"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Prekliči"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Obrnite"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Obrni"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Razprite telefon za boljši selfi"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Obrnite telefon na sprednji zaslon za boljši selfi"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Uporabite hrbtni fotoaparat, da posnamete širšo sliko višje ločljivosti."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ta zaslon se bo izklopil."</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Razpiranje zložljive naprave"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Obračanje zložljive naprave"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostanek energije baterije: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Povežite pisalo s polnilnikom."</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Skoraj prazna baterija pisala"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Ni mogoče klicati iz tega profila"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Službeni pravilnik dovoljuje opravljanje telefonskih klicev le iz delovnega profila."</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Preklopi na delovni profil"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zapri"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index e9a6e8c9efb4..a6db9966624a 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Kufiri i poshtëm <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kufiri i majtë <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Kufiri i djathtë <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Pamjet e ekranit të punës janë ruajtur në aplikacionin \"<xliff:g id="APP">%1$s</xliff:g>\""</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Skedarë"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Regjistruesi i ekranit"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Regjistrimi i ekranit po përpunohet"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Njoftim i vazhdueshëm për një seancë regjistrimi të ekranit"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatike"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Asnjë tingull ose dridhje"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Asnjë tingull ose dridhje dhe shfaqet më poshtë në seksionin e bisedave"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Mund të bjerë zilja ose të dridhet në bazë të cilësimeve të telefonit"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Mund të bjerë zilja ose të dridhet në bazë të cilësimeve të telefonit. Si parazgjedhje, bisedat nga <xliff:g id="APP_NAME">%1$s</xliff:g> shfaqen si flluska."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Mund të bjerë zilja ose të dridhet në bazë të cilësimeve të pajisjes"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Mund të bjerë zilja ose të dridhet në bazë të cilësimeve të pajisjes. Bisedat nga flluska e <xliff:g id="APP_NAME">%1$s</xliff:g> si parazgjedhje."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Kërkoji sistemit të përcaktojë nëse ky njoftim duhet të lëshojë tingull apo dridhje"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Statusi:</b> Promovuar si parazgjedhje"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Statusi:</b> Ulur në nivel si në heshtje"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Zgjidh aplikacionin për të shtuar kontrollet"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{U shtua # kontroll.}other{U shtuan # kontrolle.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"E hequr"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Të shtohet <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Kur shton <xliff:g id="APPNAME">%s</xliff:g>, ai mund t\'i shtojë kontrolle dhe përmbajtje këtij paneli. Në disa aplikacione, mund të zgjedhësh se cilat kontrolle shfaqen këtu."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"E shtuar te të preferuarat"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"E shtuar te të preferuarat, pozicioni <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"E hequr nga të preferuarat"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Zhbëj"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Afrohu për të luajtur në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Për ta luajtur këtu, afrohu më shumë te <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Po luhet në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Ndodhi një gabim. Provo përsëri."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Po ngarkohet"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Joaktive, kontrollo aplikacionin"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nuk u gjet"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrolli është i padisponueshëm"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Gabim, provo sërish"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Shto kontrollet"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Modifiko kontrollet"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Shto një aplikacion"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Shto daljet"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupi"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 pajisje e zgjedhur"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volumi"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altoparlantët dhe ekranet"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Pajisjet e sugjeruara"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Si funksionon transmetimi"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Transmetimi"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personat në afërsi me ty me pajisje të përputhshme me Bluetooth mund të dëgjojnë median që ti po transmeton"</string> @@ -1016,15 +1014,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Ofrohet të paktën një pajisje"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Prek dhe mbaj shtypur shkurtoren"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Anulo"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"U kthye tani"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Ktheje tani"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Shpalos telefonin për një selfi më të mirë"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Të kthehet tek ekrani para për selfi më të mirë?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Përdor lenten e kamerës së pasme për një fotografi më të gjerë me rezolucion më të lartë."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ky ekran do të fiket"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Pajisja e palosshme duke u hapur"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Pajisja e palosshme duke u rrotulluar"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Përqindja e mbetur e baterisë: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Lidhe stilolapsin me një karikues"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria e stilolapsit në nivel të ulët"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nuk mund të telefonosh nga ky profil"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Politika jote e punës të lejon të bësh telefonata vetëm nga profili i punës"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Kalo te profili i punës"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Mbyll"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 153ebbf3948b..a3ed992a3680 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Доња ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лева ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Десна ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Снимци екрана за посао се чувају у апликацији <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Фајлови"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Снимач екрана"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обрађујемо видео снимка екрана"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Обавештење о сесији снимања екрана је активно"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Аутоматска"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звука и вибрирања"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звука и вибрирања и приказује се у наставку одељка за конверзације"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Може да звони или вибрира у зависности од подешавања телефона"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Може да звони или вибрира у зависности од подешавања телефона. Конверзације из апликације <xliff:g id="APP_NAME">%1$s</xliff:g> се подразумевано приказују у облачићима."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Може да звони или вибрира у зависности од подешавања уређаја"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Може да звони или вибрира у зависности од подешавања уређаја. Конверзације из апликације <xliff:g id="APP_NAME">%1$s</xliff:g> подразумевано се приказују у облачићима."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Нека систем утврди да ли ово обавештење треба да емитује звук или да вибрира"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Статус:</b> Унапређено у Подразумевано"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Статус:</b> Деградирано у Нечујно"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Одаберите апликацију за додавање контрола"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# контрола је додата.}one{# контрола је додата.}few{# контроле су додате.}other{# контрола је додато.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Уклоњено"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Желите ли да додате <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Када додате апликацију <xliff:g id="APPNAME">%s</xliff:g>, она може да додаје контроле и садржај у ово окно. У неким апликацијама можете да изаберете које ће се контроле овде приказивати."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Означено је као омиљено"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Означено је као омиљено, <xliff:g id="NUMBER">%d</xliff:g>. позиција"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Уклоњено је из омиљених"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Опозови"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Приближите да бисте пуштали музику на: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Да бисте пуштали садржај овде, приближите уређају <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Пушта се на уређају <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Дошло је до грешке. Пробајте поново."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Учитава се"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно. Видите апликацију"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Није пронађено"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Контрола није доступна"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Грешка. Пробајте поново"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Додај контроле"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Измени контроле"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Додај апликацију"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Додајте излазе"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Изабран је 1 уређај"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Звук"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Звучници и екрани"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Предложени уређаји"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Како функционише емитовање"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Емитовање"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Људи у близини са компатибилним Bluetooth уређајима могу да слушају медијски садржај који емитујете"</string> @@ -1016,15 +1014,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• да је доступан барем један уређај"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Додирните и задржите пречицу"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Откажи"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Обрните"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Обрни"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Отворите телефон за бољи селфи"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Желите да обрнете на предњи екран за бољи селфи?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Користите задњу камеру да бисте снимили ширу слику са вишом резолуцијом."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Овај екран ће се искључити"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Уређај на преклоп се отвара"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Уређај на преклоп се обрће"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Преостало је још<xliff:g id="PERCENTAGE">%s</xliff:g> батерије"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Повежите писаљку са пуњачем"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Низак ниво батерије писаљке"</string> + <string name="video_camera" msgid="7654002575156149298">"Видео камера"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Не можете да упућујете позиве са овог профила"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Смернице за посао вам омогућавају да телефонирате само са пословног профила"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Пређи на пословни профил"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Затвори"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 38c7719c13ae..41fe5020b534 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Automatiskt"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Inga ljud eller vibrationer"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Inga ljud eller vibrationer och visas längre ned bland konversationerna"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan ringa eller vibrera beroende på inställningarna på telefonen"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan ringa eller vibrera beroende på inställningarna på telefonen. Konversationer från <xliff:g id="APP_NAME">%1$s</xliff:g> visas i bubblor som standard."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Kan ringa eller vibrera beroende på inställningarna på enheten"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kan ringa eller vibrera beroende på inställningarna på enheten. Konversationer från <xliff:g id="APP_NAME">%1$s</xliff:g> visas i bubblor som standard."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Låt systemet avgöra om den här aviseringen ska låta eller vibrera"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Ändrad till Standard"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Ändrad till Tyst"</string> @@ -800,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Välj en app om du vill lägga till snabbkontroller"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontroll har lagts till.}other{# kontroller har lagts till.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Har tagits bort"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Har lagts till som favorit"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Har lagts till som favorit, plats <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Har tagits bort från favoriter"</string> @@ -852,8 +856,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> från <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Ångra"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Flytta närmare för att spela upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Kom närmare <xliff:g id="DEVICENAME">%1$s</xliff:g> om du vill spela upp här"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Spelas upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Något gick fel. Försök igen."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Läser in"</string> @@ -867,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Fel, försök igen"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Lägg till snabbkontroller"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Redigera snabbkontroller"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Lägg till utgångar"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet har valts"</string> @@ -1014,13 +1019,19 @@ <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Avbryt"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Vänd nu"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Vik upp telefonen för att ta en bättre selfie"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Vill du ta en bättre selfie med främre skärmen?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Vill du ta en bättre selfie med främre kameran?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Använd den bakre kameran för att ta ett mer vidsträckt foto med högre upplösning."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Den här skärmen inaktiveras"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En vikbar enhet viks upp"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En vikbar enhet vänds"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> av batteriet återstår"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Anslut e-pennan till en laddare"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"E-pennans batterinivå är låg"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Det går inte att ringa från den här profilen"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Jobbprincipen tillåter endast att du ringer telefonsamtal från jobbprofilen"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Byt till jobbprofilen"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Stäng"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index e43acb8533ee..a126e7e7bca9 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Mpaka wa sehemu ya chini wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Mpaka wa sehemu ya kushoto wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Mpaka wa sehemu ya kulia wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Picha ya skrini ya kazi huhifadhiwa kwenye programu ya <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Faili"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Kinasa Skrini"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Inachakata rekodi ya skrini"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Arifa inayoendelea ya kipindi cha kurekodi skrini"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Otomatiki"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Hakuna sauti wala mtetemo"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Hakuna sauti wala mtetemo na huonekana upande wa chini katika sehemu ya mazungumzo"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Huenda ikalia au kutetema kulingana na mipangilio ya simu"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Huenda ikalia au kutetema kulingana na mipangilio ya simu. Mazungumzo kutoka kiputo cha <xliff:g id="APP_NAME">%1$s</xliff:g> kwa chaguomsingi."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Huenda ikalia au kutetema kulingana na mipangilio ya kifaa"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Huenda ikalia au kutetema kulingana na mipangilio ya kifaa. Mazungumzo kutoka kiputo cha <xliff:g id="APP_NAME">%1$s</xliff:g> kwa chaguomsingi."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Ruhusu mfumo ubainishe iwapo arifa hii inapaswa kutoa sauti au mtetemo"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Hali:</b> Imepandishwa Hadhi Kuwa Chaguomsingi"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Imeshushwa Hadhi Kuwa Kimya"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Chagua programu ili uweke vidhibiti"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Umeweka kidhibiti #.}other{Umeweka vidhibiti #.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Kimeondolewa"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Kimewekwa kwenye vipendwa"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Kimewekwa kwenye vipendwa, nafasi ya <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Kimeondolewa kwenye vipendwa"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> katika <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Tendua"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Sogeza karibu ili ucheze kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ili ucheze maudhui kwenye kifaa hiki, sogeza karibu na <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Inacheza kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Hitilafu fulani imetokea. Jaribu tena."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Inapakia"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"kompyuta kibao"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Haitumiki, angalia programu"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Hakipatikani"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kidhibiti hakipatikani"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Hitilafu, jaribu tena"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Weka vidhibiti"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Badilisha vidhibiti"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Weka vifaa vya kutoa sauti"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Kikundi"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Umechagua kifaa 1"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Sauti"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Spika na Skrini"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Vifaa Vilivyopendekezwa"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jinsi utangazaji unavyofanya kazi"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Tangaza"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Watu walio karibu nawe wenye vifaa oanifu vya Bluetooth wanaweza kusikiliza maudhui unayoyatangaza"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Skrini hii itajizima"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Kifaa kinachokunjwa kikikunjuliwa"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Kifaa kinachokunjwa kikigeuzwa"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Chaji ya betri imesalia <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Unganisha stylus yako kwenye chaja"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Chaji ya betri ya Stylus imepungua"</string> + <string name="video_camera" msgid="7654002575156149298">"Kamera ya kuchukulia video"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Huwezi kupiga simu kutoka kwenye wasifu huu"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Sera ya mahali pako pa kazi inakuruhusu upige simu kutoka kwenye wasifu wa kazini pekee"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Tumia wasifu wa kazini"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Funga"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index cc58ddb35c36..a5528a1cabea 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"கீழ் எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"இடது எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"வலது எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"பணிக் கணக்கு ஸ்கிரீன்ஷாட்டுகள் <xliff:g id="APP">%1$s</xliff:g> ஆப்ஸில் சேமிக்கப்படும்"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"ஸ்கிரீன் ரெக்கார்டர்"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ஸ்க்ரீன் ரெக்கார்டிங் செயலாக்கப்படுகிறது"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"திரை ரெக்கார்டிங் அமர்விற்கான தொடர் அறிவிப்பு"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"தானியங்கு"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"ஒலி / அதிர்வு இல்லை"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ஒலி / அதிர்வு இல்லாமல் உரையாடல் பிரிவின் கீழ்ப் பகுதியில் தோன்றும்"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"மொபைல் அமைப்புகளின் அடிப்படையில் ஒலிக்கலாம்/அதிரலாம்"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"மொபைல் அமைப்புகளின் அடிப்படையில் ஒலிக்கவோ அதிரவோ செய்யும். <xliff:g id="APP_NAME">%1$s</xliff:g> இலிருந்து வரும் உரையாடல்கள் இயல்பாகவே குமிழாகத் தோன்றும்."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"சாதன அமைப்புகளைப் பொறுத்து ஒலிக்கக்கூடும் அல்லது அதிர்வடையக்கூடும்"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"சாதன அமைப்புகளைப் பொறுத்து ஒலிக்கக்கூடும் அல்லது அதிர்வடையக்கூடும். இயல்பாக, <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸில் பெறப்படும் உரையாடல் அறிவிப்புகள் குமிழ்களாகத் தோன்றும்."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"இந்த அறிவிப்பு ஒலி எழுப்ப வேண்டுமா அதிர வேண்டுமா என்பதை சிஸ்டம் தீர்மானிக்கும்"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>நிலை:</b> இயல்புநிலைக்கு உயர்த்தி அமைக்கப்பட்டது"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>நிலை:</b> சைலன்ட் நிலைக்குக் குறைத்து அமைக்கப்பட்டது"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"கட்டுப்பாடுகளைச் சேர்க்க வேண்டிய ஆப்ஸைத் தேர்ந்தெடுங்கள்"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# கட்டுப்பாடு சேர்க்கப்பட்டது.}other{# கட்டுப்பாடுகள் சேர்க்கப்பட்டன.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"அகற்றப்பட்டது"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"பிடித்தவற்றில் சேர்க்கப்பட்டது"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"பிடித்தவற்றில் சேர்க்கப்பட்டது, நிலை <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"பிடித்தவற்றிலிருந்து நீக்கப்பட்டது"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%2$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"செயல்தவிர்"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் இயக்க உங்கள் சாதனத்தை அருகில் எடுத்துச் செல்லுங்கள்"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"இங்கு பிளே செய்ய உங்கள் சாதனத்தை <xliff:g id="DEVICENAME">%1$s</xliff:g>அருகில் நகர்த்துங்கள்"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் பிளே ஆகிறது"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"ஏதோ தவறாகிவிட்டது. மீண்டும் முயலவும்."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"ஏற்றுகிறது"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"டேப்லெட்"</string> <string name="controls_error_timeout" msgid="794197289772728958">"செயலில் இல்லை , சரிபார்க்கவும்"</string> <string name="controls_error_removed" msgid="6675638069846014366">"இல்லை"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"கட்டுப்பாடு இல்லை"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"பிழை, மீண்டும் முயலவும்"</string> <string name="controls_menu_add" msgid="4447246119229920050">"கட்டுப்பாடுகளைச் சேர்த்தல்"</string> <string name="controls_menu_edit" msgid="890623986951347062">"கட்டுப்பாடுகளை மாற்றுதல்"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"அவுட்புட்களைச் சேர்த்தல்"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"குழு"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 சாதனம் தேர்ந்தெடுக்கப்பட்டுள்ளது"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ஒலியளவு"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ஸ்பீக்கர்கள் & டிஸ்ப்ளேக்கள்"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"பரிந்துரைக்கப்படும் சாதனங்கள்"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"பிராட்காஸ்ட் எவ்வாறு செயல்படுகிறது?"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"பிராட்காஸ்ட்"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"நீங்கள் பிராட்காஸ்ட் செய்யும் மீடியாவை அருகிலுள்ளவர்கள் இணக்கமான புளூடூத் சாதனங்கள் மூலம் கேட்கலாம்"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ இந்தத் திரை ஆஃப் ஆகிவிடும்"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"மடக்கத்தக்க சாதனம் திறக்கப்படுகிறது"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"மடக்கத்தக்க சாதனம் ஃபிளிப் செய்யப்பட்டு திருப்பப்படுகிறது"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> பேட்டரி மீதமுள்ளது"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"உங்கள் ஸ்டைலஸைச் சார்ஜருடன் இணையுங்கள்"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"ஸ்டைலஸின் பேட்டரி குறைவாக உள்ளது"</string> + <string name="video_camera" msgid="7654002575156149298">"வீடியோ கேமரா"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"இந்தக் கணக்கிலிருந்து அழைக்க முடியாது"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"உங்கள் பணிக் கொள்கையின்படி நீங்கள் பணிக் கணக்கில் இருந்து மட்டுமே ஃபோன் அழைப்புகளைச் செய்ய முடியும்"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"பணிக் கணக்கிற்கு மாறு"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"மூடுக"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 1df7542232a5..1b82e39e7b6c 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"ఆటోమేటిక్"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"శబ్దం లేదా వైబ్రేషన్లు ఏవీ లేవు"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"శబ్దం లేదా వైబ్రేషన్ లేదు, సంభాషణ విభాగం దిగువన కనిపిస్తుంది"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"ఫోన్ సెట్టింగ్ల ఆధారంగా రింగ్ లేదా వైబ్రేట్ కావచ్చు"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ఫోన్ సెట్టింగ్ల ఆధారంగా రింగ్ లేదా వైబ్రేట్ కావచ్చు. <xliff:g id="APP_NAME">%1$s</xliff:g> నుండి సంభాషణలు ఆటోమేటిక్గా బబుల్గా కనిపిస్తాయి."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"పరికర సెట్టింగ్ల ఆధారంగా రింగ్ లేదా వైబ్రేట్ కావచ్చు"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"పరికర సెట్టింగ్ల ఆధారంగా రింగ్ లేదా వైబ్రేట్ కావచ్చు. <xliff:g id="APP_NAME">%1$s</xliff:g> నుండి సంభాషణలు ఆటోమేటిక్గా బబుల్లో కనిపిస్తాయి."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ఈ నోటిఫికేషన్ వచ్చినప్పుడు శబ్దం చేయాలా లేదా వైబ్రేట్ చేయాలా అనేది నిర్ణయించడానికి సిస్టమ్కు అనుమతి ఇవ్వండి"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>స్టేటస్:</b> ఆటోమేటిక్ సెట్టింగ్కు ప్రోమోట్ చేయబడింది"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>స్టేటస్:</b> నిశ్శబ్దం స్థాయికి తగ్గించబడింది"</string> @@ -800,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"కంట్రోల్స్ను యాడ్ చేయడానికి యాప్ను ఎంచుకోండి"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# కంట్రోల్ జోడించబడింది.}other{# కంట్రోల్స్ జోడించబడ్డాయి.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"తీసివేయబడింది"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"ఇష్టమైనదిగా గుర్తు పెట్టబడింది"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"<xliff:g id="NUMBER">%d</xliff:g>వ స్థానంలో ఇష్టమైనదిగా గుర్తు పెట్టబడింది"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ఇష్టమైనదిగా పెట్టిన గుర్తు తీసివేయబడింది"</string> @@ -852,8 +856,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> నుండి <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"చర్య రద్దు"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే చేయడానికి దగ్గరగా వెళ్లండి"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ఇక్కడ ఆడటానికి, <xliff:g id="DEVICENAME">%1$s</xliff:g>కు దగ్గరగా వెళ్లండి"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే అవుతోంది"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"ఏదో తప్పు జరిగింది. మళ్లీ ట్రై చేయండి."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"లోడ్ అవుతోంది"</string> @@ -867,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"ఎర్రర్, మళ్లీ ప్రయత్నించండి"</string> <string name="controls_menu_add" msgid="4447246119229920050">"కంట్రోల్స్ను జోడించండి"</string> <string name="controls_menu_edit" msgid="890623986951347062">"కంట్రోల్స్ను ఎడిట్ చేయండి"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"అవుట్పుట్లను జోడించండి"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"గ్రూప్"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 పరికరం ఎంచుకోబడింది"</string> @@ -1015,12 +1020,18 @@ <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"ఇప్పుడే తిప్పండి"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"మెరుగైన సెల్ఫీ కోసం ఫోన్ను అన్ఫోల్డ్ చేయండి"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"మంచి సెల్ఫీ కోసం ముందు వైపు డిస్ప్లేకు తిప్పాలా?"</string> - <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"అధిక రిజల్యూషన్తో పెద్ద ఫోటో కోసం వెనుక వైపున ఉన్న కెమెరాను ఉపయోగించండి."</string> + <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"వెనుక వైపున ఉన్న కెమెరాను ఉపయోగించి అధిక రిజల్యూషన్ గల, మరింత వెడల్పైన ఫోటోను పొందండి."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ఈ స్క్రీన్ ఆఫ్ అవుతుంది"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"మడవగల పరికరం విప్పబడుతోంది"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"మడవగల పరికరం చుట్టూ తిప్పబడుతోంది"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> బ్యాటరీ మిగిలి ఉంది"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"మీ స్టైలస్ను ఛార్జర్కి కనెక్ట్ చేయండి"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"తక్కువ స్టైలస్ బ్యాటరీ"</string> + <string name="video_camera" msgid="7654002575156149298">"వీడియో కెమెరా"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"ఈ ప్రొఫైల్ నుండి కాల్ చేయడం సాధ్యపడలేదు"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"మీ వర్క్ పాలసీ, మిమ్మల్ని వర్క్ ప్రొఫైల్ నుండి మాత్రమే ఫోన్ కాల్స్ చేయడానికి అనుమతిస్తుంది"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"వర్క్ ప్రొఫైల్కు మారండి"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"మూసివేయండి"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 771a906bc6ec..28bb38825374 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"อัตโนมัติ"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"ไม่มีเสียงหรือการสั่น"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ไม่มีเสียงหรือการสั่น และปรากฏต่ำลงมาในส่วนการสนทนา"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"อาจส่งเสียงหรือสั่นโดยขึ้นอยู่กับการตั้งค่าโทรศัพท์"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"อาจส่งเสียงหรือสั่นโดยขึ้นอยู่กับการตั้งค่าโทรศัพท์ การสนทนาจาก <xliff:g id="APP_NAME">%1$s</xliff:g> จะแสดงเป็นบับเบิลโดยค่าเริ่มต้น"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"อาจส่งเสียงหรือสั่นโดยขึ้นอยู่กับการตั้งค่าอุปกรณ์"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"อาจส่งเสียงหรือสั่นโดยขึ้นอยู่กับการตั้งค่าอุปกรณ์ การสนทนาจาก <xliff:g id="APP_NAME">%1$s</xliff:g> จะแสดงเป็นบับเบิลโดยค่าเริ่มต้น"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ให้ระบบพิจารณาว่าจะให้การแจ้งเตือนนี้ส่งเสียงหรือสั่นหรือไม่"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>สถานะ:</b> เลื่อนระดับเป็นค่าเริ่มต้น"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>สถานะ:</b> ลดระดับเป็นปิดเสียง"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"เลือกแอปเพื่อเพิ่มตัวควบคุม"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{เพิ่มตัวควบคุม # ตัวแล้ว}other{เพิ่มตัวควบคุม # ตัวแล้ว}}"</string> <string name="controls_removed" msgid="3731789252222856959">"นำออกแล้ว"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"เพิ่ม <xliff:g id="APPNAME">%s</xliff:g> ไหม"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"เมื่อเพิ่ม <xliff:g id="APPNAME">%s</xliff:g> คุณจะเพิ่มการควบคุมและเนื้อหาไปยังแผงนี้ได้ ในบางแอป คุณเลือกได้ว่าต้องการให้การควบคุมใดปรากฏขึ้นที่นี่"</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"ตั้งเป็นรายการโปรดแล้ว"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ตั้งเป็นรายการโปรดแล้ว โดยอยู่ลำดับที่ <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"นำออกจากรายการโปรดแล้ว"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> จาก <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"เลิกทำ"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"ขยับไปใกล้มากขึ้นเพื่อเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ขยับไปใกล้ <xliff:g id="DEVICENAME">%1$s</xliff:g> มากขึ้นเพื่อเล่นที่นี่"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"กำลังเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"เกิดข้อผิดพลาด โปรดลองอีกครั้ง"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"กำลังโหลด"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"พบข้อผิดพลาด โปรดลองอีกครั้ง"</string> <string name="controls_menu_add" msgid="4447246119229920050">"เพิ่มตัวควบคุม"</string> <string name="controls_menu_edit" msgid="890623986951347062">"แก้ไขตัวควบคุม"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"เพิ่มแอป"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"เพิ่มเอาต์พุต"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"กลุ่ม"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"เลือกอุปกรณ์ไว้ 1 รายการ"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ หน้าจอนี้จะปิดไป"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"อุปกรณ์ที่พับได้กำลังกางออก"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"อุปกรณ์ที่พับได้กำลังพลิกไปมา"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"เหลือแบตเตอรี่ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"เชื่อมต่อสไตลัสกับที่ชาร์จ"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"แบตเตอรี่สไตลัสเหลือน้อย"</string> + <string name="video_camera" msgid="7654002575156149298">"กล้องวิดีโอ"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"โทรจากโปรไฟล์นี้ไม่ได้"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"นโยบายการทำงานอนุญาตให้คุณโทรออกได้จากโปรไฟล์งานเท่านั้น"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"สลับไปใช้โปรไฟล์งาน"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"ปิด"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index aa58d00df76a..7387289f4ccf 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Awtomatiko"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Walang tunog o pag-vibrate"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Walang tunog o pag-vibrate at lumalabas nang mas mababa sa seksyon ng pag-uusap"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Puwedeng mag-ring o mag-vibrate batay sa mga setting ng telepono"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Puwedeng mag-ring o mag-vibrate batay sa mga setting ng telepono. Mga pag-uusap mula sa <xliff:g id="APP_NAME">%1$s</xliff:g> bubble bilang default."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Puwedeng mag-ring o mag-vibrate batay sa mga setting ng device"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Puwedeng mag-ring o mag-vibrate batay sa mga setting ng device. Mga pag-uusap mula sa <xliff:g id="APP_NAME">%1$s</xliff:g> bubble bilang default."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Ipatukoy sa system kung dapat gumawa ng tunog o pag-vibrate ang notification na ito"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Na-promote sa Default"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Na-demote sa Naka-silent"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Pumili ng app para magdagdag ng mga kontrol"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Nagdagdag ng # kontrol.}one{Nagdagdag ng # kontrol.}other{Nagdagdag ng # na kontrol.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Inalis"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Idagdag ang <xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Kapag idinagdag mo ang <xliff:g id="APPNAME">%s</xliff:g>, puwede itong magdagdag ng mga kontrol at content sa panel na ito. Sa ilang app, puwede mong piliin kung aling mga kontrol ang lalabas dito."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Ginawang paborito"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ginawang paborito, posisyon <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Inalis sa paborito"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"I-undo"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Lumapit pa para mag-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para mag-play dito, lumapit sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Nagpe-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Nagkaproblema. Subukan ulit."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Naglo-load"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Nagka-error, subukan ulit"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Magdagdag ng mga kontrol"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Mag-edit ng mga kontrol"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Magdagdag ng app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Magdagdag ng mga output"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device ang napili"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Mag-o-off ang screen na ito"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ina-unfold na foldable na device"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Fini-flip na foldable na device"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> baterya na lang ang natitira"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ikonekta sa charger ang iyong stylus"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Paubos na ang baterya ng stylus"</string> + <string name="video_camera" msgid="7654002575156149298">"Video camera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Hindi puwedeng tumawag mula sa profile na ito"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Pinapayagan ka ng iyong patakaran sa trabaho na tumawag lang mula sa profile sa trabaho"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Lumipat sa profile sa trabaho"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Isara"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 2ee29cf925a9..7c5552d3ab30 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alt sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sol sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sağ sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"İş profilindeki ekran görüntüleri <xliff:g id="APP">%1$s</xliff:g> uygulamasına kaydedilir"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Dosyalar"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Ekran Kaydedicisi"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekran kaydı işleniyor"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekran kaydı oturumu için devam eden bildirim"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Otomatik"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sessiz veya titreşim yok"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ses veya titreşim yok, görüşme bölümünün altında görünür"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Telefon ayarlarına bağlı olarak zili çalabilir veya titreyebilir"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Telefon ayarlarına bağlı olarak zili çalabilir veya titreyebilir <xliff:g id="APP_NAME">%1$s</xliff:g> adlı uygulamadan görüşmeler varsayılan olarak baloncukla gösterilir."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Cihaz ayarlarına bağlı olarak zili çalabilir veya titreyebilir"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Cihaz ayarlarına bağlı olarak zili çalabilir veya titreyebilir <xliff:g id="APP_NAME">%1$s</xliff:g> adlı uygulamadan görüşmeler varsayılan olarak baloncukla gösterilir."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Bu bildirimin ses çıkarması veya titreşmesi gerekip gerekmediğine sistem karar versin"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Durum:</b> Varsayılana yükseltildi"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Durum:</b> Sessize Düşürüldü"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Denetim eklemek için uygulama seçin"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrol eklendi.}other{# kontrol eklendi.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Kaldırıldı"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favoriler listesine eklendi"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favorilere eklendi, konum: <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Favorilerden kaldırıldı"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> uygulamasından <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Geri al"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oynatmak için yaklaşın"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Burada oynatmak için <xliff:g id="DEVICENAME">%1$s</xliff:g> cihazına yaklaşın"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oynatılıyor"</string> - <string name="media_transfer_failed" msgid="7955354964610603723">"Bir sorun oldu. Tekrar deneyin."</string> + <string name="media_transfer_failed" msgid="7955354964610603723">"Bir hata oluştu. Tekrar deneyin."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Yükleme"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Devre dışı, uygulamaya bakın"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Bulunamadı"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrol kullanılamıyor"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Hata, yeniden deneyin"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Denetim ekle"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Denetimleri düzenle"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Çıkışlar ekleyin"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçildi"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Ses düzeyi"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hoparlörler ve Ekranlar"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Önerilen Cihazlar"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Yayınlamanın işleyiş şekli"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Anons"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Yakınınızda ve uyumlu Bluetooth cihazları olan kişiler yayınladığınız medya içeriğini dinleyebilir"</string> @@ -1020,11 +1021,17 @@ <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Daha iyi selfie çekmek için telefonu açın"</string> <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Daha iyi bir selfie için ön ekrana geçilsin mi?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Daha yüksek çözünürlüğe sahip daha büyük bir fotoğraf için arka yüz kamerasını kullanın."</string> - <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ * Bu ekran kapatılacak"</b></string> + <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Bu ekran kapatılacak"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Katlanabilir cihaz açılıyor"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Katlanabilir cihaz döndürülüyor"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> pil kaldı"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ekran kaleminizi bir şarj cihazına bağlayın"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Ekran kaleminin pil seviyesi düşük"</string> + <string name="video_camera" msgid="7654002575156149298">"Video kamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Bu profilden telefon araması yapılamıyor"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"İşletme politikanız yalnızca iş profilinden telefon araması yapmanıza izin veriyor"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"İş profiline geç"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Kapat"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index ae22d087f07e..0a93ec6af07e 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Знизу на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Зліва на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Справа на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Робочі знімки екрана зберігаються в додатку <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файли"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Запис відео з екрана"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обробка записування екрана"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Сповіщення про сеанс запису екрана"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматично"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звуку чи вібрації"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звуку чи вібрації, з\'являється нижче в розділі розмов"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Дзвінок або вібрація залежно від налаштувань телефона"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Дзвінок або вібрація залежно від налаштувань телефона. Розмови з додатка <xliff:g id="APP_NAME">%1$s</xliff:g> за умовчанням з\'являються як спливаючий чат."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Дзвінок або вібрація залежно від налаштувань пристрою"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Може дзвонити або вібрувати залежно від налаштувань пристрою. Показує спливаючі розмови з додатка <xliff:g id="APP_NAME">%1$s</xliff:g> за умовчанням."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Дозволити системі визначати, чи має сповіщення супроводжуватися звуком або вібрацією"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Статус</b>: підвищено до \"За умовчанням\""</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Статус</b>: знижено до \"Без звуку\""</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Виберіть, для якого додатка налаштувати елементи керування"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Додано # елемент керування.}one{Додано # елемент керування.}few{Додано # елементи керування.}many{Додано # елементів керування.}other{Додано # елемента керування.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Вилучено"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Додано у вибране"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Додано у вибране, позиція <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Видалено з вибраного"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" у додатку <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Відмінити"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Щоб відтворити контент на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>, наблизьтеся до нього"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Щоб відтворити на цьому пристрої, перемістіть його ближче до пристрою \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Відтворюється на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Сталася помилка. Повторіть спробу."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Завантаження"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, перейдіть у додаток"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не знайдено"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Елемент керування недоступний"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Помилка. Спробуйте знову"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Додати елементи керування"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Змінити елементи керування"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Додати пристрої виводу"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Вибрано 1 пристрій"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Гучність"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Колонки й екрани"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Пропоновані пристрої"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Як працює трансляція"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Трансляція"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Люди поблизу, які мають сумісні пристрої з Bluetooth, можуть слухати медіаконтент, який ви транслюєте."</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Цей екран вимкнеться"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Розкладний пристрій у розкладеному стані"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Розкладний пристрій обертається"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Заряд акумулятора: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Підключіть стилус до зарядного пристрою"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Низький заряд акумулятора стилуса"</string> + <string name="video_camera" msgid="7654002575156149298">"Відеокамера"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Неможливо телефонувати з цього профілю"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Відповідно до правил організації ви можете телефонувати лише з робочого профілю"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Перейти в робочий профіль"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Закрити"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index f02797b6c4ae..ad845f0ed720 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"نیچے کا احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"بایاں احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"دایاں احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"دفتری اسکرین شاٹس کو <xliff:g id="APP">%1$s</xliff:g> ایپ میں محفوظ کیا جاتا ہے"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"فائلز"</string> <string name="screenrecord_name" msgid="2596401223859996572">"اسکرین ریکارڈر"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"سکرین ریکارڈنگ پروسیس ہورہی ہے"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"اسکرین ریکارڈ سیشن کیلئے جاری اطلاع"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"خودکار"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"کوئی آواز یا وائبریشن نہیں"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"کوئی آواز یا وائبریشن نہیں اور گفتگو کے سیکشن میں نیچے ظاہر ہوتا ہے"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"آپ کے آلہ کی ترتیبات کے مطابق وائبریٹ یا گھنٹی بج سکتی ہے"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"فون کی ترتیبات کے مطابق وائبریٹ یا گھنٹی بج سکتی ہے۔ بذریعہ ڈیفالٹ <xliff:g id="APP_NAME">%1$s</xliff:g> بلبلہ سے گفتگوئیں۔"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"آلے کی ترتیبات کی بنیاد پر وائبریٹ یا گھنٹی بج سکتی ہے"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"آلے کی ترتیبات بنیاد پر وائبریٹ یا گھنٹی بج سکتی ہے۔ بذریعہ ڈیفالٹ <xliff:g id="APP_NAME">%1$s</xliff:g> بلبلہ سے گفتگوئیں۔"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"سسٹم کو اس بات کا تعین کرنے دیں کہ آیا اس اطلاع کی آواز ہو یا وائبریٹ ہونا چاہیے"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> ڈیفالٹ پر درجہ بند کیا گیا"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>اسٹیٹس:</b> کو خاموش پر درجہ بند کیا گیا"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"کنٹرولز شامل کرنے کے لیے ایپ منتخب کریں"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# کنٹرول کو شامل کیا گیا۔}other{# کنٹرولز کو شامل کیا گیا۔}}"</string> <string name="controls_removed" msgid="3731789252222856959">"ہٹا دیا گیا"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> کو شامل کریں؟"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"جب آپ <xliff:g id="APPNAME">%s</xliff:g> کو شامل کرتے ہیں، تو یہ اس پینل میں کنٹرولز اور مواد کو شامل کر سکتا ہے۔ کچھ ایپس میں، آپ یہ منتخب کر سکتے ہیں کہ کون سے کنٹرولز یہاں ظاہر ہوں۔"</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"پسند کردہ"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"پسند کردہ، پوزیشن <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ناپسند کردہ"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> سے <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"کالعدم کریں"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چلانے کے لیے قریب کریں"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"یہاں چلانے کے لیے، <xliff:g id="DEVICENAME">%1$s</xliff:g> کے زیادہ جائیں"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چل رہا ہے"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"کچھ غلط ہوگیا۔ پھر کوشش کریں۔"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"لوڈ ہو رہا ہے"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ٹیبلیٹ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"غیر فعال، ایپ چیک کریں"</string> <string name="controls_error_removed" msgid="6675638069846014366">"نہیں ملا"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"کنٹرول دستیاب نہیں ہے"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"خرابی، دوبارہ کوشش کریں"</string> <string name="controls_menu_add" msgid="4447246119229920050">"کنٹرولز شامل کریں"</string> <string name="controls_menu_edit" msgid="890623986951347062">"کنٹرولز میں ترمیم کریں"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ایپ شامل کریں"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"آؤٹ پٹس شامل کریں"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"گروپ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 آلہ منتخب کیا گیا"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"والیوم"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"اسپیکرز اور ڈسپلیز"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"تجویز کردہ آلات"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"براڈکاسٹنگ کیسے کام کرتا ہے"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"براڈکاسٹ"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"موافق بلوٹوتھ آلات کے ساتھ آپ کے قریبی لوگ آپ کے نشر کردہ میڈیا کو سن سکتے ہیں"</string> @@ -1025,4 +1023,12 @@ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"فولڈ ہونے والے آلے کو گھمایا جا رہا ہے"</string> <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> بیٹری باقی ہے"</string> <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"اپنے اسٹائلس کو چارجر منسلک کریں"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"اسٹائلس بیٹری کم ہے"</string> + <string name="video_camera" msgid="7654002575156149298">"ویڈیو کیمرا"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"اس پروفائل سے کال نہیں کر سکتے"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"آپ کے کام سے متعلق پالیسی آپ کو صرف دفتری پروفائل سے فون کالز کرنے کی اجازت دیتی ہے"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"دفتری پروفائل پر سوئچ کریں"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"بند کریں"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 0d5ad6082831..4927ec239bfb 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Avtomatik"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Tovush yoki tebranishsiz"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Tovush yoki tebranishsiz hamda suhbatlar ruknining pastida chiqadi"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Telefon sozlamalari asosida jiringlashi yoki tebranishi mumkin"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Telefon sozlamalari asosida jiringlashi yoki tebranishi mumkin. <xliff:g id="APP_NAME">%1$s</xliff:g> suhbatlari standart holatda bulutcha shaklida chiqadi."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Qurilma sozlamalari asosida jiringlashi yoki tebranishi mumkin"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Qurilma sozlamalari asosida jiringlashi yoki tebranishi mumkin. <xliff:g id="APP_NAME">%1$s</xliff:g> suhbatlari standart holatda bulutcha shaklida chiqadi."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Bu bildirishnoma jiringlashi yoki tebranishini hal qilsin"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Holati:</b> Birlamchi darajaga chiqarildi"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Holati:</b> Sokin darajaga tushirildi"</string> @@ -800,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Boshqaruv elementlarini kiritish uchun ilovani tanlang"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ta boshqaruv elementi kiritildi.}other{# ta boshqaruv elementi kiritildi.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Olib tashlandi"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Saralanganlarga kiritilgan"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Saralanganlarga kiritilgan, <xliff:g id="NUMBER">%d</xliff:g>-joy"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Saralanganlardan olib tashlangan"</string> @@ -866,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Xato, qayta urining"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Element kiritish"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Elementlarni tahrirlash"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Chiquvchi qurilmani kiritish"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Guruh"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ta qurilma tanlandi"</string> @@ -1011,13 +1017,21 @@ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Kamida bitta qurilma mavjud"</string> <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Bosib turish yorligʻi"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Bekor qilish"</string> - <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Hozir aylantirish"</string> + <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Almashtirish"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Yaxshiroq selfi olish uchun telefonni yoying"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Old ekran sizga qaragan holda aylantirdingizmi?"</string> - <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Keng va yuqori tiniqlikdagi suratga olish uchun orqa kameradan foydalaning."</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Yaxshiroq selfi uchun old ekranga almashilsinmi?"</string> + <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Keng burchakli va yuqori aniqlikda suratga olish uchun orqa kameradan foydalaning."</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Bu ekran oʻchiriladi"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Buklanadigan qurilma ochilmoqda"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Buklanadigan qurilma aylantirilmoqda"</string> <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batareya quvvati: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Stilusni quvvat manbaiga ulang"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Stilus batareyasi kam"</string> + <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Bu profildan chaqiruv qilish imkonsiz"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Ishga oid siyosatingiz faqat ish profilidan telefon chaqiruvlarini amalga oshirish imkonini beradi"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Ish profiliga almashish"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Yopish"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index c2e27d45851e..016c83419de7 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Cạnh dưới cùng <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Cạnh trái <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Cạnh phải <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ảnh chụp màn hình công việc được lưu trong ứng dụng <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Trình ghi màn hình"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Đang xử lý video ghi màn hình"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Thông báo đang diễn ra về phiên ghi màn hình"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Tự động"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Không phát âm thanh hoặc rung"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Không phát âm thanh hoặc rung và xuất hiện phía dưới trong phần cuộc trò chuyện"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Có thể đổ chuông hoặc rung tùy theo chế độ cài đặt trên điện thoại"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Có thể đổ chuông hoặc rung tùy theo chế độ cài đặt trên điện thoại. Các cuộc trò chuyện từ <xliff:g id="APP_NAME">%1$s</xliff:g> sẽ hiện ở dạng bong bóng theo mặc định."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Có thể đổ chuông hoặc rung tuỳ theo chế độ cài đặt trên thiết bị"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Có thể đổ chuông hoặc rung tuỳ theo chế độ cài đặt trên thiết bị. Theo mặc định, các cuộc trò chuyện từ <xliff:g id="APP_NAME">%1$s</xliff:g> sẽ hiển thị dưới dạng bong bóng."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Cho phép hệ thống quyết định xem thông báo này phát âm thanh hay rung"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Trạng thái:</b> Đã thay đổi thành Mặc định"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Trạng thái:</b> Đã thay đổi thành Im lặng"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Chọn ứng dụng để thêm các tùy chọn điều khiển"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Đã thêm # chế độ điều khiển.}other{Đã thêm # chế độ điều khiển.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Đã xóa"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Được yêu thích"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Được yêu thích, vị trí số <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Không được yêu thích"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> trên <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Hủy"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Đưa thiết bị đến gần hơn để phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Để phát ở đây, vui lòng lại gần <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Đang phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Đã xảy ra lỗi. Hãy thử lại."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Đang tải"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"máy tính bảng"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Không hoạt động, hãy kiểm tra ứng dụng"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Không tìm thấy"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Không có chức năng điều khiển"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"Lỗi, hãy thử lại"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Thêm các tùy chọn điều khiển"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Chỉnh sửa tùy chọn điều khiển"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Thêm thiết bị đầu ra"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Nhóm"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Đã chọn 1 thiết bị"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Âm lượng"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Loa và màn hình"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Thiết bị được đề xuất"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cách tính năng truyền hoạt động"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Truyền"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Những người ở gần có thiết bị Bluetooth tương thích có thể nghe nội dung nghe nhìn bạn đang truyền"</string> @@ -1023,8 +1024,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Màn hình này sẽ tắt"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Thiết bị có thể gập lại đang được mở ra"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Thiết bị có thể gập lại đang được lật ngược"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Còn <xliff:g id="PERCENTAGE">%s</xliff:g> pin"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Hãy kết nối bút cảm ứng với bộ sạc"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Bút cảm ứng bị yếu pin"</string> + <string name="video_camera" msgid="7654002575156149298">"Máy quay video"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Không thể gọi điện từ hồ sơ này"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Chính sách của nơi làm việc chỉ cho phép bạn gọi điện thoại từ hồ sơ công việc"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Chuyển sang hồ sơ công việc"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Đóng"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index b401a8c9c5bc..15c834cfb137 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"底部边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左侧边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右侧边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"工作屏幕截图保存在“<xliff:g id="APP">%1$s</xliff:g>”应用中"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"文件"</string> <string name="screenrecord_name" msgid="2596401223859996572">"屏幕录制器"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"正在处理屏幕录制视频"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"持续显示屏幕录制会话通知"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"自动"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"不发出提示音,也不振动"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"不发出提示音,也不振动;显示在对话部分的靠下位置"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"可能会响铃或振动(取决于手机设置)"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"可能会响铃或振动(取决于手机设置)。默认情况下,来自<xliff:g id="APP_NAME">%1$s</xliff:g>的对话会以对话泡的形式显示。"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"可能会响铃或振动,取决于设备设置"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"可能会响铃或振动,取决于设备设置。默认情况下,来自<xliff:g id="APP_NAME">%1$s</xliff:g>的对话会以对话泡的形式显示。"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"让系统决定是否应让设备在收到此通知时发出提示音或振动"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>状态</b>:已提升为“默认”"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>状态</b>:已降低为“静音”"</string> @@ -802,6 +800,10 @@ <string name="controls_providers_title" msgid="6879775889857085056">"选择要添加控制器的应用"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{已添加 # 个控件。}other{已添加 # 个控件。}}"</string> <string name="controls_removed" msgid="3731789252222856959">"已移除"</string> + <!-- no translation found for controls_panel_authorization_title (267429338785864842) --> + <skip /> + <!-- no translation found for controls_panel_authorization (4540047176861801815) --> + <skip /> <string name="accessibility_control_favorite" msgid="8694362691985545985">"已收藏"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"已收藏,位置:<xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"已取消收藏"</string> @@ -854,13 +856,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"通过<xliff:g id="APP_LABEL">%2$s</xliff:g>播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"撤消"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"若要在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放,请靠近这台设备"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"若要在此设备上播放,请再靠近<xliff:g id="DEVICENAME">%1$s</xliff:g>一点"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"正在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"出了点问题,请重试。"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"正在加载"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"平板电脑"</string> <string name="controls_error_timeout" msgid="794197289772728958">"无效,请检查应用"</string> <string name="controls_error_removed" msgid="6675638069846014366">"未找到"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"控件不可用"</string> @@ -870,6 +870,8 @@ <string name="controls_error_failed" msgid="960228639198558525">"出现错误,请重试"</string> <string name="controls_menu_add" msgid="4447246119229920050">"添加控制器"</string> <string name="controls_menu_edit" msgid="890623986951347062">"修改控制器"</string> + <!-- no translation found for controls_menu_add_another_app (8661172304650786705) --> + <skip /> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"添加输出设备"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"群组"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已选择 1 个设备"</string> @@ -884,8 +886,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"音量"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"音箱和显示屏"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"建议的设备"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"广播的运作方式"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"广播"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"附近使用兼容蓝牙设备的用户可以收听您广播的媒体内容"</string> @@ -1018,13 +1019,19 @@ <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"取消"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"立即翻转"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"展开手机可拍出更好的自拍照"</string> - <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"要翻转到外屏以拍出更好的自拍照吗?"</string> + <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"翻转到外屏后自拍效果更好,要试试吗?"</string> <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"您可以使用后置摄像头拍摄视角更广、分辨率更高的照片。"</string> <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ 此屏幕将会关闭"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展开可折叠设备"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻转可折叠设备"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"电池还剩 <xliff:g id="PERCENTAGE">%s</xliff:g> 的电量"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"请将触控笔连接充电器"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"触控笔电池电量低"</string> + <string name="video_camera" msgid="7654002575156149298">"摄像机"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"无法通过这份资料拨打电话"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"根据您的工作政策,您只能通过工作资料拨打电话"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"切换到工作资料"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"关闭"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 0d54f6b2294b..8aa29763046d 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"自動"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"無音效或震動"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"無音效或震動,並在對話部分的較低位置顯示"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"根據手機設定發出鈴聲或震動"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"可能會根據手機設定發出鈴聲或震動。「<xliff:g id="APP_NAME">%1$s</xliff:g>」的對話會預設以對話氣泡顯示。"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"可能會根據裝置設定發出鈴聲或震動"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"可能會根據裝置設定發出鈴聲或震動。根據預設,來自 <xliff:g id="APP_NAME">%1$s</xliff:g> 的對話會以對話氣泡顯示。"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"由系統判斷是否要讓此通知發出音效或震動"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>狀態:</b>已提升為預設"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>狀態:</b>已降低為靜音"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"選擇要新增控制項的應用程式"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{已新增 # 個控制項。}other{已新增 # 個控制項。}}"</string> <string name="controls_removed" msgid="3731789252222856959">"已移除"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"要新增「<xliff:g id="APPNAME">%s</xliff:g>」嗎?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"當你新增「<xliff:g id="APPNAME">%s</xliff:g>」時,應用程式也可將控制選項及內容新增到這個面板。某些應用程式可讓你選擇要顯示在這裡的控制選項。"</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"已加入收藏"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"已加入至收藏位置 <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"已取消收藏"</string> @@ -852,11 +854,10 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"在 <xliff:g id="APP_LABEL">%2$s</xliff:g> 播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"如要在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放,請靠近一點"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"如要在這部裝置播放,請靠近「<xliff:g id="DEVICENAME">%1$s</xliff:g>」一點"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"正在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"發生錯誤,請再試一次。"</string> - <string name="media_transfer_loading" msgid="5544017127027152422">"載入中"</string> + <string name="media_transfer_loading" msgid="5544017127027152422">"正在載入"</string> <string name="media_ttt_default_device_type" msgid="4457646436153370169">"平板電腦"</string> <string name="controls_error_timeout" msgid="794197289772728958">"已停用,請檢查應用程式"</string> <string name="controls_error_removed" msgid="6675638069846014366">"找不到"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"發生錯誤,請重試"</string> <string name="controls_menu_add" msgid="4447246119229920050">"新增控制項"</string> <string name="controls_menu_edit" msgid="890623986951347062">"編輯控制項"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"新增應用程式"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"新增輸出裝置"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"群組"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已選取 1 部裝置"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ 此螢幕將關閉"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開折疊式裝置"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"剩餘電量:<xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"將觸控筆連接充電器"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"觸控筆電量不足"</string> + <string name="video_camera" msgid="7654002575156149298">"攝影機"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"無法透過此設定檔撥打電話"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"您的公司政策只允許透過工作設定檔撥打電話"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"切換至工作設定檔"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"關閉"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 6a78a93dc069..321821c7fd41 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -532,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"自動"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"不震動或發出聲音"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"不震動或發出聲音,並顯示在對話區的下方"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"根據手機的設定響鈴或震動"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"可能會根據手機的設定響鈴或震動。根據預設,來自「<xliff:g id="APP_NAME">%1$s</xliff:g>」的對話會以對話框形式顯示。"</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"根據裝置的設定響鈴或震動"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"根據裝置的設定響鈴或震動。根據預設,來自「<xliff:g id="APP_NAME">%1$s</xliff:g>」的對話會以對話框形式顯示。"</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"由系統判斷要讓裝置在收到這則通知時震動還是發出音效"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>狀態:</b>已提升為預設"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>狀態:</b>已降低為靜音"</string> @@ -800,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"選擇應用程式以新增控制項"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{已新增 # 個控制項。}other{已新增 # 個控制項。}}"</string> <string name="controls_removed" msgid="3731789252222856959">"已移除"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"要新增「<xliff:g id="APPNAME">%s</xliff:g>」嗎?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"當你新增「<xliff:g id="APPNAME">%s</xliff:g>」時,應用程式也可將控制選項及內容新增到這個面板。某些應用程式可讓你選擇要顯示在這裡的控制選項。"</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"已加入收藏"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"已加入收藏,位置 <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"從收藏中移除"</string> @@ -852,8 +854,7 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"透過「<xliff:g id="APP_LABEL">%2$s</xliff:g>」播放〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"如要在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放,請靠近一點"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"如要在這部裝置播放,請移到更靠近「<xliff:g id="DEVICENAME">%1$s</xliff:g>」的位置"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"正在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"發生錯誤,請再試一次。"</string> <string name="media_transfer_loading" msgid="5544017127027152422">"載入中"</string> @@ -867,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"發生錯誤,請再試一次"</string> <string name="controls_menu_add" msgid="4447246119229920050">"新增控制項"</string> <string name="controls_menu_edit" msgid="890623986951347062">"編輯控制項"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"新增應用程式"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"新增輸出裝置"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"群組"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已選取 1 部裝置"</string> @@ -1019,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ 這麼做會關閉這個螢幕"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開的折疊式裝置"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"剩餘電量:<xliff:g id="PERCENTAGE">%s</xliff:g>"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"將觸控筆接上充電器"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"觸控筆電力不足"</string> + <string name="video_camera" msgid="7654002575156149298">"攝影機"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"無法透過這個資料夾撥打電話"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"貴公司政策僅允許透過工作資料夾撥打電話"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"切換至工作資料夾"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"關閉"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 311fe8b99598..c094acf0dd1f 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -91,10 +91,8 @@ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ophansi"</string> <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ongakwesobunxele"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ongakwesokudla"</string> - <!-- no translation found for screenshot_work_profile_notification (2812417845875653929) --> - <skip /> - <!-- no translation found for screenshot_default_files_app_name (8721579578575161912) --> - <skip /> + <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Izithombe-skrini zomsebenzi zigcinwa ku-app ye-<xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Amafayela"</string> <string name="screenrecord_name" msgid="2596401223859996572">"Irekhoda yesikrini"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Icubungula okokuqopha iskrini"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Isaziso esiqhubekayo seseshini yokurekhoda isikrini"</string> @@ -534,8 +532,8 @@ <string name="notification_automatic_title" msgid="3745465364578762652">"Okuzenzekelayo"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Awukho umsindo noma ukudlidliza"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Awukho umsindo noma ukudlidliza futhi ivela ngezansi esigabeni sengxoxo"</string> - <string name="notification_channel_summary_default" msgid="3282930979307248890">"Ingase ikhale noma idlidlize kuya ngamasethingi wefoni yakho"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Ingase ikhale noma idlidlize kuya ngamasethingi wefoni yakho. Izingxoxo ezivela ku-<xliff:g id="APP_NAME">%1$s</xliff:g> ziba yibhamuza ngokuzenzakalela."</string> + <string name="notification_channel_summary_default" msgid="777294388712200605">"Ingase ikhale noma idlidlize ngokusekelwe kumasethingi edivayisi"</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Ingase ikhale noma idlidlize kuya ngamasethingi wedivayisi. Izingxoxo ezivela ku-<xliff:g id="APP_NAME">%1$s</xliff:g> ziba yibhamuza ngokuzenzakalela."</string> <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Vumela isistimu inqume uma lesi saziso kufanele senze umsindo noma sidlidlize"</string> <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Isimo:</b> Siphromothelwe Kokuzenzakalelayo"</string> <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Isimo:</b> Sehliselwe Kokuthulile"</string> @@ -802,6 +800,8 @@ <string name="controls_providers_title" msgid="6879775889857085056">"Khetha uhlelo lokusebenza ukwengeza izilawuli"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{ulawulo olu-# olwengeziwe.}one{ukulawulwa okungu-# okwengeziwe.}other{ukulawulwa okungu-# okwengeziwe.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Isusiwe"</string> + <string name="controls_panel_authorization_title" msgid="267429338785864842">"Engeza i-<xliff:g id="APPNAME">%s</xliff:g>?"</string> + <string name="controls_panel_authorization" msgid="4540047176861801815">"Uma wengeza i-<xliff:g id="APPNAME">%s</xliff:g>, ingangeza izilawuli nokuqukethwe kuleli phaneli. Kwamanye ama-app, ungakhetha ukuthi yiziphi izilawuli eziboniswa lapha."</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Kwenziwe intandokazi"</string> <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Kwenziwe intandokazi, isimo esiyi-<xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Akwenziwanga intandokazi"</string> @@ -854,13 +854,11 @@ <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Hlehlisa"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Sondeza eduze ukudlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <!-- no translation found for media_move_closer_to_end_cast (7302555909119374738) --> - <skip /> + <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ukuze udlale lapha, sondela ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Idlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Kukhona okungahambanga kahle. Zama futhi."</string> <string name="media_transfer_loading" msgid="5544017127027152422">"Iyalayisha"</string> - <!-- no translation found for media_ttt_default_device_type (4457646436153370169) --> - <skip /> + <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ithebulethi"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Akusebenzi, hlola uhlelo lokusebenza"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ayitholakali"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Ukulawula akutholakali"</string> @@ -870,6 +868,7 @@ <string name="controls_error_failed" msgid="960228639198558525">"Iphutha, zama futhi"</string> <string name="controls_menu_add" msgid="4447246119229920050">"Engeza Izilawuli"</string> <string name="controls_menu_edit" msgid="890623986951347062">"Hlela izilawuli"</string> + <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Engeza i-app"</string> <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Engeza okukhiphayo"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Iqembu"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"idivayisi ekhethiwe e-1"</string> @@ -884,8 +883,7 @@ <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Ivolumu"</string> <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string> <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Izipikha Neziboniso"</string> - <!-- no translation found for media_output_group_title_suggested_device (4157186235837903826) --> - <skip /> + <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Amadivayisi Aphakanyisiwe"</string> <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Indlela ukusakaza okusebenza ngayo"</string> <string name="media_output_broadcast" msgid="3555580945878071543">"Sakaza"</string> <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Abantu abaseduze nawe abanamadivayisi e-Bluetooth ahambisanayo bangalalela imidiya oyisakazayo"</string> @@ -1023,8 +1021,14 @@ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Lesi sikrini sizovala"</b></string> <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Idivayisi egoqekayo iyembulwa"</string> <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Idivayisi egoqekayo iphendulwa nxazonke"</string> - <!-- no translation found for stylus_battery_low_percentage (1620068112350141558) --> - <skip /> - <!-- no translation found for stylus_battery_low_subtitle (3583843128908823273) --> + <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ibhethri elisele"</string> + <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Xhuma i-stylus yakho kushaja"</string> + <string name="stylus_battery_low" msgid="7134370101603167096">"Ibhethri le-stylus liphansi"</string> + <string name="video_camera" msgid="7654002575156149298">"Ikhamera yevidiyo"</string> + <string name="call_from_work_profile_title" msgid="6991157106804289643">"Ayikwazi ukufonela le phrofayela"</string> + <string name="call_from_work_profile_text" msgid="3458704745640229638">"Inqubomgomo yakho yomsebenzi ikuvumela ukuthi wenze amakholi wefoni kuphela ngephrofayela yomsebenzi"</string> + <string name="call_from_work_profile_action" msgid="2937701298133010724">"Shintshela kuphrofayela yomsebenzi"</string> + <string name="call_from_work_profile_close" msgid="7927067108901068098">"Vala"</string> + <!-- no translation found for lock_screen_settings (9197175446592718435) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index e8a5e7ed8546..371f00189ad6 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -824,4 +824,12 @@ <item>bottom_end:wallet</item> </string-array> + <!-- Package name for the app that implements the wallpaper picker. --> + <string name="config_wallpaperPickerPackage" translatable="false"> + com.android.wallpaper + </string> + + <!-- Whether the floating rotation button should be on the left/right in the device's natural + orientation --> + <bool name="floating_rotation_button_position_left">true</bool> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 1ef020643a90..2b0021b26f19 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -337,7 +337,7 @@ <!-- Used for both start and bottom margin of the preview, relative to the action container --> <dimen name="overlay_preview_container_margin">8dp</dimen> <dimen name="overlay_action_container_margin_horizontal">8dp</dimen> - <dimen name="overlay_action_container_margin_bottom">4dp</dimen> + <dimen name="overlay_action_container_margin_bottom">6dp</dimen> <dimen name="overlay_bg_protection_height">242dp</dimen> <dimen name="overlay_action_container_corner_radius">18dp</dimen> <dimen name="overlay_action_container_padding_vertical">4dp</dimen> @@ -1068,8 +1068,13 @@ <!-- Size of Smartspace media recommendations cards in the QSPanel carousel --> <dimen name="qs_media_rec_icon_top_margin">16dp</dimen> <dimen name="qs_media_rec_album_size">88dp</dimen> + <dimen name="qs_media_rec_album_width">110dp</dimen> + <dimen name="qs_media_rec_album_height_expanded">108dp</dimen> + <dimen name="qs_media_rec_album_height_collapsed">77dp</dimen> <dimen name="qs_media_rec_album_side_margin">16dp</dimen> <dimen name="qs_media_rec_album_bottom_margin">8dp</dimen> + <dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen> + <dimen name="qs_media_rec_album_subtitle_height">12dp</dimen> <!-- Media tap-to-transfer chip for sender device --> <dimen name="media_ttt_chip_outer_padding">16dp</dimen> @@ -1648,4 +1653,10 @@ <dimen name="rear_display_animation_height">200dp</dimen> <dimen name="rear_display_title_top_padding">24dp</dimen> <dimen name="rear_display_title_bottom_padding">16dp</dimen> + + <!-- + Vertical distance between the pointer and the popup menu that shows up on the lock screen when + it is long-pressed. + --> + <dimen name="keyguard_long_press_settings_popup_vertical_offset">96dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index c5ffc94d01af..6354752e1b22 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -33,8 +33,6 @@ <!-- Whether to show chipbar UI whenever the device is unlocked by ActiveUnlock. --> <bool name="flag_active_unlock_chipbar">true</bool> - <bool name="flag_smartspace">false</bool> - <!-- Whether the user switcher chip shows in the status bar. When true, the multi user avatar will no longer show on the lockscreen --> <bool name="flag_user_switcher_chip">false</bool> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 066b185c6d1f..3d2b2e6a8558 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -243,6 +243,10 @@ <string name="screenshot_work_profile_notification">Work screenshots are saved in the <xliff:g id="app" example="Work Files">%1$s</xliff:g> app</string> <!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] --> <string name="screenshot_default_files_app_name">Files</string> + <!-- A notice shown to the user to indicate that an app has detected the screenshot that the user has just taken. [CHAR LIMIT=75] --> + <string name="screenshot_detected_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> detected this screenshot.</string> + <!-- A notice shown to the user to indicate that multiple apps have detected the screenshot that the user has just taken. [CHAR LIMIT=75] --> + <string name="screenshot_detected_multiple_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> and other open apps detected this screenshot.</string> <!-- Notification title displayed for screen recording [CHAR LIMIT=50]--> <string name="screenrecord_name">Screen Recorder</string> @@ -2238,6 +2242,14 @@ <!-- Removed control in management screen [CHAR LIMIT=20] --> <string name="controls_removed">Removed</string> + <!-- Title for the dialog presented to the user to authorize this app to display a Device + controls panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=30] --> + <string name="controls_panel_authorization_title">Add <xliff:g id="appName" example="My app">%s</xliff:g>?</string> + + <!-- Shows in a dialog presented to the user to authorize this app to display a Device controls + panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] --> + <string name="controls_panel_authorization">When you add <xliff:g id="appName" example="My app">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here.</string> + <!-- a11y state description for a control that is currently favorited [CHAR LIMIT=NONE] --> <string name="accessibility_control_favorite">Favorited</string> <!-- a11y state description for a control that is currently favorited with its position [CHAR LIMIT=NONE] --> @@ -2352,6 +2364,8 @@ <string name="controls_media_smartspace_rec_item_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string> <!-- Description for Smartspace recommendation's media item which doesn't have artist info, including information for the media's title and the source app [CHAR LIMIT=NONE]--> <string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string> + <!-- Header title for Smartspace recommendation card within media controls. [CHAR_LIMIT=30] --> + <string name="controls_media_smartspace_rec_header">For You</string> <!--- ****** Media tap-to-transfer ****** --> <!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] --> @@ -2387,6 +2401,8 @@ <string name="controls_menu_add">Add controls</string> <!-- Controls menu, edit [CHAR_LIMIT=30] --> <string name="controls_menu_edit">Edit controls</string> + <!-- Controls menu, add another app [CHAR LIMIT=30] --> + <string name="controls_menu_add_another_app">Add app</string> <!-- Title for the media output dialog with media related devices [CHAR LIMIT=50] --> <string name="media_output_dialog_add_output">Add outputs</string> @@ -2418,6 +2434,8 @@ <string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string> <!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] --> <string name="media_output_group_title_suggested_device">Suggested Devices</string> + <!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] --> + <string name="media_output_status_require_premium">Requires premium account</string> <!-- Media Output Broadcast Dialog --> <!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] --> @@ -2798,4 +2816,13 @@ <!-- Label for the close button on switch to work profile dialog. Switch to work profile dialog guide users to make call from work profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] --> <string name="call_from_work_profile_close">Close</string> + + <!-- + Label for a menu item in a menu that is shown when the user wishes to configure the lock screen. + Clicking on this menu item takes the user to a screen where they can modify the settings of the + lock screen. + + [CHAR LIMIT=32] + --> + <string name="lock_screen_settings">Lock screen settings</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 9846fc251a27..58b0234023ae 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -678,6 +678,17 @@ <style name="MediaPlayer.Recommendation"/> + <style name="MediaPlayer.Recommendation.Header"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_marginTop">@dimen/qs_media_padding</item> + <item name="android:layout_marginStart">@dimen/qs_media_padding</item> + <item name="android:fontFamily">=@*android:string/config_headlineFontFamilyMedium</item> + <item name="android:singleLine">true</item> + <item name="android:textSize">14sp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + <style name="MediaPlayer.Recommendation.AlbumContainer"> <item name="android:layout_width">@dimen/qs_media_rec_album_size</item> <item name="android:layout_height">@dimen/qs_media_rec_album_size</item> @@ -686,6 +697,12 @@ <item name="android:layout_marginBottom">@dimen/qs_media_rec_album_bottom_margin</item> </style> + <style name="MediaPlayer.Recommendation.AlbumContainer.Updated"> + <item name="android:layout_width">@dimen/qs_media_rec_album_width</item> + <item name="android:background">@drawable/qs_media_light_source</item> + <item name="android:layout_marginTop">@dimen/qs_media_info_spacing</item> + </style> + <style name="MediaPlayer.Recommendation.Album"> <item name="android:backgroundTint">@color/media_player_album_bg</item> </style> diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml new file mode 100644 index 000000000000..d3be3c7de5ad --- /dev/null +++ b/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<ConstraintSet + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + > + + <Constraint + android:id="@+id/sizing_view" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_session_height_collapsed" + /> + + <Constraint + android:id="@+id/media_rec_title" + style="@style/MediaPlayer.Recommendation.Header" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + + <Constraint + android:id="@+id/media_cover1_container" + style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" + android:layout_height="@dimen/qs_media_rec_album_height_collapsed" + android:layout_marginEnd="@dimen/qs_media_info_spacing" + android:layout_marginStart="@dimen/qs_media_padding" + app:layout_constraintTop_toBottomOf="@+id/media_rec_title" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/> + + + <Constraint + android:id="@+id/media_cover2_container" + style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" + android:layout_height="@dimen/qs_media_rec_album_height_collapsed" + android:layout_marginEnd="@dimen/qs_media_info_spacing" + app:layout_constraintTop_toBottomOf="@+id/media_rec_title" + app:layout_constraintStart_toEndOf="@id/media_cover1_container" + app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/> + + <Constraint + android:id="@+id/media_cover3_container" + style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" + android:layout_height="@dimen/qs_media_rec_album_height_collapsed" + android:layout_marginEnd="@dimen/qs_media_padding" + app:layout_constraintTop_toBottomOf="@+id/media_rec_title" + app:layout_constraintStart_toEndOf="@id/media_cover2_container" + app:layout_constraintEnd_toEndOf="parent"/> + + +</ConstraintSet> diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml new file mode 100644 index 000000000000..88c70552e9e8 --- /dev/null +++ b/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<ConstraintSet + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + > + + <Constraint + android:id="@+id/sizing_view" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_session_height_expanded" + /> + + <Constraint + android:id="@+id/media_rec_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/qs_media_padding" + android:layout_marginStart="@dimen/qs_media_padding" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:singleLine="true" + android:textSize="14sp" + android:textColor="@color/notification_primary_text_color"/> + + <Constraint + android:id="@+id/media_cover1_container" + style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" + android:layout_height="@dimen/qs_media_rec_album_height_expanded" + android:layout_marginEnd="@dimen/qs_media_info_spacing" + android:layout_marginStart="@dimen/qs_media_padding" + app:layout_constraintTop_toBottomOf="@+id/media_rec_title" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/> + + + <Constraint + android:id="@+id/media_cover2_container" + style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" + android:layout_height="@dimen/qs_media_rec_album_height_expanded" + android:layout_marginEnd="@dimen/qs_media_info_spacing" + app:layout_constraintTop_toBottomOf="@+id/media_rec_title" + app:layout_constraintStart_toEndOf="@id/media_cover1_container" + app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/> + + <Constraint + android:id="@+id/media_cover3_container" + style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" + android:layout_height="@dimen/qs_media_rec_album_height_expanded" + android:layout_marginEnd="@dimen/qs_media_padding" + app:layout_constraintTop_toBottomOf="@+id/media_rec_title" + app:layout_constraintStart_toEndOf="@id/media_cover2_container" + app:layout_constraintEnd_toEndOf="parent"/> + + +</ConstraintSet> diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml index d97031f35d6b..52a98984e6e2 100644 --- a/packages/SystemUI/res/xml/qs_header.xml +++ b/packages/SystemUI/res/xml/qs_header.xml @@ -41,9 +41,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/privacy_container" app:layout_constraintBottom_toBottomOf="@id/carrier_group" - app:layout_constraintEnd_toStartOf="@id/carrier_group" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintHorizontal_chainStyle="spread_inside" /> <Transform android:scaleX="2.57" @@ -62,18 +59,18 @@ /> </Constraint> + <!-- LargeScreenShadeHeaderController helps with managing clock width to layout this view --> <Constraint android:id="@+id/carrier_group"> <Layout - app:layout_constraintWidth_min="48dp" - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="@dimen/large_screen_shade_header_min_height" - app:layout_constraintStart_toEndOf="@id/clock" + app:layout_constraintWidth_min="48dp" + app:layout_constraintWidth_default="wrap" + app:layout_constraintStart_toStartOf="@id/clock" app:layout_constraintTop_toBottomOf="@id/privacy_container" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="1" app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon" - app:layout_constraintHorizontal_chainStyle="spread_inside" /> <PropertySet android:alpha="1" diff --git a/packages/SystemUI/scripts/token_alignment/.eslintrc.json b/packages/SystemUI/scripts/token_alignment/.eslintrc.json new file mode 100644 index 000000000000..69dc00e67dd8 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/.eslintrc.json @@ -0,0 +1,31 @@ +{ + "env": { + "es2021": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["prettier", "@typescript-eslint", "eslint-plugin-simple-import-sort", "import"], + "extends": ["prettier", "eslint:recommended", "plugin:@typescript-eslint/recommended"], + "rules": { + "prettier/prettier": ["error"], + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + "no-multiple-empty-lines": ["error", { "max": 2 }], + "no-multi-spaces": "error", + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + "import/first": "error", + "import/newline-after-import": "error", + "import/no-duplicates": "error" + } +} diff --git a/packages/SystemUI/scripts/token_alignment/.gitignore b/packages/SystemUI/scripts/token_alignment/.gitignore new file mode 100644 index 000000000000..96ce14f0120b --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/.gitignore @@ -0,0 +1,2 @@ +vscode +node_modules
\ No newline at end of file diff --git a/packages/SystemUI/scripts/token_alignment/.prettierrc b/packages/SystemUI/scripts/token_alignment/.prettierrc new file mode 100644 index 000000000000..20f02f962cdd --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/.prettierrc @@ -0,0 +1,9 @@ +{ + "tabWidth": 4, + "printWidth": 100, + "semi": true, + "singleQuote": true, + "bracketSameLine": true, + "bracketSpacing": true, + "arrowParens": "always" +}
\ No newline at end of file diff --git a/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts b/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts new file mode 100644 index 000000000000..80e075c9e070 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts @@ -0,0 +1,297 @@ +// Copyright 2022 Google LLC + +// 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. + +type IElementComment = + | { commentNode: undefined; textContent: undefined; hidden: undefined } + | { commentNode: Node; textContent: string; hidden: boolean }; + +interface ITag { + attrs?: Record<string, string | number>; + tagName: string; +} + +export interface INewTag extends ITag { + content?: string | number; + comment?: string; +} + +export type IUpdateTag = Partial<Omit<INewTag, 'tagName'>>; + +export default class DOM { + static addEntry(containerElement: Element, tagOptions: INewTag) { + const doc = containerElement.ownerDocument; + const exists = this.alreadyHasEntry(containerElement, tagOptions); + + if (exists) { + console.log('Ignored adding entry already available: ', exists.outerHTML); + return; + } + + let insertPoint: Node | null = containerElement.lastElementChild; //.childNodes[containerElement.childNodes.length - 1]; + + if (!insertPoint) { + console.log('Ignored adding entry in empity parent: ', containerElement.outerHTML); + return; + } + + const { attrs, comment, content, tagName } = tagOptions; + + if (comment) { + const commentNode = doc.createComment(comment); + this.insertAfterIdented(commentNode, insertPoint); + insertPoint = commentNode; + } + + const newEl = doc.createElement(tagName); + if (content) newEl.innerHTML = content.toString(); + if (attrs) + Object.entries(attrs).forEach(([attr, value]) => + newEl.setAttribute(attr, value.toString()) + ); + this.insertAfterIdented(newEl, insertPoint); + + return true; + } + + static insertBeforeIndented(newNode: Node, referenceNode: Node) { + const paddingNode = referenceNode.previousSibling; + const ownerDoc = referenceNode.ownerDocument; + const containerNode = referenceNode.parentNode; + + if (!paddingNode || !ownerDoc || !containerNode) return; + + const currentPadding = paddingNode.textContent || ''; + const textNode = referenceNode.ownerDocument.createTextNode(currentPadding); + + containerNode.insertBefore(newNode, referenceNode); + containerNode.insertBefore(textNode, newNode); + } + + static insertAfterIdented(newNode: Node, referenceNode: Node) { + const paddingNode = referenceNode.previousSibling; + const ownerDoc = referenceNode.ownerDocument; + const containerNode = referenceNode.parentNode; + + if (!paddingNode || !ownerDoc || !containerNode) return; + + const currentPadding = paddingNode.textContent || ''; + const textNode = ownerDoc.createTextNode(currentPadding); + + containerNode.insertBefore(newNode, referenceNode.nextSibling); + containerNode.insertBefore(textNode, newNode); + } + + static getElementComment(el: Element): IElementComment { + const commentNode = el.previousSibling?.previousSibling; + + const out = { commentNode: undefined, textContent: undefined, hidden: undefined }; + + if (!commentNode) return out; + + const textContent = commentNode.textContent || ''; + const hidden = textContent.substring(textContent.length - 6) == '@hide '; + + if (!(commentNode && commentNode.nodeName == '#comment')) return out; + + return { commentNode, textContent, hidden: hidden }; + } + + static duplicateEntryWithChange( + templateElement: Element, + options: Omit<IUpdateTag, 'content'> + ) { + const exists = this.futureEntryAlreadyExist(templateElement, options); + if (exists) { + console.log('Ignored duplicating entry already available: ', exists.outerHTML); + return; + } + + const { commentNode } = this.getElementComment(templateElement); + let insertPoint: Node = templateElement; + + if (commentNode) { + const newComment = commentNode.cloneNode(); + this.insertAfterIdented(newComment, insertPoint); + insertPoint = newComment; + } + + const newEl = templateElement.cloneNode(true) as Element; + this.insertAfterIdented(newEl, insertPoint); + + this.updateElement(newEl, options); + return true; + } + + static replaceStringInAttributeValueOnQueried( + root: Element, + query: string, + attrArray: string[], + replaceMap: Map<string, string> + ): boolean { + let updated = false; + const queried = [...Array.from(root.querySelectorAll(query)), root]; + + queried.forEach((el) => { + attrArray.forEach((attr) => { + if (el.hasAttribute(attr)) { + const currentAttrValue = el.getAttribute(attr); + + if (!currentAttrValue) return; + + [...replaceMap.entries()].some(([oldStr, newStr]) => { + if ( + currentAttrValue.length >= oldStr.length && + currentAttrValue.indexOf(oldStr) == + currentAttrValue.length - oldStr.length + ) { + el.setAttribute(attr, currentAttrValue.replace(oldStr, newStr)); + updated = true; + return true; + } + return false; + }); + } + }); + }); + + return updated; + } + + static updateElement(el: Element, updateOptions: IUpdateTag) { + const exists = this.futureEntryAlreadyExist(el, updateOptions); + if (exists) { + console.log('Ignored updating entry already available: ', exists.outerHTML); + return; + } + + const { comment, attrs, content } = updateOptions; + + if (comment) { + const { commentNode } = this.getElementComment(el); + if (commentNode) { + commentNode.textContent = comment; + } + } + + if (attrs) { + for (const attr in attrs) { + const value = attrs[attr]; + + if (value != undefined) { + el.setAttribute(attr, `${value}`); + } else { + el.removeAttribute(attr); + } + } + } + + if (content != undefined) { + el.innerHTML = `${content}`; + } + + return true; + } + + static elementToOptions(el: Element): ITag { + return { + attrs: this.getAllElementAttributes(el), + tagName: el.tagName, + }; + } + + static getAllElementAttributes(el: Element): Record<string, string> { + return el + .getAttributeNames() + .reduce( + (acc, attr) => ({ ...acc, [attr]: el.getAttribute(attr) || '' }), + {} as Record<string, string> + ); + } + + static futureEntryAlreadyExist(el: Element, updateOptions: IUpdateTag) { + const currentElOptions = this.elementToOptions(el); + + if (!el.parentElement) { + console.log('Checked el has no parent'); + process.exit(); + } + + return this.alreadyHasEntry(el.parentElement, { + ...currentElOptions, + ...updateOptions, + attrs: { ...currentElOptions.attrs, ...updateOptions.attrs }, + }); + } + + static alreadyHasEntry( + containerElement: Element, + { attrs, tagName }: Pick<INewTag, 'attrs' | 'tagName'> + ) { + const qAttrs = attrs + ? Object.entries(attrs) + .map(([a, v]) => `[${a}="${v}"]`) + .join('') + : ''; + + return containerElement.querySelector(tagName + qAttrs); + } + + static replaceContentTextOnQueried( + root: Element, + query: string, + replacePairs: Array<[string, string]> + ) { + let updated = false; + let queried = Array.from(root.querySelectorAll(query)); + + if (queried.length == 0) queried = [...Array.from(root.querySelectorAll(query)), root]; + + queried.forEach((el) => { + replacePairs.forEach(([oldStr, newStr]) => { + if (el.innerHTML == oldStr) { + el.innerHTML = newStr; + updated = true; + } + }); + }); + + return updated; + } + + static XMLDocToString(doc: XMLDocument) { + let str = ''; + + doc.childNodes.forEach((node) => { + switch (node.nodeType) { + case 8: // comment + str += `<!--${node.nodeValue}-->\n`; + break; + + case 3: // text + str += node.textContent; + break; + + case 1: // element + str += (node as Element).outerHTML; + break; + + default: + console.log('Unhandled node type: ' + node.nodeType); + break; + } + }); + + return str; + } +} diff --git a/packages/SystemUI/scripts/token_alignment/helpers/FileIO.ts b/packages/SystemUI/scripts/token_alignment/helpers/FileIO.ts new file mode 100644 index 000000000000..359e3ab6568b --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/helpers/FileIO.ts @@ -0,0 +1,112 @@ +// Copyright 2022 Google LLC + +// 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.import { exec } from 'child_process'; + +import { exec } from 'child_process'; +import { parse } from 'csv-parse'; +import { promises as fs } from 'fs'; +import jsdom from 'jsdom'; + +const DOMParser = new jsdom.JSDOM('').window.DOMParser as typeof window.DOMParser; + +type TFileList = string[]; + +export type TCSVRecord = Array<string | boolean | number>; + +class _FileIO { + public parser = new DOMParser(); + public saved: string[] = []; + + public loadXML = async (path: string): Promise<XMLDocument> => { + try { + const src = await this.loadFileAsText(path); + return this.parser.parseFromString(src, 'text/xml') as XMLDocument; + } catch (error) { + console.log(`Failed to parse XML file '${path}'.`, error); + process.exit(); + } + }; + + public loadFileAsText = async (path: string): Promise<string> => { + try { + return await fs.readFile(path, { encoding: 'utf8' }); + } catch (error) { + console.log(`Failed to read file '${path}'.`, error); + process.exit(); + } + }; + + public saveFile = async (data: string, path: string) => { + try { + await fs.writeFile(path, data, { encoding: 'utf8' }); + this.saved.push(path); + } catch (error) { + console.log(error); + console.log(`Failed to write file '${path}'.`); + process.exit(); + } + }; + + public loadFileList = async (path: string): Promise<TFileList> => { + const src = await this.loadFileAsText(path); + + try { + return JSON.parse(src) as TFileList; + } catch (error) { + console.log(error); + console.log(`Failed to parse JSON file '${path}'.`); + process.exit(); + } + }; + + public loadCSV = (path: string): Promise<Array<TCSVRecord>> => { + return new Promise((resolve, reject) => { + this.loadFileAsText(path).then((src) => { + parse( + src, + { + delimiter: ' ', + }, + (err, records) => { + if (err) { + reject(err); + return; + } + + resolve(records); + } + ); + }); + }); + }; + + formatSaved = () => { + const cmd = `idea format ${this.saved.join(' ')}`; + + exec(cmd, (error, out, stderr) => { + if (error) { + console.log(error.message); + return; + } + + if (stderr) { + console.log(stderr); + return; + } + + console.log(out); + }); + }; +} + +export const FileIO = new _FileIO(); diff --git a/packages/SystemUI/scripts/token_alignment/helpers/migrationList.ts b/packages/SystemUI/scripts/token_alignment/helpers/migrationList.ts new file mode 100644 index 000000000000..8d506449e6fe --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/helpers/migrationList.ts @@ -0,0 +1,70 @@ +// Copyright 2022 Google LLC + +// 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.import { exec } from 'child_process'; + +import { FileIO, TCSVRecord } from './FileIO'; +import ProcessArgs from './processArgs'; + +interface IInputMigItem { + migrationToken: string; + materialToken: string; + newDefaultValue?: string; + newComment?: string; +} + +interface IAditionalKeys { + step: ('update' | 'duplicate' | 'add' | 'ignore')[]; + isHidden: boolean; + replaceToken: string; +} + +export type IMigItem = Omit<IInputMigItem, 'materialToken' | 'migrationToken'> & IAditionalKeys; + +export type IMigrationMap = Map<string, IMigItem>; + +function isMigrationRecord(record: TCSVRecord): record is string[] { + return !record.some((value) => typeof value != 'string') || record.length != 5; +} + +export const loadMIgrationList = async function (): Promise<IMigrationMap> { + const out: IMigrationMap = new Map(); + const csv = await FileIO.loadCSV('resources/migrationList.csv'); + + csv.forEach((record, i) => { + if (i == 0) return; // header + + if (typeof record[0] != 'string') return; + + if (!isMigrationRecord(record)) { + console.log(`Failed to validade CSV record as string[5].`, record); + process.exit(); + } + + const [originalToken, materialToken, newDefaultValue, newComment, migrationToken] = record; + + if (out.has(originalToken)) { + console.log('Duplicated entry on Migration CSV file: ', originalToken); + return; + } + + out.set(originalToken, { + replaceToken: ProcessArgs.isDebug ? migrationToken : materialToken, + ...(!!newDefaultValue && { newDefaultValue }), + ...(!!newComment && { newComment }), + step: [], + isHidden: false, + }); + }); + + return new Map([...out].sort((a, b) => b[0].length - a[0].length)); +}; diff --git a/packages/SystemUI/scripts/token_alignment/helpers/processArgs.ts b/packages/SystemUI/scripts/token_alignment/helpers/processArgs.ts new file mode 100644 index 000000000000..be0e232e66b1 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/helpers/processArgs.ts @@ -0,0 +1,21 @@ +// Copyright 2022 Google LLC + +// 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.import { exec } from 'child_process'; + +const myArgs = process.argv.slice(2); + +const ProcessArgs = { + isDebug: myArgs.includes('debug'), +}; + +export default ProcessArgs; diff --git a/packages/SystemUI/scripts/token_alignment/helpers/processXML.ts b/packages/SystemUI/scripts/token_alignment/helpers/processXML.ts new file mode 100644 index 000000000000..368d4cbad3bd --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/helpers/processXML.ts @@ -0,0 +1,102 @@ +// Copyright 2022 Google LLC + +// 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.import { exec } from 'child_process'; + +import DOM, { INewTag, IUpdateTag } from './DOMFuncs'; +import { FileIO } from './FileIO'; +import { IMigItem, IMigrationMap } from './migrationList'; + +export type TResultExistingEval = ['update' | 'duplicate', IUpdateTag] | void; +export type TResultMissingEval = INewTag | void; + +interface IProcessXML { + attr?: string; + containerQuery?: string; + evalExistingEntry?: TEvalExistingEntry; + evalMissingEntry?: TEvalMissingEntry; + hidable?: boolean; + path: string; + step: number; + tagName: string; +} + +export type TEvalExistingEntry = ( + attrname: string, + migItem: IMigItem, + qItem: Element +) => TResultExistingEval; + +export type TEvalMissingEntry = (originalToken: string, migItem: IMigItem) => TResultMissingEval; + +export async function processQueriedEntries( + migrationMap: IMigrationMap, + { + attr = 'name', + containerQuery = '*', + evalExistingEntry, + path, + step, + tagName, + evalMissingEntry, + }: IProcessXML +) { + const doc = await FileIO.loadXML(path); + + const containerElement = + (containerQuery && doc.querySelector(containerQuery)) || doc.documentElement; + + migrationMap.forEach((migItem, originalToken) => { + migItem.step[step] = 'ignore'; + + const queryTiems = containerElement.querySelectorAll( + `${tagName}[${attr}="${originalToken}"]` + ); + + if (evalMissingEntry) { + const addinOptions = evalMissingEntry(originalToken, migItem); + + if (queryTiems.length == 0 && containerElement && addinOptions) { + DOM.addEntry(containerElement, addinOptions); + migItem.step[step] = 'add'; + return; + } + } + + if (evalExistingEntry) + queryTiems.forEach((qEl) => { + const attrName = qEl.getAttribute(attr); + const migItem = migrationMap.get(attrName || ''); + + if (!attrName || !migItem) return; + + const updateOptions = evalExistingEntry(attrName, migItem, qEl); + + if (!updateOptions) return; + + const [processType, processOptions] = updateOptions; + + switch (processType) { + case 'update': + if (DOM.updateElement(qEl, processOptions)) migItem.step[step] = 'update'; + break; + + case 'duplicate': + if (DOM.duplicateEntryWithChange(qEl, processOptions)) + migItem.step[step] = 'duplicate'; + break; + } + }); + }); + + await FileIO.saveFile(doc.documentElement.outerHTML, path); +} diff --git a/packages/SystemUI/scripts/token_alignment/helpers/rootPath.ts b/packages/SystemUI/scripts/token_alignment/helpers/rootPath.ts new file mode 100644 index 000000000000..2c6f6329d7ea --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/helpers/rootPath.ts @@ -0,0 +1,21 @@ +// Copyright 2022 Google LLC + +// 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.import { exec } from 'child_process'; + +if (!process?.env?.ANDROID_BUILD_TOP) { + console.log( + "Error: Couldn't find 'ANDROID_BUILD_TOP' environment variable. Make sure to run 'lunch' in this terminal" + ); +} + +export const repoPath = process?.env?.ANDROID_BUILD_TOP; diff --git a/packages/SystemUI/scripts/token_alignment/helpers/textFuncs.ts b/packages/SystemUI/scripts/token_alignment/helpers/textFuncs.ts new file mode 100644 index 000000000000..6679c5a9d777 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/helpers/textFuncs.ts @@ -0,0 +1,27 @@ +// Copyright 2022 Google LLC + +// 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.import { exec } from 'child_process'; + +export function groupReplace(src: string, replaceMap: Map<string, string>, pattern: string) { + const fullPattern = pattern.replace('#group#', [...replaceMap.keys()].join('|')); + + const regEx = new RegExp(fullPattern, 'g'); + + ''.replace; + + return src.replace(regEx, (...args) => { + //match, ...matches, offset, string, groups + const [match, key] = args as string[]; + return match.replace(key, replaceMap.get(key) || ''); + }); +} diff --git a/packages/SystemUI/scripts/token_alignment/index.ts b/packages/SystemUI/scripts/token_alignment/index.ts new file mode 100644 index 000000000000..1b15e48ecbd0 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/index.ts @@ -0,0 +1,240 @@ +// Copyright 2022 Google LLC + +// 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.import { exec } from 'child_process'; + +import DOM from './helpers/DOMFuncs'; +import { FileIO } from './helpers/FileIO'; +import { loadMIgrationList } from './helpers/migrationList'; +import { processQueriedEntries, TEvalExistingEntry } from './helpers/processXML'; +import { repoPath } from './helpers/rootPath'; +import { groupReplace } from './helpers/textFuncs'; + +async function init() { + const migrationMap = await loadMIgrationList(); + const basePath = `${repoPath}/../tm-qpr-dev/frameworks/base/core/res/res/values/`; + + await processQueriedEntries(migrationMap, { + containerQuery: 'declare-styleable[name="Theme"]', + hidable: true, + path: `${basePath}attrs.xml`, + step: 0, + tagName: 'attr', + evalExistingEntry: (_attrValue, migItem, qItem) => { + const { hidden, textContent: currentComment } = DOM.getElementComment(qItem); + + if (hidden) migItem.isHidden = hidden; + + const { newComment } = migItem; + return [ + hidden ? 'update' : 'duplicate', + { + attrs: { name: migItem.replaceToken }, + ...(newComment + ? { comment: `${newComment} @hide ` } + : currentComment + ? { comment: hidden ? currentComment : `${currentComment} @hide ` } + : {}), + }, + ]; + }, + evalMissingEntry: (_originalToken, { replaceToken, newComment }) => { + return { + tagName: 'attr', + attrs: { + name: replaceToken, + format: 'color', + }, + comment: `${newComment} @hide `, + }; + }, + }); + + // only update all existing entries + await processQueriedEntries(migrationMap, { + tagName: 'item', + path: `${basePath}themes_device_defaults.xml`, + containerQuery: 'resources', + step: 2, + evalExistingEntry: (_attrValue, { isHidden, replaceToken, step }, _qItem) => { + if (step[0] != 'ignore') + return [ + isHidden ? 'update' : 'duplicate', + { + attrs: { name: replaceToken }, + }, + ]; + }, + }); + + // add missing entries on specific container + await processQueriedEntries(migrationMap, { + tagName: 'item', + path: `${basePath}themes_device_defaults.xml`, + containerQuery: 'resources style[parent="Theme.Material"]', + step: 3, + evalMissingEntry: (originalToken, { newDefaultValue, replaceToken }) => { + return { + tagName: 'item', + content: newDefaultValue, + attrs: { + name: replaceToken, + }, + }; + }, + }); + + const evalExistingEntry: TEvalExistingEntry = (_attrValue, { replaceToken, step }, _qItem) => { + if (step[0] == 'update') + return [ + 'update', + { + attrs: { name: replaceToken }, + }, + ]; + }; + + await processQueriedEntries(migrationMap, { + tagName: 'item', + containerQuery: 'resources', + path: `${basePath}../values-night/themes_device_defaults.xml`, + step: 4, + evalExistingEntry, + }); + + await processQueriedEntries(migrationMap, { + tagName: 'java-symbol', + path: `${basePath}symbols.xml`, + containerQuery: 'resources', + step: 5, + evalExistingEntry, + }); + + // update attributes on tracked XML files + { + const searchAttrs = [ + 'android:color', + 'android:indeterminateTint', + 'app:tint', + 'app:backgroundTint', + 'android:background', + 'android:tint', + 'android:drawableTint', + 'android:textColor', + 'android:fillColor', + 'android:startColor', + 'android:endColor', + 'name', + 'ns1:color', + ]; + + const filtered = new Map( + [...migrationMap] + .filter(([_originalToken, { step }]) => step[0] == 'update') + .map(([originalToken, { replaceToken }]) => [originalToken, replaceToken]) + ); + + const query = + searchAttrs.map((str) => `*[${str}]`).join(',') + + [...filtered.keys()].map((originalToken) => `item[name*="${originalToken}"]`).join(','); + + const trackedFiles = await FileIO.loadFileList( + `${__dirname}/resources/whitelist/xmls1.json` + ); + + const promises = trackedFiles.map(async (locaFilePath) => { + const filePath = `${repoPath}/${locaFilePath}`; + + const doc = await FileIO.loadXML(filePath); + const docUpdated = DOM.replaceStringInAttributeValueOnQueried( + doc.documentElement, + query, + searchAttrs, + filtered + ); + if (docUpdated) { + await FileIO.saveFile(DOM.XMLDocToString(doc), filePath); + } else { + console.warn(`Failed to update tracked file: '${locaFilePath}'`); + } + }); + await Promise.all(promises); + } + + // updates tag content on tracked files + { + const searchPrefixes = ['?android:attr/', '?androidprv:attr/']; + const filtered = searchPrefixes + .reduce<Array<[string, string]>>((acc, prefix) => { + return [ + ...acc, + ...[...migrationMap.entries()] + .filter(([_originalToken, { step }]) => step[0] == 'update') + .map( + ([originalToken, { replaceToken }]) => + [`${prefix}${originalToken}`, `${prefix}${replaceToken}`] as [ + string, + string + ] + ), + ]; + }, []) + .sort((a, b) => b[0].length - a[0].length); + + const trackedFiles = await FileIO.loadFileList( + `${__dirname}/resources/whitelist/xmls2.json` + ); + + const promises = trackedFiles.map(async (locaFilePath) => { + const filePath = `${repoPath}/${locaFilePath}`; + const doc = await FileIO.loadXML(filePath); + const docUpdated = DOM.replaceContentTextOnQueried( + doc.documentElement, + 'item, color', + filtered + ); + if (docUpdated) { + await FileIO.saveFile(DOM.XMLDocToString(doc), filePath); + } else { + console.warn(`Failed to update tracked file: '${locaFilePath}'`); + } + }); + await Promise.all(promises); + } + + // replace imports on Java / Kotlin + { + const replaceMap = new Map( + [...migrationMap.entries()] + .filter(([_originalToken, { step }]) => step[0] == 'update') + .map( + ([originalToken, { replaceToken }]) => + [originalToken, replaceToken] as [string, string] + ) + .sort((a, b) => b[0].length - a[0].length) + ); + + const trackedFiles = await FileIO.loadFileList( + `${__dirname}/resources/whitelist/java.json` + ); + + const promises = trackedFiles.map(async (locaFilePath) => { + const filePath = `${repoPath}/${locaFilePath}`; + const fileContent = await FileIO.loadFileAsText(filePath); + const str = groupReplace(fileContent, replaceMap, 'R.attr.(#group#)(?![a-zA-Z])'); + await FileIO.saveFile(str, filePath); + }); + await Promise.all(promises); + } +} + +init(); diff --git a/packages/SystemUI/scripts/token_alignment/package-lock.json b/packages/SystemUI/scripts/token_alignment/package-lock.json new file mode 100644 index 000000000000..da9edb3b39b5 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/package-lock.json @@ -0,0 +1,3356 @@ +{ + "name": "token_alignment", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "csv-parse": "^5.3.3", + "high5": "^1.0.0", + "jsdom": "^20.0.3" + }, + "devDependencies": { + "@types/jsdom": "^20.0.1", + "@types/node": "^18.11.18", + "@typescript-eslint/eslint-plugin": "^5.48.0", + "eslint-config-prettier": "^8.6.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-simple-import-sort": "^8.0.0", + "ts-node": "^10.9.1", + "typescript": "^4.9.4" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true, + "peer": true + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz", + "integrity": "sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/type-utils": "5.48.0", + "@typescript-eslint/utils": "5.48.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz", + "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/typescript-estree": "5.48.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", + "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz", + "integrity": "sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.48.0", + "@typescript-eslint/utils": "5.48.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", + "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", + "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.0.tgz", + "integrity": "sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/typescript-estree": "5.48.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", + "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.48.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "peer": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, + "node_modules/csv-parse": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.3.3.tgz", + "integrity": "sha512-kEWkAPleNEdhFNkHQpFHu9RYPogsFj3dx6bCxL847fsiLgidzWg0z/O0B1kVWMJUc5ky64zGp18LX2T3DQrOfw==" + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.0.tgz", + "integrity": "sha512-GUGtW7eXQay0c+PRq0sGIKSdaBorfVqsCMhGHo4elP7YVqZu9nCZS4UkK4gv71gOWNMra/PaSKD3ao1oWExO0g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.0", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.0", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", + "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-8.0.0.tgz", + "integrity": "sha512-bXgJQ+lqhtQBCuWY/FUWdB27j4+lqcvXv5rUARkzbeWLwea+S5eBZEQrhnO+WgX3ZoJHVj0cn943iyXwByHHQw==", + "dev": true, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "peer": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "peer": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "peer": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true, + "peer": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "peer": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true, + "peer": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/high5": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/high5/-/high5-1.0.0.tgz", + "integrity": "sha512-xucW/5M1hd+p6bj530wtRSKwqUQrgiIgOWepi4Di9abkonZaxhTDf0zrqqraxfZSXBcFSuc1/WVGBIlqSe1Hdw==", + "dependencies": { + "entities": "1.0" + } + }, + "node_modules/high5/node_modules/entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==" + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "peer": true + }, + "node_modules/internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "peer": true + }, + "node_modules/js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "peer": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "peer": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "peer": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz", + "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==", + "dev": true, + "peer": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "peer": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "peer": true + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/packages/SystemUI/scripts/token_alignment/package.json b/packages/SystemUI/scripts/token_alignment/package.json new file mode 100644 index 000000000000..2e6366876406 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/package.json @@ -0,0 +1,22 @@ +{ + "dependencies": { + "csv-parse": "^5.3.3", + "high5": "^1.0.0", + "jsdom": "^20.0.3" + }, + "devDependencies": { + "@types/jsdom": "^20.0.1", + "@types/node": "^18.11.18", + "@typescript-eslint/eslint-plugin": "^5.48.0", + "eslint-config-prettier": "^8.6.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-simple-import-sort": "^8.0.0", + "ts-node": "^10.9.1", + "typescript": "^4.9.4" + }, + "scripts": { + "main": "ts-node ./index.ts", + "resetRepo": "repo forall -j100 -c 'git restore .'" + } +} diff --git a/packages/SystemUI/scripts/token_alignment/resources/migrationList.csv b/packages/SystemUI/scripts/token_alignment/resources/migrationList.csv new file mode 100644 index 000000000000..4111bb3ffb9b --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/resources/migrationList.csv @@ -0,0 +1,16 @@ +android material newDefaultValue newComment migrationToken +colorAccentPrimaryVariant colorPrimaryContainer MigTok02 +colorAccentSecondaryVariant colorSecondaryContainer MigTok04 +colorAccentTertiary colorTertiary MigTok05 +colorAccentTertiaryVariant colorTertiaryContainer MigTok06 +colorBackground colorSurfaceContainer MigTok07 +colorSurface colorSurfaceContainer MigTok08 +colorSurfaceHeader colorSurfaceContainerHighest MigTok09 +colorSurfaceHighlight colorSurfaceBright MigTok10 +colorSurfaceVariant colorSurfaceContainerHigh MigTok11 +textColorOnAccent colorOnPrimary MigTok12 +textColorPrimary colorOnSurface MigTok13 +textColorPrimaryInverse colorOnShadeInactive MigTok14 +textColorSecondary colorOnSurfaceVariant MigTok15 +textColorSecondaryInverse colorOnShadeInactiveVariant MigTok16 +textColorTertiary colorOutline MigTok17
\ No newline at end of file diff --git a/packages/SystemUI/scripts/token_alignment/resources/whitelist/java.json b/packages/SystemUI/scripts/token_alignment/resources/whitelist/java.json new file mode 100644 index 000000000000..7f55b2d56875 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/resources/whitelist/java.json @@ -0,0 +1,30 @@ +[ + "frameworks/base/core/java/android/app/Notification.java", + "packages/apps/Settings/src/com/android/settings/dashboard/profileselector/UserAdapter.java", + "packages/apps/Settings/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java", + "frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt", + "frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt", + "packages/apps/WallpaperPicker2/src/com/android/wallpaper/picker/PreviewFragment.java", + "packages/apps/WallpaperPicker2/src/com/android/wallpaper/picker/CategorySelectorFragment.java", + "packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt", + "frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java", + "vendor/unbundled_google/packages/NexusLauncher/src/com/google/android/apps/nexuslauncher/customize/WallpaperCarouselView.java", + "vendor/unbundled_google/packages/NexusLauncher/src/com/google/android/apps/nexuslauncher/quickstep/TaskOverlayFactoryImpl.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt", + "frameworks/base/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt", + "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt", + "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt", + "frameworks/base/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserViewBinder.kt", + "frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java", + "frameworks/base/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java", + "frameworks/base/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java" +]
\ No newline at end of file diff --git a/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls1.json b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls1.json new file mode 100644 index 000000000000..1e59773e0d81 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls1.json @@ -0,0 +1,184 @@ +[ + "frameworks/base/core/res/res/color/resolver_profile_tab_selected_bg.xml", + "frameworks/base/core/res/res/color-night/resolver_profile_tab_selected_bg.xml", + "frameworks/base/core/res/res/drawable/autofill_bottomsheet_background.xml", + "frameworks/base/core/res/res/drawable/btn_outlined.xml", + "frameworks/base/core/res/res/drawable/btn_tonal.xml", + "frameworks/base/core/res/res/drawable/chooser_action_button_bg.xml", + "frameworks/base/core/res/res/drawable/chooser_row_layer_list.xml", + "frameworks/base/core/res/res/drawable/resolver_outlined_button_bg.xml", + "frameworks/base/core/res/res/drawable/resolver_profile_tab_bg.xml", + "frameworks/base/core/res/res/drawable/toast_frame.xml", + "frameworks/base/core/res/res/drawable/work_widget_mask_view_background.xml", + "frameworks/base/core/res/res/layout/app_language_picker_current_locale_item.xml", + "frameworks/base/core/res/res/layout/app_language_picker_system_current.xml", + "frameworks/base/core/res/res/layout/autofill_save.xml", + "frameworks/base/core/res/res/layout/chooser_grid.xml", + "frameworks/base/core/res/res/layout/user_switching_dialog.xml", + "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml", + "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml", + "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml", + "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml", + "packages/modules/IntentResolver/java/res/drawable/chooser_action_button_bg.xml", + "packages/modules/IntentResolver/java/res/drawable/chooser_row_layer_list.xml", + "packages/modules/IntentResolver/java/res/drawable/resolver_outlined_button_bg.xml", + "packages/modules/IntentResolver/java/res/drawable/resolver_profile_tab_bg.xml", + "packages/modules/IntentResolver/java/res/layout/chooser_grid.xml", + "frameworks/base/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml", + "frameworks/base/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_bg.xml", + "frameworks/base/libs/WindowManager/Shell/res/drawable/bubble_stack_user_education_bg.xml", + "frameworks/base/libs/WindowManager/Shell/res/drawable/bubble_stack_user_education_bg_rtl.xml", + "vendor/unbundled_google/packages/SystemUIGoogle/bcsmartspace/res/drawable/bg_smartspace_combination_sub_card.xml", + "vendor/unbundled_google/packages/SystemUIGoogle/bcsmartspace/res/layout/smartspace_combination_sub_card.xml", + "packages/apps/Launcher3/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml", + "packages/apps/Launcher3/res/drawable/rounded_action_button.xml", + "packages/apps/Launcher3/res/drawable/work_card.xml", + "packages/apps/Nfc/res/color/nfc_icon.xml", + "packages/apps/Nfc/res/color-night/nfc_icon.xml", + "packages/apps/Launcher3/quickstep/res/drawable/bg_overview_clear_all_button.xml", + "packages/apps/Launcher3/quickstep/res/drawable/bg_sandbox_feedback.xml", + "packages/apps/Launcher3/quickstep/res/drawable/bg_wellbeing_toast.xml", + "packages/apps/Launcher3/quickstep/res/drawable/button_taskbar_edu_bordered.xml", + "packages/apps/Launcher3/quickstep/res/drawable/button_taskbar_edu_colored.xml", + "packages/apps/Launcher3/quickstep/res/drawable/split_instructions_background.xml", + "packages/apps/Launcher3/quickstep/res/drawable/task_menu_item_bg.xml", + "packages/apps/Launcher3/quickstep/res/layout/digital_wellbeing_toast.xml", + "packages/apps/Launcher3/quickstep/res/layout/split_instructions_view.xml", + "packages/apps/Launcher3/quickstep/res/layout/taskbar_edu.xml", + "frameworks/base/packages/SettingsLib/res/drawable/broadcast_dialog_btn_bg.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/color/share_target_text.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/all_apps_tab_background_selected.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/all_apps_tabs_background.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/arrow_tip_view_bg.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/button_bg.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/shortcut_halo.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/surface.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/color-night-v31/all_apps_tab_background_selected.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/color-night-v31/surface.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/drawable/bg_pin_keyboard_snackbar_accept_button.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/drawable/bg_search_edu_preferences_button.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/drawable/circle_accentprimary_32dp.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/drawable/ic_search.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/drawable/ic_suggest_icon_background.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/drawable/share_target_background.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/drawable/sticky_snackbar_accept_btn_background.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/drawable/sticky_snackbar_background.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/drawable/sticky_snackbar_dismiss_btn_background.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/drawable/tall_card_btn_background.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/layout/section_header.xml", + "packages/apps/Settings/res/color/dream_card_color_state_list.xml", + "packages/apps/Settings/res/color/dream_card_icon_color_state_list.xml", + "packages/apps/Settings/res/color/dream_card_text_color_state_list.xml", + "packages/apps/Settings/res/drawable/accessibility_text_reading_preview.xml", + "packages/apps/Settings/res/drawable/broadcast_button_outline.xml", + "packages/apps/Settings/res/drawable/button_border_selected.xml", + "packages/apps/Settings/res/drawable/dream_preview_rounded_bg.xml", + "packages/apps/Settings/res/drawable/rounded_bg.xml", + "packages/apps/Settings/res/drawable/sim_confirm_dialog_btn_outline.xml", + "packages/apps/Settings/res/drawable/user_select_background.xml", + "packages/apps/Settings/res/drawable/volume_dialog_button_background_outline.xml", + "packages/apps/Settings/res/drawable/volume_dialog_button_background_solid.xml", + "packages/apps/Settings/res/layout/dream_preview_button.xml", + "packages/apps/Settings/res/layout/qrcode_scanner_fragment.xml", + "frameworks/base/packages/SystemUI/res/values-television/styles.xml", + "frameworks/base/packages/SystemUI/res/color/media_player_album_bg.xml", + "frameworks/base/packages/SystemUI/res/color/media_player_outline_button_bg.xml", + "frameworks/base/packages/SystemUI/res/color/media_player_solid_button_bg.xml", + "frameworks/base/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml", + "frameworks/base/packages/SystemUI/res/color/settingslib_state_on.xml", + "frameworks/base/packages/SystemUI/res/color/settingslib_track_off.xml", + "frameworks/base/packages/SystemUI/res/color/settingslib_track_on.xml", + "frameworks/base/packages/SystemUI/res/drawable/accessibility_floating_tooltip_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/action_chip_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/action_chip_container_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/availability_dot_10dp.xml", + "frameworks/base/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml", + "frameworks/base/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_item_selected_bg.xml", + "frameworks/base/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_popup_bg.xml", + "frameworks/base/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml", + "frameworks/base/packages/SystemUI/res/drawable/broadcast_dialog_btn_bg.xml", + "frameworks/base/packages/SystemUI/res/drawable/fgs_dot.xml", + "frameworks/base/packages/SystemUI/res/drawable/fingerprint_bg.xml", + "frameworks/base/packages/SystemUI/res/drawable/ic_avatar_with_badge.xml", + "frameworks/base/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml", + "frameworks/base/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml", + "frameworks/base/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/logout_button_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/media_ttt_chip_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/media_ttt_chip_background_receiver.xml", + "frameworks/base/packages/SystemUI/res/drawable/media_ttt_undo_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/notif_footer_btn_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/notification_guts_bg.xml", + "frameworks/base/packages/SystemUI/res/drawable/notification_material_bg.xml", + "frameworks/base/packages/SystemUI/res/drawable/overlay_badge_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/overlay_border.xml", + "frameworks/base/packages/SystemUI/res/drawable/overlay_button_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/overlay_cancel.xml", + "frameworks/base/packages/SystemUI/res/drawable/people_space_messages_count_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/people_tile_status_scrim.xml", + "frameworks/base/packages/SystemUI/res/drawable/people_tile_suppressed_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml", + "frameworks/base/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml", + "frameworks/base/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml", + "frameworks/base/packages/SystemUI/res/drawable/qs_media_outline_button.xml", + "frameworks/base/packages/SystemUI/res/drawable/qs_media_solid_button.xml", + "frameworks/base/packages/SystemUI/res/drawable/rounded_bg_full.xml", + "frameworks/base/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml", + "frameworks/base/packages/SystemUI/res/drawable/screenrecord_button_background_solid.xml", + "frameworks/base/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/screenrecord_spinner_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/screenshot_edit_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml", + "frameworks/base/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml", + "frameworks/base/packages/SystemUI/res/drawable/volume_background_bottom.xml", + "frameworks/base/packages/SystemUI/res/drawable/volume_background_top.xml", + "frameworks/base/packages/SystemUI/res/drawable/volume_background_top_rounded.xml", + "frameworks/base/packages/SystemUI/res/drawable/volume_row_rounded_background.xml", + "frameworks/base/packages/SystemUI/res/drawable/volume_row_seekbar.xml", + "frameworks/base/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml", + "frameworks/base/packages/SystemUI/res/drawable/wallet_action_button_bg.xml", + "frameworks/base/packages/SystemUI/res/drawable/wallet_app_button_bg.xml", + "frameworks/base/packages/SystemUI/res/drawable/wallet_empty_state_bg.xml", + "frameworks/base/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml", + "frameworks/base/packages/SystemUI/res/layout/chipbar.xml", + "frameworks/base/packages/SystemUI/res/layout/chipbar.xml", + "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay.xml", + "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay.xml", + "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml", + "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml", + "frameworks/base/packages/SystemUI/res/layout/internet_connectivity_dialog.xml", + "frameworks/base/packages/SystemUI/res/layout/notification_snooze.xml", + "frameworks/base/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml", + "frameworks/base/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml", + "frameworks/base/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml", + "frameworks/base/packages/SystemUI/res/layout/people_space_tile_view.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_large_with_content.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_medium_with_content.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml", + "frameworks/base/packages/SystemUI/res/layout/chipbar.xml", + "frameworks/base/packages/SystemUI/res/layout/notification_snooze.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_small.xml", + "frameworks/base/packages/SystemUI/res/layout/people_tile_small_horizontal.xml", + "frameworks/base/packages/SystemUI/res/layout/screen_share_dialog.xml", + "frameworks/base/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml", + "frameworks/base/packages/SystemUI/res/layout/user_switcher_fullscreen.xml", + "frameworks/base/packages/SystemUI/res/layout/user_switcher_fullscreen.xml", + "frameworks/base/packages/SystemUI/res/layout/wallet_empty_state.xml", + "frameworks/base/packages/SystemUI/res/layout/wallet_fullscreen.xml", + "frameworks/base/packages/SystemUI/res/layout/wallet_fullscreen.xml", + "frameworks/base/packages/SystemUI/res/layout/chipbar.xml", + "frameworks/base/packages/SystemUI/res/layout/notification_snooze.xml", + "vendor/unbundled_google/packages/SystemUIGoogle/res/drawable/columbus_chip_background_raw.xml", + "vendor/unbundled_google/packages/SystemUIGoogle/res/drawable/columbus_chip_background_raw.xml", + "vendor/unbundled_google/packages/SystemUIGoogle/res/drawable/columbus_dialog_background.xml", + "vendor/unbundled_google/packages/SystemUIGoogle/res/layout/columbus_target_request_dialog.xml", + "vendor/unbundled_google/packages/SettingsGoogle/res/color/dream_card_suw_color_state_list.xml", + "vendor/unbundled_google/packages/SettingsGoogle/res/drawable/dream_item_suw_rounded_bg.xml" +]
\ No newline at end of file diff --git a/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls2.json b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls2.json new file mode 100644 index 000000000000..20a7c76146e2 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls2.json @@ -0,0 +1,13 @@ +[ + "vendor/google/nexus_overlay/PixelDocumentsUIGoogleOverlay/res/values-v31/themes.xml", + "vendor/google/nexus_overlay/PixelDocumentsUIGoogleOverlay/res/values-night-v31/themes.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/values/colors.xml", + "vendor/unbundled_google/packages/NexusLauncher/res/values/styles.xml", + "packages/apps/Settings/res/values-night/colors.xml", + "packages/apps/Settings/res/values/colors.xml", + "packages/apps/Settings/res/values/styles.xml", + "frameworks/base/packages/SystemUI/res-keyguard/values/styles.xml", + "frameworks/base/packages/SystemUI/res/values/styles.xml", + "vendor/unbundled_google/packages/SettingsGoogle/res/values/styles.xml", + "vendor/unbundled_google/packages/SettingsGoogle/res/values/styles.xml" +]
\ No newline at end of file diff --git a/packages/SystemUI/scripts/token_alignment/tsconfig.json b/packages/SystemUI/scripts/token_alignment/tsconfig.json new file mode 100644 index 000000000000..20c7321d6347 --- /dev/null +++ b/packages/SystemUI/scripts/token_alignment/tsconfig.json @@ -0,0 +1,103 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Speciffy the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": ["../node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt index 196f7f05d20d..c9a25b067b94 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt @@ -47,10 +47,6 @@ interface ResourceFlag<T> : Flag<T> { val resourceId: Int } -interface DeviceConfigFlag<T> : Flag<T> { - val default: T -} - interface SysPropFlag<T> : Flag<T> { val default: T } @@ -80,8 +76,8 @@ abstract class BooleanFlag constructor( private constructor(parcel: Parcel) : this( id = parcel.readInt(), - name = parcel.readString(), - namespace = parcel.readString(), + name = parcel.readString() ?: "", + namespace = parcel.readString() ?: "", default = parcel.readBoolean(), teamfood = parcel.readBoolean(), overridden = parcel.readBoolean() @@ -137,21 +133,6 @@ data class ResourceBooleanFlag constructor( ) : ResourceFlag<Boolean> /** - * A Flag that can reads its overrides from DeviceConfig. - * - * This is generally useful for flags that come from or are used _outside_ of SystemUI. - * - * Prefer [UnreleasedFlag] and [ReleasedFlag]. - */ -data class DeviceConfigBooleanFlag constructor( - override val id: Int, - override val name: String, - override val namespace: String, - override val default: Boolean = false, - override val teamfood: Boolean = false -) : DeviceConfigFlag<Boolean> - -/** * A Flag that can reads its overrides from System Properties. * * This is generally useful for flags that come from or are used _outside_ of SystemUI. @@ -186,8 +167,8 @@ data class StringFlag constructor( private constructor(parcel: Parcel) : this( id = parcel.readInt(), - name = parcel.readString(), - namespace = parcel.readString(), + name = parcel.readString() ?: "", + namespace = parcel.readString() ?: "", default = parcel.readString() ?: "" ) @@ -226,8 +207,8 @@ data class IntFlag constructor( private constructor(parcel: Parcel) : this( id = parcel.readInt(), - name = parcel.readString(), - namespace = parcel.readString(), + name = parcel.readString() ?: "", + namespace = parcel.readString() ?: "", default = parcel.readInt() ) @@ -266,8 +247,8 @@ data class LongFlag constructor( private constructor(parcel: Parcel) : this( id = parcel.readInt(), - name = parcel.readString(), - namespace = parcel.readString(), + name = parcel.readString() ?: "", + namespace = parcel.readString() ?: "", default = parcel.readLong() ) @@ -298,8 +279,8 @@ data class FloatFlag constructor( private constructor(parcel: Parcel) : this( id = parcel.readInt(), - name = parcel.readString(), - namespace = parcel.readString(), + name = parcel.readString() ?: "", + namespace = parcel.readString() ?: "", default = parcel.readFloat() ) @@ -338,8 +319,8 @@ data class DoubleFlag constructor( private constructor(parcel: Parcel) : this( id = parcel.readInt(), - name = parcel.readString(), - namespace = parcel.readString(), + name = parcel.readString() ?: "", + namespace = parcel.readString() ?: "", default = parcel.readDouble() ) diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt index 195ba46515cd..72a4fabf1f0d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt @@ -34,7 +34,7 @@ interface FlagListenable { /** An event representing the change */ interface FlagEvent { /** the id of the flag which changed */ - val flagId: Int + val flagName: String /** if all listeners alerted invoke this method, the restart will be skipped */ fun requestNoRestart() } diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt index d85292a90d63..da1641c326de 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt @@ -39,7 +39,7 @@ class FlagManager constructor( const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS" const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS" const val ACTION_SYSUI_STARTED = "com.android.systemui.STARTED" - const val EXTRA_ID = "id" + const val EXTRA_NAME = "name" const val EXTRA_VALUE = "value" const val EXTRA_FLAGS = "flags" private const val SETTINGS_PREFIX = "systemui/flags" @@ -56,7 +56,7 @@ class FlagManager constructor( * that the restart be suppressed */ var onSettingsChangedAction: Consumer<Boolean>? = null - var clearCacheAction: Consumer<Int>? = null + var clearCacheAction: Consumer<String>? = null private val listeners: MutableSet<PerFlagListener> = mutableSetOf() private val settingsObserver: ContentObserver = SettingsObserver() @@ -96,35 +96,42 @@ class FlagManager constructor( * Returns the stored value or null if not set. * This API is used by TheFlippinApp. */ - fun isEnabled(id: Int): Boolean? = readFlagValue(id, BooleanFlagSerializer) + fun isEnabled(name: String): Boolean? = readFlagValue(name, BooleanFlagSerializer) /** * Sets the value of a boolean flag. * This API is used by TheFlippinApp. */ - fun setFlagValue(id: Int, enabled: Boolean) { - val intent = createIntent(id) + fun setFlagValue(name: String, enabled: Boolean) { + val intent = createIntent(name) intent.putExtra(EXTRA_VALUE, enabled) context.sendBroadcast(intent) } - fun eraseFlag(id: Int) { - val intent = createIntent(id) + fun eraseFlag(name: String) { + val intent = createIntent(name) context.sendBroadcast(intent) } /** Returns the stored value or null if not set. */ + // TODO(b/265188950): Remove method this once ids are fully deprecated. fun <T> readFlagValue(id: Int, serializer: FlagSerializer<T>): T? { - val data = settings.getString(idToSettingsKey(id)) + val data = settings.getStringFromSecure(idToSettingsKey(id)) + return serializer.fromSettingsData(data) + } + + /** Returns the stored value or null if not set. */ + fun <T> readFlagValue(name: String, serializer: FlagSerializer<T>): T? { + val data = settings.getString(nameToSettingsKey(name)) return serializer.fromSettingsData(data) } override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) { synchronized(listeners) { val registerNeeded = listeners.isEmpty() - listeners.add(PerFlagListener(flag.id, listener)) + listeners.add(PerFlagListener(flag.name, listener)) if (registerNeeded) { settings.registerContentObserver(SETTINGS_PREFIX, true, settingsObserver) } @@ -143,38 +150,38 @@ class FlagManager constructor( } } - private fun createIntent(id: Int): Intent { + private fun createIntent(name: String): Intent { val intent = Intent(ACTION_SET_FLAG) intent.setPackage(RECEIVING_PACKAGE) - intent.putExtra(EXTRA_ID, id) + intent.putExtra(EXTRA_NAME, name) return intent } + // TODO(b/265188950): Remove method this once ids are fully deprecated. fun idToSettingsKey(id: Int): String { return "$SETTINGS_PREFIX/$id" } + fun nameToSettingsKey(name: String): String { + return "$SETTINGS_PREFIX/$name" + } + inner class SettingsObserver : ContentObserver(handler) { override fun onChange(selfChange: Boolean, uri: Uri?) { if (uri == null) { return } val parts = uri.pathSegments - val idStr = parts[parts.size - 1] - val id = try { - idStr.toInt() - } catch (e: NumberFormatException) { - return - } - clearCacheAction?.accept(id) - dispatchListenersAndMaybeRestart(id, onSettingsChangedAction) + val name = parts[parts.size - 1] + clearCacheAction?.accept(name) + dispatchListenersAndMaybeRestart(name, onSettingsChangedAction) } } - fun dispatchListenersAndMaybeRestart(id: Int, restartAction: Consumer<Boolean>?) { + fun dispatchListenersAndMaybeRestart(name: String, restartAction: Consumer<Boolean>?) { val filteredListeners: List<FlagListenable.Listener> = synchronized(listeners) { - listeners.mapNotNull { if (it.id == id) it.listener else null } + listeners.mapNotNull { if (it.name == name) it.listener else null } } // If there are no listeners, there's nothing to dispatch to, and nothing to suppress it. if (filteredListeners.isEmpty()) { @@ -185,7 +192,7 @@ class FlagManager constructor( val suppressRestartList: List<Boolean> = filteredListeners.map { listener -> var didRequestNoRestart = false val event = object : FlagListenable.FlagEvent { - override val flagId = id + override val flagName = name override fun requestNoRestart() { didRequestNoRestart = true } @@ -198,7 +205,7 @@ class FlagManager constructor( restartAction?.accept(suppressRestart) } - private data class PerFlagListener(val id: Int, val listener: FlagListenable.Listener) + private data class PerFlagListener(val name: String, val listener: FlagListenable.Listener) } class NoFlagResultsException : Exception( diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt index 742bb0b6f954..6beb8518ab67 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt @@ -22,7 +22,10 @@ import android.provider.Settings class FlagSettingsHelper(private val contentResolver: ContentResolver) { - fun getString(key: String): String? = Settings.Secure.getString(contentResolver, key) + // TODO(b/265188950): Remove method this once ids are fully deprecated. + fun getStringFromSecure(key: String): String? = Settings.Secure.getString(contentResolver, key) + + fun getString(key: String): String? = Settings.Global.getString(contentResolver, key) fun registerContentObserver( name: String, diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt index 0ee813b84402..ef2247f5d62c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt @@ -15,39 +15,36 @@ */ package com.android.systemui.shared.regionsampling +import android.app.WallpaperColors +import android.app.WallpaperManager import android.graphics.Color +import android.graphics.Point import android.graphics.Rect +import android.graphics.RectF import android.view.View import androidx.annotation.VisibleForTesting import com.android.systemui.shared.navigationbar.RegionSamplingHelper -import com.android.systemui.shared.navigationbar.RegionSamplingHelper.SamplingCallback import java.io.PrintWriter import java.util.concurrent.Executor /** Class for instance of RegionSamplingHelper */ -open class RegionSampler( - sampledView: View?, +open class RegionSampler +@JvmOverloads +constructor( + val sampledView: View?, mainExecutor: Executor?, - bgExecutor: Executor?, - regionSamplingEnabled: Boolean, - updateFun: UpdateColorCallback -) { + val bgExecutor: Executor?, + val regionSamplingEnabled: Boolean, + val updateForegroundColor: UpdateColorCallback, + val wallpaperManager: WallpaperManager? = WallpaperManager.getInstance(sampledView?.context) +) : WallpaperManager.LocalWallpaperColorConsumer { private var regionDarkness = RegionDarkness.DEFAULT private var samplingBounds = Rect() private val tmpScreenLocation = IntArray(2) @VisibleForTesting var regionSampler: RegionSamplingHelper? = null private var lightForegroundColor = Color.WHITE private var darkForegroundColor = Color.BLACK - - @VisibleForTesting - open fun createRegionSamplingHelper( - sampledView: View, - callback: SamplingCallback, - mainExecutor: Executor?, - bgExecutor: Executor? - ): RegionSamplingHelper { - return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor) - } + private val displaySize = Point() /** * Sets the colors to be used for Dark and Light Foreground. @@ -73,7 +70,7 @@ open class RegionSampler( } } - private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness { + private fun getRegionDarkness(isRegionDark: Boolean): RegionDarkness { return if (isRegionDark) { RegionDarkness.DARK } else { @@ -87,12 +84,32 @@ open class RegionSampler( /** Start region sampler */ fun startRegionSampler() { - regionSampler?.start(samplingBounds) + if (!regionSamplingEnabled || sampledView == null) { + return + } + + val sampledRegion = calculateSampledRegion(sampledView) + val regions = ArrayList<RectF>() + val sampledRegionWithOffset = convertBounds(sampledRegion) + regions.add(sampledRegionWithOffset) + + wallpaperManager?.removeOnColorsChangedListener(this) + wallpaperManager?.addOnColorsChangedListener(this, regions) + + // TODO(b/265969235): conditionally set FLAG_LOCK or FLAG_SYSTEM once HS smartspace + // implemented + bgExecutor?.execute( + Runnable { + val initialSampling = + wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK) + onColorsChanged(sampledRegionWithOffset, initialSampling) + } + ) } /** Stop region sampler */ fun stopRegionSampler() { - regionSampler?.stop() + wallpaperManager?.removeOnColorsChangedListener(this) } /** Dump region sampler */ @@ -100,43 +117,66 @@ open class RegionSampler( regionSampler?.dump(pw) } + fun calculateSampledRegion(sampledView: View): RectF { + val screenLocation = tmpScreenLocation + /** + * 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. + */ + 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 RectF(samplingBounds) + } + + /** + * Convert the bounds of the region we want to sample from to fractional offsets because + * WallpaperManager requires the bounds to be between [0,1]. The wallpaper is treated as one + * continuous image, so if there are multiple screens, then each screen falls into a fractional + * range. For instance, 4 screens have the ranges [0, 0.25], [0,25, 0.5], [0.5, 0.75], [0.75, + * 1]. + */ + fun convertBounds(originalBounds: RectF): RectF { + + // TODO(b/265969235): GRAB # PAGES + CURRENT WALLPAPER PAGE # FROM LAUNCHER + // TODO(b/265968912): remove hard-coded value once LS wallpaper supported + val wallpaperPageNum = 0 + val numScreens = 1 + + val screenWidth = displaySize.x + // TODO: investigate small difference between this and the height reported in go/web-hv + val screenHeight = displaySize.y + + val newBounds = RectF() + // horizontal + newBounds.left = ((originalBounds.left / screenWidth) + wallpaperPageNum) / numScreens + newBounds.right = ((originalBounds.right / screenWidth) + wallpaperPageNum) / numScreens + // vertical + newBounds.top = originalBounds.top / screenHeight + newBounds.bottom = originalBounds.bottom / screenHeight + + return newBounds + } + init { - if (regionSamplingEnabled && sampledView != null) { - regionSampler = - createRegionSamplingHelper( - sampledView, - object : SamplingCallback { - override fun onRegionDarknessChanged(isRegionDark: Boolean) { - regionDarkness = convertToClockDarkness(isRegionDark) - updateFun() - } - /** - * 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 { - 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 - } - - override fun isSamplingEnabled(): Boolean { - return regionSamplingEnabled - } - }, - mainExecutor, - bgExecutor - ) - } - regionSampler?.setWindowVisible(true) + sampledView?.context?.display?.getSize(displaySize) + } + + override fun onColorsChanged(area: RectF?, colors: WallpaperColors?) { + // update text color when wallpaper color changes + regionDarkness = + getRegionDarkness( + (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) != + WallpaperColors.HINT_SUPPORTS_DARK_TEXT + ) + updateForegroundColor() } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java index 857cc4620ebd..5d036fbe5e52 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java @@ -35,6 +35,7 @@ import android.view.WindowManager.LayoutParams; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; +import androidx.annotation.BoolRes; import androidx.core.view.OneShotPreDrawListener; import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position; @@ -65,6 +66,8 @@ public class FloatingRotationButton implements RotationButton { private final int mTaskbarBottomMarginResource; @DimenRes private final int mButtonDiameterResource; + @BoolRes + private final int mFloatingRotationBtnPositionLeftResource; private AnimatedVectorDrawable mAnimatedDrawable; private boolean mIsShowing; @@ -84,7 +87,7 @@ public class FloatingRotationButton implements RotationButton { @LayoutRes int layout, @IdRes int keyButtonId, @DimenRes int minMargin, @DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin, @DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter, - @DimenRes int rippleMaxWidth) { + @DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) { mWindowManager = context.getSystemService(WindowManager.class); mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(layout, null); mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId); @@ -100,6 +103,7 @@ public class FloatingRotationButton implements RotationButton { mTaskbarLeftMarginResource = taskbarLeftMargin; mTaskbarBottomMarginResource = taskbarBottomMargin; mButtonDiameterResource = buttonDiameter; + mFloatingRotationBtnPositionLeftResource = floatingRotationBtnPositionLeftResource; updateDimensionResources(); } @@ -116,8 +120,11 @@ public class FloatingRotationButton implements RotationButton { int taskbarMarginBottom = res.getDimensionPixelSize(mTaskbarBottomMarginResource); + boolean floatingRotationButtonPositionLeft = + res.getBoolean(mFloatingRotationBtnPositionLeftResource); + mPositionCalculator = new FloatingRotationButtonPositionCalculator(defaultMargin, - taskbarMarginLeft, taskbarMarginBottom); + taskbarMarginLeft, taskbarMarginBottom, floatingRotationButtonPositionLeft); final int diameter = res.getDimensionPixelSize(mButtonDiameterResource); mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft, diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt index ec3c073dc68d..40e43a94ab17 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt @@ -10,7 +10,8 @@ import android.view.Surface class FloatingRotationButtonPositionCalculator( private val defaultMargin: Int, private val taskbarMarginLeft: Int, - private val taskbarMarginBottom: Int + private val taskbarMarginBottom: Int, + private val floatingRotationButtonPositionLeft: Boolean ) { fun calculatePosition( @@ -18,7 +19,6 @@ class FloatingRotationButtonPositionCalculator( taskbarVisible: Boolean, taskbarStashed: Boolean ): Position { - val isTaskbarSide = currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_90 val useTaskbarMargin = isTaskbarSide && taskbarVisible && !taskbarStashed @@ -55,11 +55,21 @@ class FloatingRotationButtonPositionCalculator( ) private fun resolveGravity(rotation: Int): Int = - when (rotation) { - Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.LEFT - Surface.ROTATION_90 -> Gravity.BOTTOM or Gravity.RIGHT - Surface.ROTATION_180 -> Gravity.TOP or Gravity.RIGHT - Surface.ROTATION_270 -> Gravity.TOP or Gravity.LEFT - else -> throw IllegalArgumentException("Invalid rotation $rotation") + if (floatingRotationButtonPositionLeft) { + when (rotation) { + Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.LEFT + Surface.ROTATION_90 -> Gravity.BOTTOM or Gravity.RIGHT + Surface.ROTATION_180 -> Gravity.TOP or Gravity.RIGHT + Surface.ROTATION_270 -> Gravity.TOP or Gravity.LEFT + else -> throw IllegalArgumentException("Invalid rotation $rotation") + } + } else { + when (rotation) { + Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.RIGHT + Surface.ROTATION_90 -> Gravity.TOP or Gravity.RIGHT + Surface.ROTATION_180 -> Gravity.TOP or Gravity.LEFT + Surface.ROTATION_270 -> Gravity.BOTTOM or Gravity.LEFT + else -> throw IllegalArgumentException("Invalid rotation $rotation") + } } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 766266d9cc94..037a71ea3eac 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -112,7 +112,8 @@ public class QuickStepContract { public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25; // Freeform windows are showing in desktop mode public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26; - + // Device dreaming state + public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27; @Retention(RetentionPolicy.SOURCE) @IntDef({SYSUI_STATE_SCREEN_PINNING, @@ -141,7 +142,8 @@ public class QuickStepContract { SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, SYSUI_STATE_IMMERSIVE_MODE, SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, - SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE + SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, + SYSUI_STATE_DEVICE_DREAMING }) public @interface SystemUiStateFlags {} @@ -179,6 +181,7 @@ public class QuickStepContract { str.add((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0 ? "vis_win_showing" : ""); str.add((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0 ? "freeform_active_in_desktop_mode" : ""); + str.add((flags & SYSUI_STATE_DEVICE_DREAMING) != 0 ? "device_dreaming" : ""); return str.toString(); } diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt index 05372fec7211..31234cf2ab53 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt @@ -35,7 +35,7 @@ object FlagsFactory { teamfood: Boolean = false ): UnreleasedFlag { val flag = UnreleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood) - FlagsFactory.checkForDupesAndAdd(flag) + checkForDupesAndAdd(flag) return flag } @@ -46,7 +46,7 @@ object FlagsFactory { teamfood: Boolean = false ): ReleasedFlag { val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood) - FlagsFactory.checkForDupesAndAdd(flag) + checkForDupesAndAdd(flag) return flag } @@ -65,7 +65,7 @@ object FlagsFactory { resourceId = resourceId, teamfood = teamfood ) - FlagsFactory.checkForDupesAndAdd(flag) + checkForDupesAndAdd(flag) return flag } @@ -77,18 +77,13 @@ object FlagsFactory { ): SysPropBooleanFlag { val flag = SysPropBooleanFlag(id = id, name = name, namespace = "systemui", default = default) - FlagsFactory.checkForDupesAndAdd(flag) + checkForDupesAndAdd(flag) return flag } private fun checkForDupesAndAdd(flag: Flag<*>) { if (flagMap.containsKey(flag.name)) { - throw IllegalArgumentException("Name {flag.name} is already registered") - } - flagMap.forEach { - if (it.value.id == flag.id) { - throw IllegalArgumentException("Name {flag.id} is already registered") - } + throw IllegalArgumentException("Name {$flag.name} is already registered") } flagMap[flag.name] = flag } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index a45ce422dca5..3a940e95bd12 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -24,6 +24,7 @@ import android.content.res.Resources import android.text.format.DateFormat import android.util.TypedValue import android.view.View +import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle @@ -47,18 +48,16 @@ import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import com.android.systemui.statusbar.policy.ConfigurationController -import java.io.PrintWriter -import java.util.Locale -import java.util.TimeZone -import java.util.concurrent.Executor -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch +import java.util.Locale +import java.util.TimeZone +import java.util.concurrent.Executor +import javax.inject.Inject /** * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by @@ -89,7 +88,11 @@ open class ClockEventController @Inject constructor( value.largeClock.logBuffer = largeLogBuffer value.initialize(resources, dozeAmount, 0f) - updateRegionSamplers(value) + + if (regionSamplingEnabled) { + clock?.smallClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener) + clock?.largeClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener) + } updateFontSizes() } } @@ -104,47 +107,87 @@ open class ClockEventController @Inject constructor( private var disposableHandle: DisposableHandle? = null private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING) - private fun updateColors() { + private val mLayoutChangedListener = object : View.OnLayoutChangeListener { + private var currentSmallClockView: View? = null + private var currentLargeClockView: View? = null + private var currentSmallClockLocation = IntArray(2) + private var currentLargeClockLocation = IntArray(2) + + override fun onLayoutChange( + view: View?, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int + ) { + val parent = (view?.parent) as FrameLayout + + // don't pass in negative bounds when clocks are in transition state + if (view.locationOnScreen[0] < 0 || view.locationOnScreen[1] < 0) { + return + } - if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) { - val wallpaperManager = WallpaperManager.getInstance(context) - if (!wallpaperManager.lockScreenWallpaperExists()) { - smallClockIsDark = smallRegionSampler!!.currentRegionDarkness().isDark - largeClockIsDark = largeRegionSampler!!.currentRegionDarkness().isDark + // SMALL CLOCK + if (parent.id == R.id.lockscreen_clock_view) { + // view bounds have changed due to clock size changing (i.e. different character widths) + // AND/OR the view has been translated when transitioning between small and large clock + if (view != currentSmallClockView || + !view.locationOnScreen.contentEquals(currentSmallClockLocation)) { + currentSmallClockView = view + currentSmallClockLocation = view.locationOnScreen + updateRegionSampler(view) } - } else { - val isLightTheme = TypedValue() - context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true) - smallClockIsDark = isLightTheme.data == 0 - largeClockIsDark = isLightTheme.data == 0 } - - clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark) - clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark) + // LARGE CLOCK + else if (parent.id == R.id.lockscreen_clock_view_large) { + if (view != currentLargeClockView || + !view.locationOnScreen.contentEquals(currentLargeClockLocation)) { + currentLargeClockView = view + currentLargeClockLocation = view.locationOnScreen + updateRegionSampler(view) + } + } + } } - private fun updateRegionSamplers(currentClock: ClockController?) { - smallRegionSampler?.stopRegionSampler() - largeRegionSampler?.stopRegionSampler() + private fun updateColors() { + val wallpaperManager = WallpaperManager.getInstance(context) + if (regionSamplingEnabled && !wallpaperManager.lockScreenWallpaperExists()) { + if (regionSampler != null) { + if (regionSampler?.sampledView == clock?.smallClock?.view) { + smallClockIsDark = regionSampler!!.currentRegionDarkness().isDark + clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark) + return + } else if (regionSampler?.sampledView == clock?.largeClock?.view) { + largeClockIsDark = regionSampler!!.currentRegionDarkness().isDark + clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark) + return + } + } + } - smallRegionSampler = createRegionSampler( - currentClock?.smallClock?.view, - mainExecutor, - bgExecutor, - regionSamplingEnabled, - ::updateColors - ) + val isLightTheme = TypedValue() + context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true) + smallClockIsDark = isLightTheme.data == 0 + largeClockIsDark = isLightTheme.data == 0 - largeRegionSampler = createRegionSampler( - currentClock?.largeClock?.view, - mainExecutor, - bgExecutor, - regionSamplingEnabled, - ::updateColors - ) + clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark) + clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark) + } - smallRegionSampler!!.startRegionSampler() - largeRegionSampler!!.startRegionSampler() + private fun updateRegionSampler(sampledRegion: View) { + regionSampler?.stopRegionSampler() + regionSampler = createRegionSampler( + sampledRegion, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + ::updateColors + )?.apply { startRegionSampler() } updateColors() } @@ -155,7 +198,7 @@ open class ClockEventController @Inject constructor( bgExecutor: Executor?, regionSamplingEnabled: Boolean, updateColors: () -> Unit - ): RegionSampler { + ): RegionSampler? { return RegionSampler( sampledView, mainExecutor, @@ -164,8 +207,7 @@ open class ClockEventController @Inject constructor( updateColors) } - var smallRegionSampler: RegionSampler? = null - var largeRegionSampler: RegionSampler? = null + var regionSampler: RegionSampler? = null private var smallClockIsDark = true private var largeClockIsDark = true @@ -232,8 +274,6 @@ open class ClockEventController @Inject constructor( configurationController.addCallback(configListener) batteryController.addCallback(batteryCallback) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) - smallRegionSampler?.startRegionSampler() - largeRegionSampler?.startRegionSampler() disposableHandle = parent.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) @@ -258,8 +298,7 @@ open class ClockEventController @Inject constructor( configurationController.removeCallback(configListener) batteryController.removeCallback(batteryCallback) keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) - smallRegionSampler?.stopRegionSampler() - largeRegionSampler?.stopRegionSampler() + regionSampler?.stopRegionSampler() } private fun updateFontSizes() { @@ -269,16 +308,6 @@ open class ClockEventController @Inject constructor( resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()) } - /** - * Dump information for debugging - */ - fun dump(pw: PrintWriter) { - pw.println(this) - clock?.dump(pw) - smallRegionSampler?.dump(pw) - largeRegionSampler?.dump(pw) - } - @VisibleForTesting internal fun listenForDozeAmount(scope: CoroutineScope): Job { return scope.launch { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 7da27b1d6898..baaef1983e9c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -103,6 +103,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey @Override public void reset() { + super.reset(); // start fresh mDismissing = false; mView.resetPasswordText(false /* animate */, false /* announce */); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 71cd18dd9703..86840194a79b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -45,6 +45,7 @@ import com.android.systemui.plugins.log.LogBuffer; import com.android.systemui.plugins.log.LogLevel; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.clocks.ClockRegistry; +import com.android.systemui.shared.regionsampling.RegionSampler; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -88,7 +89,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final ClockRegistry.ClockChangeListener mClockChangedListener; private ViewGroup mStatusArea; - // If set will replace keyguard_slice_view + + // If the SMARTSPACE flag is set, keyguard_slice_view is replaced by the following views. + private View mWeatherView; private View mSmartspaceView; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @@ -192,10 +195,17 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS if (mSmartspaceController.isEnabled()) { View ksv = mView.findViewById(R.id.keyguard_slice_view); - int ksvIndex = mStatusArea.indexOfChild(ksv); + int viewIndex = mStatusArea.indexOfChild(ksv); ksv.setVisibility(View.GONE); - addSmartspaceView(ksvIndex); + // TODO(b/261757708): add content observer for the Settings toggle and add/remove + // weather according to the Settings. + if (mSmartspaceController.isDateWeatherDecoupled()) { + addWeatherView(viewIndex); + viewIndex += 1; + } + + addSmartspaceView(viewIndex); } mSecureSettings.registerContentObserverForUser( @@ -237,6 +247,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } } + private void addWeatherView(int index) { + mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + MATCH_PARENT, WRAP_CONTENT); + mStatusArea.addView(mWeatherView, index, lp); + int startPadding = getContext().getResources().getDimensionPixelSize( + R.dimen.below_clock_padding_start); + int endPadding = getContext().getResources().getDimensionPixelSize( + R.dimen.below_clock_padding_end); + mWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0); + } + private void addSmartspaceView(int index) { mSmartspaceView = mSmartspaceController.buildAndConnectView(mView); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( @@ -424,6 +446,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS if (clock != null) { clock.dump(pw); } + final RegionSampler regionSampler = mClockEventController.getRegionSampler(); + if (regionSampler != null) { + regionSampler.dump(pw); + } } /** Gets the animations for the current clock. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 02776a295359..ec8fa921c6fa 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -15,8 +15,6 @@ */ package com.android.keyguard; -import static android.view.Display.DEFAULT_DISPLAY; - import android.app.Presentation; import android.content.Context; import android.graphics.Color; @@ -37,9 +35,11 @@ import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; +import com.android.systemui.settings.DisplayTracker; import java.util.concurrent.Executor; @@ -53,6 +53,7 @@ public class KeyguardDisplayManager { private MediaRouter mMediaRouter = null; private final DisplayManager mDisplayService; + private final DisplayTracker mDisplayTracker; private final Lazy<NavigationBarController> mNavigationBarControllerLazy; private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; private final Context mContext; @@ -62,46 +63,43 @@ public class KeyguardDisplayManager { private final SparseArray<Presentation> mPresentations = new SparseArray<>(); - private final DisplayManager.DisplayListener mDisplayListener = - new DisplayManager.DisplayListener() { - - @Override - public void onDisplayAdded(int displayId) { - Trace.beginSection( - "KeyguardDisplayManager#onDisplayAdded(displayId=" + displayId + ")"); - final Display display = mDisplayService.getDisplay(displayId); - if (mShowing) { - updateNavigationBarVisibility(displayId, false /* navBarVisible */); - showPresentation(display); - } - Trace.endSection(); - } - - @Override - public void onDisplayChanged(int displayId) { - - } + private final DisplayTracker.Callback mDisplayCallback = + new DisplayTracker.Callback() { + @Override + public void onDisplayAdded(int displayId) { + Trace.beginSection( + "KeyguardDisplayManager#onDisplayAdded(displayId=" + displayId + ")"); + final Display display = mDisplayService.getDisplay(displayId); + if (mShowing) { + updateNavigationBarVisibility(displayId, false /* navBarVisible */); + showPresentation(display); + } + Trace.endSection(); + } - @Override - public void onDisplayRemoved(int displayId) { - Trace.beginSection( - "KeyguardDisplayManager#onDisplayRemoved(displayId=" + displayId + ")"); - hidePresentation(displayId); - Trace.endSection(); - } - }; + @Override + public void onDisplayRemoved(int displayId) { + Trace.beginSection( + "KeyguardDisplayManager#onDisplayRemoved(displayId=" + displayId + ")"); + hidePresentation(displayId); + Trace.endSection(); + } + }; @Inject public KeyguardDisplayManager(Context context, Lazy<NavigationBarController> navigationBarControllerLazy, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, + DisplayTracker displayTracker, + @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor) { mContext = context; mNavigationBarControllerLazy = navigationBarControllerLazy; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class)); mDisplayService = mContext.getSystemService(DisplayManager.class); - mDisplayService.registerDisplayListener(mDisplayListener, null /* handler */); + mDisplayTracker = displayTracker; + mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor); } private boolean isKeyguardShowable(Display display) { @@ -109,7 +107,7 @@ public class KeyguardDisplayManager { if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display"); return false; } - if (display.getDisplayId() == DEFAULT_DISPLAY) { + if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) { if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display"); return false; } @@ -224,7 +222,7 @@ public class KeyguardDisplayManager { protected boolean updateDisplays(boolean showing) { boolean changed = false; if (showing) { - final Display[] displays = mDisplayService.getDisplays(); + final Display[] displays = mDisplayTracker.getAllDisplays(); for (Display display : displays) { int displayId = display.getDisplayId(); updateNavigationBarVisibility(displayId, false /* navBarVisible */); @@ -247,7 +245,7 @@ public class KeyguardDisplayManager { // term solution in R. private void updateNavigationBarVisibility(int displayId, boolean navBarVisible) { // Leave this task to {@link StatusBarKeyguardViewManager} - if (displayId == DEFAULT_DISPLAY) return; + if (displayId == mDisplayTracker.getDefaultDisplayId()) return; NavigationBarView navBarView = mNavigationBarControllerLazy.get() .getNavigationBarView(displayId); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index d1c9a3090860..b143c5b90373 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -121,6 +121,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> @Override public void reset() { + mMessageAreaController.setMessage("", false); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index e4f85db3971e..9f07a20ce812 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -92,6 +92,7 @@ import com.android.internal.util.UserIcons; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.settingslib.Utils; +import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; @@ -235,14 +236,6 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } updateChildren(0 /* translationY */, 1f /* alpha */); } - - private void updateChildren(int translationY, float alpha) { - for (int i = 0; i < KeyguardSecurityContainer.this.getChildCount(); ++i) { - View child = KeyguardSecurityContainer.this.getChildAt(i); - child.setTranslationY(translationY); - child.setAlpha(alpha); - } - } }; private final OnBackAnimationCallback mBackCallback = new OnBackAnimationCallback() { @@ -594,6 +587,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { * This will run when the bouncer shows in all cases except when the user drags the bouncer up. */ public void startAppearAnimation(SecurityMode securityMode) { + updateChildren(0 /* translationY */, 1f /* alpha */); mViewMode.startAppearAnimation(securityMode); } @@ -777,6 +771,14 @@ public class KeyguardSecurityContainer extends ConstraintLayout { setScaleY(scale); } + private void updateChildren(int translationY, float alpha) { + for (int i = 0; i < getChildCount(); ++i) { + View child = getChildAt(i); + child.setTranslationY(translationY); + child.setAlpha(alpha); + } + } + /** * Enscapsulates the differences between bouncer modes for the container. */ @@ -998,8 +1000,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout { private Drawable findUserIcon(int userId) { Bitmap userIcon = UserManager.get(mView.getContext()).getUserIcon(userId); if (userIcon != null) { - return new BitmapDrawable(userIcon); + return CircleFramedDrawable.getInstance(mView.getContext(), + userIcon); } + return UserIcons.getDefaultUserIcon(mResources, userId, false); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java index 0b2b1214e599..e3de8c7e5671 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java @@ -17,7 +17,6 @@ package com.android.keyguard; import static android.app.slice.Slice.HINT_LIST_ITEM; -import static android.view.Display.DEFAULT_DISPLAY; import android.app.PendingIntent; import android.net.Uri; @@ -43,6 +42,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.ViewController; @@ -64,6 +64,7 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie private final ConfigurationController mConfigurationController; private final TunerService mTunerService; private final DumpManager mDumpManager; + private final DisplayTracker mDisplayTracker; private int mDisplayId; private LiveData<Slice> mLiveData; private Uri mKeyguardSliceUri; @@ -108,12 +109,14 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie ActivityStarter activityStarter, ConfigurationController configurationController, TunerService tunerService, - DumpManager dumpManager) { + DumpManager dumpManager, + DisplayTracker displayTracker) { super(keyguardSliceView); mActivityStarter = activityStarter; mConfigurationController = configurationController; mTunerService = tunerService; mDumpManager = dumpManager; + mDisplayTracker = displayTracker; } @Override @@ -124,7 +127,7 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie } mTunerService.addTunable(mTunable, Settings.Secure.KEYGUARD_SLICE_URI); // Make sure we always have the most current slice - if (mDisplayId == DEFAULT_DISPLAY && mLiveData != null) { + if (mDisplayId == mDisplayTracker.getDefaultDisplayId() && mLiveData != null) { mLiveData.observeForever(mObserver); } mConfigurationController.addCallback(mConfigurationListener); @@ -137,7 +140,7 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie @Override protected void onViewDetached() { // TODO(b/117344873) Remove below work around after this issue be fixed. - if (mDisplayId == DEFAULT_DISPLAY) { + if (mDisplayId == mDisplayTracker.getDefaultDisplayId()) { mLiveData.removeObserver(mObserver); } mTunerService.removeTunable(mTunable); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index a9695dd62ba4..54886c3e2080 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -26,7 +26,6 @@ import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_N import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED; import static android.hardware.biometrics.BiometricConstants.LockoutMode; -import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; import static android.hardware.biometrics.BiometricSourceType.FACE; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; @@ -352,7 +351,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final Executor mBackgroundExecutor; private final SensorPrivacyManager mSensorPrivacyManager; private final ActiveUnlockConfig mActiveUnlockConfig; - private final PowerManager mPowerManager; private final IDreamManager mDreamManager; private final TelephonyManager mTelephonyManager; @Nullable @@ -360,7 +358,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Nullable private final FaceManager mFaceManager; private final LockPatternUtils mLockPatternUtils; - private final boolean mWakeOnFingerprintAcquiredStart; @VisibleForTesting @DevicePostureController.DevicePostureInt protected int mConfigFaceAuthSupportedPosture; @@ -884,11 +881,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleFingerprintAcquired( @BiometricFingerprintConstants.FingerprintAcquired int acquireInfo) { Assert.isMainThread(); - if (mWakeOnFingerprintAcquiredStart && acquireInfo == FINGERPRINT_ACQUIRED_START) { - mPowerManager.wakeUp( - SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC, - "com.android.systemui.keyguard:FINGERPRINT_ACQUIRED_START"); - } for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -1537,6 +1529,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting void setAssistantVisible(boolean assistantVisible) { mAssistantVisible = assistantVisible; + mLogger.logAssistantVisible(mAssistantVisible); updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED); if (mAssistantVisible) { @@ -1944,6 +1937,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } mGoingToSleep = true; + // Resetting assistant visibility state as the device is going to sleep now. + // TaskStackChangeListener gets triggered a little late when we transition to AoD, + // which results in face auth running once on AoD. + mAssistantVisible = false; + mLogger.d("Started going to sleep, mGoingToSleep=true, mAssistantVisible=false"); updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_GOING_TO_SLEEP); } @@ -2049,7 +2047,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab UiEventLogger uiEventLogger, // This has to be a provider because SessionTracker depends on KeyguardUpdateMonitor :( Provider<SessionTracker> sessionTrackerProvider, - PowerManager powerManager, TrustManager trustManager, SubscriptionManager subscriptionManager, UserManager userManager, @@ -2086,7 +2083,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger = logger; mUiEventLogger = uiEventLogger; mSessionTrackerProvider = sessionTrackerProvider; - mPowerManager = powerManager; mTrustManager = trustManager; mUserManager = userManager; mDreamManager = dreamManager; @@ -2097,8 +2093,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFpm = fingerprintManager; mFaceManager = faceManager; mActiveUnlockConfig.setKeyguardUpdateMonitor(this); - mWakeOnFingerprintAcquiredStart = context.getResources() - .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start); mFaceAcquiredInfoIgnoreList = Arrays.stream( mContext.getResources().getIntArray( R.array.config_face_acquire_device_entry_ignorelist)) @@ -2678,7 +2672,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean shouldListenForFaceAssistant() { BiometricAuthenticated face = mUserFaceAuthenticated.get(getCurrentUser()); - return mAssistantVisible && mKeyguardOccluded + return mAssistantVisible + // There can be intermediate states where mKeyguardShowing is false but + // mKeyguardOccluded is true, we don't want to run face auth in such a scenario. + && (mKeyguardShowing && mKeyguardOccluded) && !(face != null && face.mAuthenticated) && !mUserHasTrust.get(getCurrentUser(), false); } @@ -3338,7 +3335,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * Handle {@link #MSG_KEYGUARD_RESET} */ - private void handleKeyguardReset() { + @VisibleForTesting + protected void handleKeyguardReset() { mLogger.d("handleKeyguardReset"); updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_KEYGUARD_RESET); @@ -3679,6 +3677,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (info == null) { return; } + mLogger.logTaskStackChangedForAssistant(info.visible); mHandler.sendMessage(mHandler.obtainMessage(MSG_ASSISTANT_STACK_CHANGED, info.visible)); } catch (RemoteException e) { @@ -3877,7 +3876,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" getUserHasTrust()=" + getUserHasTrust(getCurrentUser())); pw.println(" getUserUnlockedWithBiometric()=" + getUserUnlockedWithBiometric(getCurrentUser())); - pw.println(" mWakeOnFingerprintAcquiredStart=" + mWakeOnFingerprintAcquiredStart); pw.println(" SIM States:"); for (SimData data : mSimDatas.values()) { pw.println(" " + data.toString()); diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 1322f16a5a59..8071a5de1ce9 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -429,6 +429,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState)); pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount); pw.println(" mSensorTouchLocation: " + mSensorTouchLocation); + pw.println(" mDefaultPaddingPx: " + mDefaultPaddingPx); if (mView != null) { mView.dump(pw, args); diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java index b1597144d237..35cae099e44e 100644 --- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java +++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java @@ -47,7 +47,6 @@ import com.android.settingslib.Utils; import com.android.systemui.R; import java.util.ArrayList; -import java.util.Stack; /** * A View similar to a textView which contains password text and can animate when the text is @@ -92,7 +91,6 @@ public class PasswordTextView extends View { private final int mGravity; private ArrayList<CharState> mTextChars = new ArrayList<>(); private String mText = ""; - private Stack<CharState> mCharPool = new Stack<>(); private int mDotSize; private PowerManager mPM; private int mCharPadding; @@ -310,13 +308,7 @@ public class PasswordTextView extends View { } private CharState obtainCharState(char c) { - CharState charState; - if(mCharPool.isEmpty()) { - charState = new CharState(); - } else { - charState = mCharPool.pop(); - charState.reset(); - } + CharState charState = new CharState(); charState.whichChar = c; return charState; } @@ -343,8 +335,6 @@ public class PasswordTextView extends View { maxDelay = Math.min(maxDelay, RESET_MAX_DELAY) + DISAPPEAR_DURATION; charState.startRemoveAnimation(startDelay, maxDelay); charState.removeDotSwapCallbacks(); - } else { - mCharPool.push(charState); } } if (!animated) { @@ -421,8 +411,6 @@ public class PasswordTextView extends View { public void onAnimationEnd(Animator animation) { if (!mCancelled) { mTextChars.remove(CharState.this); - mCharPool.push(CharState.this); - reset(); cancelAnimator(textTranslateAnimator); textTranslateAnimator = null; } @@ -518,21 +506,6 @@ public class PasswordTextView extends View { } }; - void reset() { - whichChar = 0; - currentTextSizeFactor = 0.0f; - currentDotSizeFactor = 0.0f; - currentWidthFactor = 0.0f; - cancelAnimator(textAnimator); - textAnimator = null; - cancelAnimator(dotAnimator); - dotAnimator = null; - cancelAnimator(widthAnimator); - widthAnimator = null; - currentTextTranslationY = 1.0f; - removeDotSwapCallbacks(); - } - void startRemoveAnimation(long startDelay, long widthDelay) { boolean dotNeedsAnimation = (currentDotSizeFactor > 0.0f && dotAnimator == null) || (dotAnimator != null && dotAnimationIsGrowing); diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java index 0cbf8bc07edf..5ad21df1e652 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java @@ -20,13 +20,13 @@ import android.view.ViewGroup; import com.android.keyguard.KeyguardHostViewController; import com.android.systemui.dagger.qualifiers.RootView; -import com.android.systemui.statusbar.phone.KeyguardBouncer; +import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import dagger.BindsInstance; import dagger.Subcomponent; /** - * Dagger Subcomponent for the {@link KeyguardBouncer}. + * Dagger Subcomponent for the {@link PrimaryBouncerInteractor}. */ @Subcomponent(modules = {KeyguardBouncerModule.class}) @KeyguardBouncerScope diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java index ef067b89f9c7..cb7a0a9b1653 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java @@ -29,7 +29,7 @@ import com.android.keyguard.KeyguardSecurityViewFlipper; import com.android.systemui.R; import com.android.systemui.biometrics.SideFpsController; import com.android.systemui.dagger.qualifiers.RootView; -import com.android.systemui.statusbar.phone.KeyguardBouncer; +import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import java.util.Optional; @@ -39,7 +39,7 @@ import dagger.Module; import dagger.Provides; /** - * Module to create and access view related to the {@link KeyguardBouncer}. + * Module to create and access view related to the {@link PrimaryBouncerInteractor}. */ @Module public interface KeyguardBouncerModule { diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 5b4245595be9..201a1d950748 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -431,4 +431,20 @@ class KeyguardUpdateMonitorLogger @Inject constructor( str1 = PowerManager.wakeReasonToString(pmWakeReason) }, { "Skip updating face listening state on wakeup from $str1"}) } + + fun logTaskStackChangedForAssistant(assistantVisible: Boolean) { + logBuffer.log(TAG, VERBOSE, { + bool1 = assistantVisible + }, { + "TaskStackChanged for ACTIVITY_TYPE_ASSISTANT, assistant visible: $bool1" + }) + } + + fun logAssistantVisible(assistantVisible: Boolean) { + logBuffer.log(TAG, VERBOSE, { + bool1 = assistantVisible + }, { + "Updating mAssistantVisible to new value: $bool1" + }) + } } diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt index 9ac45b3c77cc..227f0ace33dd 100644 --- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt +++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt @@ -34,7 +34,7 @@ class ChooserSelector @Inject constructor( override fun start() { coroutineScope.launch { val listener = FlagListenable.Listener { event -> - if (event.flagId == Flags.CHOOSER_UNBUNDLED.id) { + if (event.flagName == Flags.CHOOSER_UNBUNDLED.name) { launch { updateUnbundledChooserEnabled() } event.requestNoRestart() } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index e6f559b3da5f..71f98fa3613a 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -36,10 +36,10 @@ import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.hardware.display.DisplayManager; import android.hardware.graphics.common.AlphaInterpretation; import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.SystemProperties; import android.os.Trace; import android.provider.Settings.Secure; @@ -76,6 +76,7 @@ import com.android.systemui.decor.PrivacyDotDecorProviderFactory; import com.android.systemui.decor.RoundedCornerDecorProviderFactory; import com.android.systemui.decor.RoundedCornerResDelegate; import com.android.systemui.qs.SettingObserver; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.tuner.TunerService; @@ -120,7 +121,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { R.id.display_cutout_bottom }; - private DisplayManager mDisplayManager; + private DisplayTracker mDisplayTracker; @VisibleForTesting protected boolean mIsRegistered; private final Context mContext; @@ -128,7 +129,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { private final TunerService mTunerService; private final SecureSettings mSecureSettings; @VisibleForTesting - DisplayManager.DisplayListener mDisplayListener; + DisplayTracker.Callback mDisplayListener; private CameraAvailabilityListener mCameraListener; private final UserTracker mUserTracker; private final PrivacyDotViewController mDotViewController; @@ -302,6 +303,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { SecureSettings secureSettings, TunerService tunerService, UserTracker userTracker, + DisplayTracker displayTracker, PrivacyDotViewController dotViewController, ThreadFactory threadFactory, PrivacyDotDecorProviderFactory dotFactory, @@ -311,6 +313,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mSecureSettings = secureSettings; mTunerService = tunerService; mUserTracker = userTracker; + mDisplayTracker = displayTracker; mDotViewController = dotViewController; mThreadFactory = threadFactory; mDotFactory = dotFactory; @@ -376,7 +379,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { private void startOnScreenDecorationsThread() { Trace.beginSection("ScreenDecorations#startOnScreenDecorationsThread"); mWindowManager = mContext.getSystemService(WindowManager.class); - mDisplayManager = mContext.getSystemService(DisplayManager.class); mContext.getDisplay().getDisplayInfo(mDisplayInfo); mRotation = mDisplayInfo.rotation; mDisplayMode = mDisplayInfo.getMode(); @@ -393,17 +395,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { setupDecorations(); setupCameraListener(); - mDisplayListener = new DisplayManager.DisplayListener() { - @Override - public void onDisplayAdded(int displayId) { - // do nothing - } - - @Override - public void onDisplayRemoved(int displayId) { - // do nothing - } - + mDisplayListener = new DisplayTracker.Callback() { @Override public void onDisplayChanged(int displayId) { mContext.getDisplay().getDisplayInfo(mDisplayInfo); @@ -474,8 +466,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { } } }; - - mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); + mDisplayTracker.addDisplayChangeCallback(mDisplayListener, new HandlerExecutor(mHandler)); updateConfiguration(); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index 4c9259889a37..ddac25b67766 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -36,7 +36,6 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; -import android.view.Display; import android.view.IWindowManager; import android.view.InputDevice; import android.view.KeyCharacterMap; @@ -50,6 +49,7 @@ import com.android.internal.util.ScreenshotHelper; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.recents.Recents; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; @@ -180,6 +180,7 @@ public class SystemActions implements CoreStartable { private final Context mContext; private final UserTracker mUserTracker; private final Optional<Recents> mRecentsOptional; + private final DisplayTracker mDisplayTracker; private Locale mLocale; private final AccessibilityManager mA11yManager; private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; @@ -194,11 +195,13 @@ public class SystemActions implements CoreStartable { NotificationShadeWindowController notificationShadeController, ShadeController shadeController, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, - Optional<Recents> recentsOptional) { + Optional<Recents> recentsOptional, + DisplayTracker displayTracker) { mContext = context; mUserTracker = userTracker; mShadeController = shadeController; mRecentsOptional = recentsOptional; + mDisplayTracker = displayTracker; mReceiver = new SystemActionsBroadcastReceiver(); mLocale = mContext.getResources().getConfiguration().getLocales().get(0); mA11yManager = (AccessibilityManager) mContext.getSystemService( @@ -207,7 +210,8 @@ public class SystemActions implements CoreStartable { // Saving in instance variable since to prevent GC since // NotificationShadeWindowController.registerCallback() only keeps weak references. mNotificationShadeCallback = - (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded) -> + (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded, + isDreaming) -> registerOrUnregisterDismissNotificationShadeAction(); mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; } @@ -522,7 +526,7 @@ public class SystemActions implements CoreStartable { private void handleAccessibilityButton() { AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked( - Display.DEFAULT_DISPLAY); + mDisplayTracker.getDefaultDisplayId()); } private void handleAccessibilityButtonChooser() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index ab11fcea7b28..b3574bfc83cd 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -39,6 +39,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.statusbar.CommandQueue; import java.io.PrintWriter; @@ -62,6 +63,7 @@ public class WindowMagnification implements CoreStartable, WindowMagnifierCallba private final AccessibilityManager mAccessibilityManager; private final CommandQueue mCommandQueue; private final OverviewProxyService mOverviewProxyService; + private final DisplayTracker mDisplayTracker; private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl; private SysUiState mSysUiState; @@ -102,7 +104,8 @@ public class WindowMagnification implements CoreStartable, WindowMagnifierCallba @Inject public WindowMagnification(Context context, @Main Handler mainHandler, CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, - SysUiState sysUiState, OverviewProxyService overviewProxyService) { + SysUiState sysUiState, OverviewProxyService overviewProxyService, + DisplayTracker displayTracker) { mContext = context; mHandler = mainHandler; mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); @@ -110,6 +113,7 @@ public class WindowMagnification implements CoreStartable, WindowMagnifierCallba mModeSwitchesController = modeSwitchesController; mSysUiState = sysUiState; mOverviewProxyService = overviewProxyService; + mDisplayTracker = displayTracker; mMagnificationControllerSupplier = new ControllerSupplier(context, mHandler, this, context.getSystemService(DisplayManager.class), sysUiState); } @@ -130,14 +134,14 @@ public class WindowMagnification implements CoreStartable, WindowMagnifierCallba private void updateSysUiStateFlag() { //TODO(b/187510533): support multi-display once SysuiState supports it. final WindowMagnificationController controller = - mMagnificationControllerSupplier.valueAt(Display.DEFAULT_DISPLAY); + mMagnificationControllerSupplier.valueAt(mDisplayTracker.getDefaultDisplayId()); if (controller != null) { controller.updateSysUIStateFlag(); } else { // The instance is initialized when there is an IPC request. Considering // self-crash cases, we need to reset the flag in such situation. mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false) - .commitUpdate(Display.DEFAULT_DISPLAY); + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index da2e28c5522f..1ea173ea9f48 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -1,7 +1,5 @@ package com.android.systemui.assist; -import static android.view.Display.DEFAULT_DISPLAY; - import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED; @@ -35,6 +33,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -122,6 +121,7 @@ public class AssistManager { protected final Lazy<SysUiState> mSysUiState; protected final AssistLogger mAssistLogger; private final UserTracker mUserTracker; + private final DisplayTracker mDisplayTracker; private final SecureSettings mSecureSettings; private final DeviceProvisionedController mDeviceProvisionedController; @@ -141,6 +141,7 @@ public class AssistManager { AssistLogger assistLogger, @Main Handler uiHandler, UserTracker userTracker, + DisplayTracker displayTracker, SecureSettings secureSettings) { mContext = context; mDeviceProvisionedController = controller; @@ -150,6 +151,7 @@ public class AssistManager { mPhoneStateMonitor = phoneStateMonitor; mAssistLogger = assistLogger; mUserTracker = userTracker; + mDisplayTracker = displayTracker; mSecureSettings = secureSettings; registerVoiceInteractionSessionListener(); @@ -214,7 +216,7 @@ public class AssistManager { .setFlag( SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED, hints.getBoolean(CONSTRAINED_KEY, false)) - .commitUpdate(DEFAULT_DISPLAY); + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 68e1f72d042a..febf75e90a14 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -847,7 +847,7 @@ public class AuthContainerView extends LinearLayout final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL, + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, windowFlags, PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 4b57d455a137..53ab6d63c62d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -55,11 +55,11 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at private val fadeDuration = 83L private val retractDuration = 400L private var alphaInDuration: Long = 0 - private var unlockedRippleInProgress: Boolean = false private val dwellShader = DwellRippleShader() private val dwellPaint = Paint() private val rippleShader = RippleShader() private val ripplePaint = Paint() + private var unlockedRippleAnimator: AnimatorSet? = null private var fadeDwellAnimator: Animator? = null private var retractDwellAnimator: Animator? = null private var dwellPulseOutAnimator: Animator? = null @@ -86,7 +86,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at init { rippleShader.color = 0xffffffff.toInt() // default color - rippleShader.progress = 0f + rippleShader.rawProgress = 0f rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH ripplePaint.shader = rippleShader @@ -205,7 +205,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at * Plays a ripple animation that grows to the dwellRadius with distortion. */ fun startDwellRipple(isDozing: Boolean) { - if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) { + if (unlockedRippleAnimator?.isRunning == true || dwellPulseOutAnimator?.isRunning == true) { return } @@ -262,16 +262,14 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at * Ripple that bursts outwards from the position of the sensor to the edges of the screen */ fun startUnlockedRipple(onAnimationEnd: Runnable?) { - if (unlockedRippleInProgress) { - return // Ignore if ripple effect is already playing - } + unlockedRippleAnimator?.cancel() val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { interpolator = Interpolators.LINEAR_OUT_SLOW_IN duration = AuthRippleController.RIPPLE_ANIMATION_DURATION addUpdateListener { animator -> val now = animator.currentPlayTime - rippleShader.progress = animator.animatedValue as Float + rippleShader.rawProgress = animator.animatedValue as Float rippleShader.time = now.toFloat() invalidate() @@ -289,14 +287,13 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } } - val animatorSet = AnimatorSet().apply { + unlockedRippleAnimator = AnimatorSet().apply { playTogether( rippleAnimator, alphaInAnimator ) addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { - unlockedRippleInProgress = true rippleShader.rippleFill = false drawRipple = true visibility = VISIBLE @@ -304,13 +301,13 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at override fun onAnimationEnd(animation: Animator?) { onAnimationEnd?.run() - unlockedRippleInProgress = false drawRipple = false visibility = GONE + unlockedRippleAnimator = null } }) } - animatorSet.start() + unlockedRippleAnimator?.start() } fun resetRippleAlpha() { @@ -345,7 +342,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at override fun onDraw(canvas: Canvas?) { // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover // the active effect area. Values here should be kept in sync with the - // animation implementation in the ripple shader. + // animation implementation in the ripple shader. (Twice bigger) if (drawDwell) { val maskRadius = (1 - (1 - dwellShader.progress) * (1 - dwellShader.progress) * (1 - dwellShader.progress)) * dwellRadius * 2f @@ -354,10 +351,8 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } if (drawRipple) { - val mask = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * - (1 - rippleShader.progress)) * radius * 2f canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(), - mask, ripplePaint) + rippleShader.currentWidth, ripplePaint) } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 6f594d5eb0e2..c799e91ad36b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -54,12 +54,18 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.recents.OverviewProxyService import com.android.systemui.util.concurrency.DelayableExecutor import java.io.PrintWriter import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch private const val TAG = "SideFpsController" @@ -79,6 +85,9 @@ constructor( displayManager: DisplayManager, @Main private val mainExecutor: DelayableExecutor, @Main private val handler: Handler, + private val alternateBouncerInteractor: AlternateBouncerInteractor, + @Application private val scope: CoroutineScope, + private val featureFlags: FeatureFlags, dumpManager: DumpManager ) : Dumpable { val requests: HashSet<SideFpsUiRequestSource> = HashSet() @@ -168,9 +177,26 @@ constructor( } ) overviewProxyService.addCallback(overviewProxyListener) + listenForAlternateBouncerVisibility() + dumpManager.registerDumpable(this) } + private fun listenForAlternateBouncerVisibility() { + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true) + if (featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)) { + scope.launch { + alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> + if (isVisible) { + show(SideFpsUiRequestSource.ALTERNATE_BOUNCER) + } else { + hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER) + } + } + } + } + } + /** Shows the side fps overlay if not already shown. */ fun show(request: SideFpsUiRequestSource) { requests.add(request) @@ -423,4 +449,5 @@ enum class SideFpsUiRequestSource { AUTO_SHOW, /** Pin, pattern or password bouncer */ PRIMARY_BOUNCER, + ALTERNATE_BOUNCER } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt index 63a1b76b8103..addbee954fea 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt @@ -32,9 +32,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor -import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionListener @@ -84,7 +82,6 @@ constructor( ) { private val useExpandedOverlay: Boolean = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) - private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER) private val isModernAlternateBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER) private var showingUdfpsBouncer = false @@ -110,12 +107,6 @@ constructor( ) } } - /** - * Hidden amount of input (pin/pattern/password) bouncer. This is used - * [KeyguardBouncerConstants.EXPANSION_VISIBLE] (0f) to - * [KeyguardBouncerConstants.EXPANSION_HIDDEN] (1f). Only used for the non-modernBouncer. - */ - private var inputBouncerHiddenAmount = KeyguardBouncerConstants.EXPANSION_HIDDEN private var inputBouncerExpansion = 0f // only used for modernBouncer private val stateListener: StatusBarStateController.StateListener = @@ -149,21 +140,6 @@ constructor( } } - private val mPrimaryBouncerExpansionCallback: PrimaryBouncerExpansionCallback = - object : PrimaryBouncerExpansionCallback { - override fun onExpansionChanged(expansion: Float) { - inputBouncerHiddenAmount = expansion - updateAlpha() - updatePauseAuth() - } - - override fun onVisibilityChanged(isVisible: Boolean) { - updateBouncerHiddenAmount() - updateAlpha() - updatePauseAuth() - } - } - private val configurationListener: ConfigurationController.ConfigurationListener = object : ConfigurationController.ConfigurationListener { override fun onUiModeChanged() { @@ -269,15 +245,13 @@ constructor( } init { - if (isModernBouncerEnabled || isModernAlternateBouncerEnabled) { - view.repeatWhenAttached { - // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion - // can make the view not visible; and we still want to listen for events - // that may make the view visible again. - repeatOnLifecycle(Lifecycle.State.CREATED) { - if (isModernBouncerEnabled) listenForBouncerExpansion(this) - if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this) - } + view.repeatWhenAttached { + // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion + // can make the view not visible; and we still want to listen for events + // that may make the view visible again. + repeatOnLifecycle(Lifecycle.State.CREATED) { + listenForBouncerExpansion(this) + if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this) } } } @@ -315,14 +289,6 @@ constructor( statusBarState = statusBarStateController.state qsExpansion = keyguardViewManager.qsExpansion keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback) - if (!isModernBouncerEnabled) { - val bouncer = keyguardViewManager.primaryBouncer - bouncer?.expansion?.let { - mPrimaryBouncerExpansionCallback.onExpansionChanged(it) - bouncer.addBouncerExpansionCallback(mPrimaryBouncerExpansionCallback) - } - updateBouncerHiddenAmount() - } configurationController.addCallback(configurationListener) shadeExpansionStateManager.addExpansionListener(shadeExpansionListener) updateScaleFactor() @@ -352,16 +318,10 @@ constructor( } activityLaunchAnimator.removeListener(activityLaunchAnimatorListener) keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback) - if (!isModernBouncerEnabled) { - keyguardViewManager.primaryBouncer?.removeBouncerExpansionCallback( - mPrimaryBouncerExpansionCallback - ) - } } override fun dump(pw: PrintWriter, args: Array<String>) { super.dump(pw, args) - pw.println("isModernBouncerEnabled=$isModernBouncerEnabled") pw.println("isModernAlternateBouncerEnabled=$isModernAlternateBouncerEnabled") pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer") pw.println( @@ -381,11 +341,7 @@ constructor( pw.println("udfpsRequestedByApp=$udfpsRequested") pw.println("launchTransitionFadingAway=$launchTransitionFadingAway") pw.println("lastDozeAmount=$lastDozeAmount") - if (isModernBouncerEnabled) { - pw.println("inputBouncerExpansion=$inputBouncerExpansion") - } else { - pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount") - } + pw.println("inputBouncerExpansion=$inputBouncerExpansion") view.dump(pw) } @@ -412,7 +368,6 @@ constructor( } else { keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false) } - updateBouncerHiddenAmount() updateAlpha() updatePauseAuth() return true @@ -453,19 +408,11 @@ constructor( } fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean { - return if (isModernBouncerEnabled) { - inputBouncerExpansion >= bouncerExpansionThreshold - } else { - inputBouncerHiddenAmount < bouncerExpansionThreshold - } + return inputBouncerExpansion >= bouncerExpansionThreshold } fun isInputBouncerFullyVisible(): Boolean { - return if (isModernBouncerEnabled) { - inputBouncerExpansion == 1f - } else { - keyguardViewManager.isBouncerShowing && !alternateBouncerInteractor.isVisibleState() - } + return inputBouncerExpansion == 1f } override fun listenForTouchesOutsideView(): Boolean { @@ -517,11 +464,7 @@ constructor( } private fun getInputBouncerHiddenAmt(): Float { - return if (isModernBouncerEnabled) { - 1f - inputBouncerExpansion - } else { - inputBouncerHiddenAmount - } + return 1f - inputBouncerExpansion } /** Update the scale factor based on the device's resolution. */ @@ -529,19 +472,6 @@ constructor( udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) } } - private fun updateBouncerHiddenAmount() { - if (isModernBouncerEnabled) { - return - } - val altBouncerShowing = alternateBouncerInteractor.isVisibleState() - if (altBouncerShowing || !keyguardViewManager.primaryBouncerIsOrWillBeShowing()) { - inputBouncerHiddenAmount = 1f - } else if (keyguardViewManager.isBouncerShowing) { - // input bouncer is fully showing - inputBouncerHiddenAmount = 0f - } - } - private val legacyAlternateBouncer: LegacyAlternateBouncer = object : LegacyAlternateBouncer { override fun showAlternateBouncer(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt index 3a01cd502929..39ea9368dacb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt @@ -54,9 +54,12 @@ class SinglePointerTouchProcessor @Inject constructor(val overlapDetector: Overl return when (event.actionMasked) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN, - MotionEvent.ACTION_MOVE -> processActionMove(preprocess()) + MotionEvent.ACTION_MOVE, + MotionEvent.ACTION_HOVER_ENTER, + MotionEvent.ACTION_HOVER_MOVE -> processActionMove(preprocess()) MotionEvent.ACTION_UP, - MotionEvent.ACTION_POINTER_UP -> + MotionEvent.ACTION_POINTER_UP, + MotionEvent.ACTION_HOVER_EXIT -> processActionUp(preprocess(), event.getPointerId(event.actionIndex)) MotionEvent.ACTION_CANCEL -> processActionCancel(NormalizedTouchData()) else -> diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index 805a20a6d965..1c26841a00be 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -18,7 +18,6 @@ package com.android.systemui.clipboardoverlay; import static android.content.ClipDescription.CLASSIFICATION_COMPLETE; -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN; @@ -29,7 +28,6 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.os.SystemProperties; -import android.provider.DeviceConfig; import android.provider.Settings; import android.util.Log; @@ -37,9 +35,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.util.DeviceConfigProxy; import javax.inject.Inject; import javax.inject.Provider; @@ -59,42 +54,28 @@ public class ClipboardListener implements "com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY"; private final Context mContext; - private final DeviceConfigProxy mDeviceConfig; private final Provider<ClipboardOverlayController> mOverlayProvider; - private final ClipboardOverlayControllerLegacyFactory mOverlayFactory; private final ClipboardToast mClipboardToast; private final ClipboardManager mClipboardManager; private final UiEventLogger mUiEventLogger; - private final FeatureFlags mFeatureFlags; - private boolean mUsingNewOverlay; private ClipboardOverlay mClipboardOverlay; @Inject - public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy, + public ClipboardListener(Context context, Provider<ClipboardOverlayController> clipboardOverlayControllerProvider, - ClipboardOverlayControllerLegacyFactory overlayFactory, ClipboardToast clipboardToast, ClipboardManager clipboardManager, - UiEventLogger uiEventLogger, - FeatureFlags featureFlags) { + UiEventLogger uiEventLogger) { mContext = context; - mDeviceConfig = deviceConfigProxy; mOverlayProvider = clipboardOverlayControllerProvider; - mOverlayFactory = overlayFactory; mClipboardToast = clipboardToast; mClipboardManager = clipboardManager; mUiEventLogger = uiEventLogger; - mFeatureFlags = featureFlags; - - mUsingNewOverlay = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR); } @Override public void start() { - if (mDeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) { - mClipboardManager.addPrimaryClipChangedListener(this); - } + mClipboardManager.addPrimaryClipChangedListener(this); } @Override @@ -120,14 +101,8 @@ public class ClipboardListener implements return; } - boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR); - if (mClipboardOverlay == null || enabled != mUsingNewOverlay) { - mUsingNewOverlay = enabled; - if (enabled) { - mClipboardOverlay = mOverlayProvider.get(); - } else { - mClipboardOverlay = mOverlayFactory.create(mContext); - } + if (mClipboardOverlay == null) { + mClipboardOverlay = mOverlayProvider.get(); mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource); } else { mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index f97d6af632b0..8c8ee8a325a0 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -17,7 +17,6 @@ package com.android.systemui.clipboardoverlay; import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS; @@ -72,6 +71,7 @@ import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.Overl import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.screenshot.TimeoutHandler; +import com.android.systemui.settings.DisplayTracker; import java.io.IOException; import java.util.Optional; @@ -96,6 +96,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private final ClipboardLogger mClipboardLogger; private final BroadcastDispatcher mBroadcastDispatcher; private final DisplayManager mDisplayManager; + private final DisplayTracker mDisplayTracker; private final ClipboardOverlayWindow mWindow; private final TimeoutHandler mTimeoutHandler; private final ClipboardOverlayUtils mClipboardUtils; @@ -186,9 +187,11 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv FeatureFlags featureFlags, ClipboardOverlayUtils clipboardUtils, @Background Executor bgExecutor, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + DisplayTracker displayTracker) { mBroadcastDispatcher = broadcastDispatcher; mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); + mDisplayTracker = displayTracker; final Context displayContext = context.createDisplayContext(getDefaultDisplay()); mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null); @@ -514,7 +517,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv } private Display getDefaultDisplay() { - return mDisplayManager.getDisplay(DEFAULT_DISPLAY); + return mDisplayManager.getDisplay(mDisplayTracker.getDefaultDisplayId()); } static class ClipboardLogger { diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java deleted file mode 100644 index 3a040829ba0c..000000000000 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java +++ /dev/null @@ -1,963 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.clipboardoverlay; - -import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; - -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS; -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON; -import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED; -import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER; -import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED; -import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EDIT_TAPPED; -import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED; -import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED; -import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED; -import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE; -import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT; - -import static java.util.Objects.requireNonNull; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.annotation.MainThread; -import android.app.ICompatCameraControlCallback; -import android.app.RemoteAction; -import android.content.BroadcastReceiver; -import android.content.ClipData; -import android.content.ClipDescription; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Insets; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.drawable.Icon; -import android.hardware.display.DisplayManager; -import android.hardware.input.InputManager; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Looper; -import android.provider.DeviceConfig; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.MathUtils; -import android.util.Size; -import android.util.TypedValue; -import android.view.Display; -import android.view.DisplayCutout; -import android.view.Gravity; -import android.view.InputEvent; -import android.view.InputEventReceiver; -import android.view.InputMonitor; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewRootImpl; -import android.view.ViewTreeObserver; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; -import android.view.animation.LinearInterpolator; -import android.view.animation.PathInterpolator; -import android.view.textclassifier.TextClassification; -import android.view.textclassifier.TextClassificationManager; -import android.view.textclassifier.TextClassifier; -import android.view.textclassifier.TextLinks; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.core.view.ViewCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; - -import com.android.internal.logging.UiEventLogger; -import com.android.internal.policy.PhoneWindow; -import com.android.systemui.R; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.broadcast.BroadcastSender; -import com.android.systemui.screenshot.DraggableConstraintLayout; -import com.android.systemui.screenshot.FloatingWindowUtil; -import com.android.systemui.screenshot.OverlayActionChip; -import com.android.systemui.screenshot.TimeoutHandler; - -import java.io.IOException; -import java.util.ArrayList; - -/** - * Controls state and UI for the overlay that appears when something is added to the clipboard - */ -public class ClipboardOverlayControllerLegacy implements ClipboardListener.ClipboardOverlay { - private static final String TAG = "ClipboardOverlayCtrlr"; - private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY"; - - /** Constants for screenshot/copy deconflicting */ - public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT"; - public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF"; - public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY"; - - private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard"; - - private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000; - private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe - private static final int FONT_SEARCH_STEP_PX = 4; - - private final Context mContext; - private final ClipboardLogger mClipboardLogger; - private final BroadcastDispatcher mBroadcastDispatcher; - private final DisplayManager mDisplayManager; - private final DisplayMetrics mDisplayMetrics; - private final WindowManager mWindowManager; - private final WindowManager.LayoutParams mWindowLayoutParams; - private final PhoneWindow mWindow; - private final TimeoutHandler mTimeoutHandler; - private final AccessibilityManager mAccessibilityManager; - private final TextClassifier mTextClassifier; - - private final DraggableConstraintLayout mView; - private final View mClipboardPreview; - private final ImageView mImagePreview; - private final TextView mTextPreview; - private final TextView mHiddenPreview; - private final View mPreviewBorder; - private final OverlayActionChip mEditChip; - private final OverlayActionChip mShareChip; - private final OverlayActionChip mRemoteCopyChip; - private final View mActionContainerBackground; - private final View mDismissButton; - private final LinearLayout mActionContainer; - private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>(); - - private Runnable mOnSessionCompleteListener; - - private InputMonitor mInputMonitor; - private InputEventReceiver mInputEventReceiver; - - private BroadcastReceiver mCloseDialogsReceiver; - private BroadcastReceiver mScreenshotReceiver; - - private boolean mBlockAttach = false; - private Animator mExitAnimator; - private Animator mEnterAnimator; - private final int mOrientation; - private boolean mKeyboardVisible; - - - public ClipboardOverlayControllerLegacy(Context context, - BroadcastDispatcher broadcastDispatcher, - BroadcastSender broadcastSender, - TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) { - mBroadcastDispatcher = broadcastDispatcher; - mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); - final Context displayContext = context.createDisplayContext(getDefaultDisplay()); - mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null); - - mClipboardLogger = new ClipboardLogger(uiEventLogger); - - mAccessibilityManager = AccessibilityManager.getInstance(mContext); - mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class)) - .getTextClassifier(); - - mWindowManager = mContext.getSystemService(WindowManager.class); - - mDisplayMetrics = new DisplayMetrics(); - mContext.getDisplay().getRealMetrics(mDisplayMetrics); - - mTimeoutHandler = timeoutHandler; - mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS); - - // Setup the window that we are going to use - mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams(); - mWindowLayoutParams.setTitle("ClipboardOverlay"); - - mWindow = FloatingWindowUtil.getFloatingWindow(mContext); - mWindow.setWindowManager(mWindowManager, null, null); - - setWindowFocusable(false); - - mView = (DraggableConstraintLayout) - LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay_legacy, null); - mActionContainerBackground = - requireNonNull(mView.findViewById(R.id.actions_container_background)); - mActionContainer = requireNonNull(mView.findViewById(R.id.actions)); - mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview)); - mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview)); - mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview)); - mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview)); - mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border)); - mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip)); - mShareChip = requireNonNull(mView.findViewById(R.id.share_chip)); - mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip)); - mEditChip.setAlpha(1); - mShareChip.setAlpha(1); - mRemoteCopyChip.setAlpha(1); - mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button)); - - mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share)); - mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() { - @Override - public void onInteraction() { - mTimeoutHandler.resetTimeout(); - } - - @Override - public void onSwipeDismissInitiated(Animator animator) { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED); - mExitAnimator = animator; - } - - @Override - public void onDismissComplete() { - hideImmediate(); - } - }); - - mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> { - int availableHeight = mTextPreview.getHeight() - - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom()); - mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight()); - return true; - }); - - mDismissButton.setOnClickListener(view -> { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED); - animateOut(); - }); - - mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true); - mRemoteCopyChip.setIcon( - Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true); - mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true); - mOrientation = mContext.getResources().getConfiguration().orientation; - - attachWindow(); - withWindowAttached(() -> { - mWindow.setContentView(mView); - WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets(); - mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime()); - updateInsets(insets); - mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener( - new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - WindowInsets insets = - mWindowManager.getCurrentWindowMetrics().getWindowInsets(); - boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime()); - if (keyboardVisible != mKeyboardVisible) { - mKeyboardVisible = keyboardVisible; - updateInsets(insets); - } - } - }); - mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback( - new ViewRootImpl.ActivityConfigCallback() { - @Override - public void onConfigurationChanged(Configuration overrideConfig, - int newDisplayId) { - if (mContext.getResources().getConfiguration().orientation - != mOrientation) { - mClipboardLogger.logSessionComplete( - CLIPBOARD_OVERLAY_DISMISSED_OTHER); - hideImmediate(); - } - } - - @Override - public void requestCompatCameraControl( - boolean showControl, boolean transformationApplied, - ICompatCameraControlCallback callback) { - Log.w(TAG, "unexpected requestCompatCameraControl call"); - } - }); - }); - - mTimeoutHandler.setOnTimeoutRunnable(() -> { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT); - animateOut(); - }); - - mCloseDialogsReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER); - animateOut(); - } - } - }; - - mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver, - new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS)); - mScreenshotReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (SCREENSHOT_ACTION.equals(intent.getAction())) { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER); - animateOut(); - } - } - }; - - mBroadcastDispatcher.registerReceiver(mScreenshotReceiver, - new IntentFilter(SCREENSHOT_ACTION), null, null, Context.RECEIVER_EXPORTED, - SELF_PERMISSION); - monitorOutsideTouches(); - - Intent copyIntent = new Intent(COPY_OVERLAY_ACTION); - // Set package name so the system knows it's safe - copyIntent.setPackage(mContext.getPackageName()); - broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION); - } - - @Override // ClipboardListener.ClipboardOverlay - public void setClipData(ClipData clipData, String clipSource) { - if (mExitAnimator != null && mExitAnimator.isRunning()) { - mExitAnimator.cancel(); - } - reset(); - String accessibilityAnnouncement; - - boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null - && clipData.getDescription().getExtras() - .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE); - if (clipData == null || clipData.getItemCount() == 0) { - showTextPreview( - mContext.getResources().getString(R.string.clipboard_overlay_text_copied), - mTextPreview); - accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); - } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) { - ClipData.Item item = clipData.getItemAt(0); - if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) { - if (item.getTextLinks() != null) { - AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource)); - } - } - if (isSensitive) { - showEditableText( - mContext.getResources().getString(R.string.clipboard_asterisks), true); - } else { - showEditableText(item.getText(), false); - } - showShareChip(clipData); - accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied); - } else if (clipData.getItemAt(0).getUri() != null) { - if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) { - showShareChip(clipData); - accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied); - } else { - accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); - } - } else { - showTextPreview( - mContext.getResources().getString(R.string.clipboard_overlay_text_copied), - mTextPreview); - accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); - } - Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext); - // Only show remote copy if it's available. - PackageManager packageManager = mContext.getPackageManager(); - if (packageManager.resolveActivity( - remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) { - mRemoteCopyChip.setContentDescription( - mContext.getString(R.string.clipboard_send_nearby_description)); - mRemoteCopyChip.setVisibility(View.VISIBLE); - mRemoteCopyChip.setOnClickListener((v) -> { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED); - mContext.startActivity(remoteCopyIntent); - animateOut(); - }); - mActionContainerBackground.setVisibility(View.VISIBLE); - } else { - mRemoteCopyChip.setVisibility(View.GONE); - } - withWindowAttached(() -> { - if (mEnterAnimator == null || !mEnterAnimator.isRunning()) { - mView.post(this::animateIn); - } - mView.announceForAccessibility(accessibilityAnnouncement); - }); - mTimeoutHandler.resetTimeout(); - } - - @Override // ClipboardListener.ClipboardOverlay - public void setOnSessionCompleteListener(Runnable runnable) { - mOnSessionCompleteListener = runnable; - } - - private void classifyText(ClipData.Item item, String source) { - ArrayList<RemoteAction> actions = new ArrayList<>(); - for (TextLinks.TextLink link : item.getTextLinks().getLinks()) { - TextClassification classification = mTextClassifier.classifyText( - item.getText(), link.getStart(), link.getEnd(), null); - actions.addAll(classification.getActions()); - } - mView.post(() -> { - resetActionChips(); - if (actions.size() > 0) { - mActionContainerBackground.setVisibility(View.VISIBLE); - for (RemoteAction action : actions) { - Intent targetIntent = action.getActionIntent().getIntent(); - ComponentName component = targetIntent.getComponent(); - if (component != null && !TextUtils.equals(source, - component.getPackageName())) { - OverlayActionChip chip = constructActionChip(action); - mActionContainer.addView(chip); - mActionChips.add(chip); - break; // only show at most one action chip - } - } - } - }); - } - - private void showShareChip(ClipData clip) { - mShareChip.setVisibility(View.VISIBLE); - mActionContainerBackground.setVisibility(View.VISIBLE); - mShareChip.setOnClickListener((v) -> shareContent(clip)); - } - - private OverlayActionChip constructActionChip(RemoteAction action) { - OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate( - R.layout.overlay_action_chip, mActionContainer, false); - chip.setText(action.getTitle()); - chip.setContentDescription(action.getTitle()); - chip.setIcon(action.getIcon(), false); - chip.setPendingIntent(action.getActionIntent(), () -> { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED); - animateOut(); - }); - chip.setAlpha(1); - return chip; - } - - private void monitorOutsideTouches() { - InputManager inputManager = mContext.getSystemService(InputManager.class); - mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0); - mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(), - Looper.getMainLooper()) { - @Override - public void onInputEvent(InputEvent event) { - if (event instanceof MotionEvent) { - MotionEvent motionEvent = (MotionEvent) event; - if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { - Region touchRegion = new Region(); - - final Rect tmpRect = new Rect(); - mPreviewBorder.getBoundsOnScreen(tmpRect); - tmpRect.inset( - (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP), - (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, - -SWIPE_PADDING_DP)); - touchRegion.op(tmpRect, Region.Op.UNION); - mActionContainerBackground.getBoundsOnScreen(tmpRect); - tmpRect.inset( - (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP), - (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, - -SWIPE_PADDING_DP)); - touchRegion.op(tmpRect, Region.Op.UNION); - mDismissButton.getBoundsOnScreen(tmpRect); - touchRegion.op(tmpRect, Region.Op.UNION); - if (!touchRegion.contains( - (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE); - animateOut(); - } - } - } - finishInputEvent(event, true /* handled */); - } - }; - } - - private void editImage(Uri uri) { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext)); - animateOut(); - } - - private void editText() { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - mContext.startActivity(IntentCreator.getTextEditorIntent(mContext)); - animateOut(); - } - - private void shareContent(ClipData clip) { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED); - mContext.startActivity(IntentCreator.getShareIntent(clip, mContext)); - animateOut(); - } - - private void showSinglePreview(View v) { - mTextPreview.setVisibility(View.GONE); - mImagePreview.setVisibility(View.GONE); - mHiddenPreview.setVisibility(View.GONE); - v.setVisibility(View.VISIBLE); - } - - private void showTextPreview(CharSequence text, TextView textView) { - showSinglePreview(textView); - final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length())); - textView.setText(truncatedText); - updateTextSize(truncatedText, textView); - - textView.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - if (right - left != oldRight - oldLeft) { - updateTextSize(truncatedText, textView); - } - }); - mEditChip.setVisibility(View.GONE); - } - - private void updateTextSize(CharSequence text, TextView textView) { - Paint paint = new Paint(textView.getPaint()); - Resources res = textView.getResources(); - float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font); - float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font); - if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) { - // If the text is a single word and would fit within the TextView at the min font size, - // find the biggest font size that will fit. - float fontSizePx = minFontSize; - while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize - && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) { - fontSizePx += FONT_SEARCH_STEP_PX; - } - // Need to turn off autosizing, otherwise setTextSize is a no-op. - textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE); - // It's possible to hit the max font size and not fill the width, so centering - // horizontally looks better in this case. - textView.setGravity(Gravity.CENTER); - textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx); - } else { - // Otherwise just stick with autosize. - textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize, - (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX); - textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START); - } - } - - private static boolean fitsInView(CharSequence text, TextView textView, Paint paint, - float fontSizePx) { - paint.setTextSize(fontSizePx); - float size = paint.measureText(text.toString()); - float availableWidth = textView.getWidth() - textView.getPaddingLeft() - - textView.getPaddingRight(); - return size < availableWidth; - } - - private static boolean isOneWord(CharSequence text) { - return text.toString().split("\\s+", 2).length == 1; - } - - private void showEditableText(CharSequence text, boolean hidden) { - TextView textView = hidden ? mHiddenPreview : mTextPreview; - showTextPreview(text, textView); - View.OnClickListener listener = v -> editText(); - setAccessibilityActionToEdit(textView); - if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) { - mEditChip.setVisibility(View.VISIBLE); - mActionContainerBackground.setVisibility(View.VISIBLE); - mEditChip.setContentDescription( - mContext.getString(R.string.clipboard_edit_text_description)); - mEditChip.setOnClickListener(listener); - } - textView.setOnClickListener(listener); - } - - private boolean tryShowEditableImage(Uri uri, boolean isSensitive) { - View.OnClickListener listener = v -> editImage(uri); - ContentResolver resolver = mContext.getContentResolver(); - String mimeType = resolver.getType(uri); - boolean isEditableImage = mimeType != null && mimeType.startsWith("image"); - if (isSensitive) { - mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden)); - showSinglePreview(mHiddenPreview); - if (isEditableImage) { - mHiddenPreview.setOnClickListener(listener); - setAccessibilityActionToEdit(mHiddenPreview); - } - } else if (isEditableImage) { // if the MIMEtype is image, try to load - try { - int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale); - // The width of the view is capped, height maintains aspect ratio, so allow it to be - // taller if needed. - Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null); - showSinglePreview(mImagePreview); - mImagePreview.setImageBitmap(thumbnail); - mImagePreview.setOnClickListener(listener); - setAccessibilityActionToEdit(mImagePreview); - } catch (IOException e) { - Log.e(TAG, "Thumbnail loading failed", e); - showTextPreview( - mContext.getResources().getString(R.string.clipboard_overlay_text_copied), - mTextPreview); - isEditableImage = false; - } - } else { - showTextPreview( - mContext.getResources().getString(R.string.clipboard_overlay_text_copied), - mTextPreview); - } - if (isEditableImage && DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) { - mEditChip.setVisibility(View.VISIBLE); - mActionContainerBackground.setVisibility(View.VISIBLE); - mEditChip.setOnClickListener(listener); - mEditChip.setContentDescription( - mContext.getString(R.string.clipboard_edit_image_description)); - } - return isEditableImage; - } - - private void setAccessibilityActionToEdit(View view) { - ViewCompat.replaceAccessibilityAction(view, - AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK, - mContext.getString(R.string.clipboard_edit), null); - } - - private void animateIn() { - if (mAccessibilityManager.isEnabled()) { - mDismissButton.setVisibility(View.VISIBLE); - } - mEnterAnimator = getEnterAnimation(); - mEnterAnimator.start(); - } - - private void animateOut() { - if (mExitAnimator != null && mExitAnimator.isRunning()) { - return; - } - Animator anim = getExitAnimation(); - anim.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationCancel(Animator animation) { - super.onAnimationCancel(animation); - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (!mCancelled) { - hideImmediate(); - } - } - }); - mExitAnimator = anim; - anim.start(); - } - - private Animator getEnterAnimation() { - TimeInterpolator linearInterpolator = new LinearInterpolator(); - TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f); - AnimatorSet enterAnim = new AnimatorSet(); - - ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1); - rootAnim.setInterpolator(linearInterpolator); - rootAnim.setDuration(66); - rootAnim.addUpdateListener(animation -> { - mView.setAlpha(animation.getAnimatedFraction()); - }); - - ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1); - scaleAnim.setInterpolator(scaleInterpolator); - scaleAnim.setDuration(333); - scaleAnim.addUpdateListener(animation -> { - float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction()); - mClipboardPreview.setScaleX(previewScale); - mClipboardPreview.setScaleY(previewScale); - mPreviewBorder.setScaleX(previewScale); - mPreviewBorder.setScaleY(previewScale); - - float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX(); - mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX()); - mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX()); - float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction()); - float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction()); - mActionContainer.setScaleX(actionsScaleX); - mActionContainer.setScaleY(actionsScaleY); - mActionContainerBackground.setScaleX(actionsScaleX); - mActionContainerBackground.setScaleY(actionsScaleY); - }); - - ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1); - alphaAnim.setInterpolator(linearInterpolator); - alphaAnim.setDuration(283); - alphaAnim.addUpdateListener(animation -> { - float alpha = animation.getAnimatedFraction(); - mClipboardPreview.setAlpha(alpha); - mPreviewBorder.setAlpha(alpha); - mDismissButton.setAlpha(alpha); - mActionContainer.setAlpha(alpha); - }); - - mActionContainer.setAlpha(0); - mPreviewBorder.setAlpha(0); - mClipboardPreview.setAlpha(0); - enterAnim.play(rootAnim).with(scaleAnim); - enterAnim.play(alphaAnim).after(50).after(rootAnim); - - enterAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - mView.setAlpha(1); - mTimeoutHandler.resetTimeout(); - } - }); - return enterAnim; - } - - private Animator getExitAnimation() { - TimeInterpolator linearInterpolator = new LinearInterpolator(); - TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f); - AnimatorSet exitAnim = new AnimatorSet(); - - ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1); - rootAnim.setInterpolator(linearInterpolator); - rootAnim.setDuration(100); - rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction())); - - ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1); - scaleAnim.setInterpolator(scaleInterpolator); - scaleAnim.setDuration(250); - scaleAnim.addUpdateListener(animation -> { - float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction()); - mClipboardPreview.setScaleX(previewScale); - mClipboardPreview.setScaleY(previewScale); - mPreviewBorder.setScaleX(previewScale); - mPreviewBorder.setScaleY(previewScale); - - float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX(); - mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX()); - mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX()); - float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction()); - float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction()); - mActionContainer.setScaleX(actionScaleX); - mActionContainer.setScaleY(actionScaleY); - mActionContainerBackground.setScaleX(actionScaleX); - mActionContainerBackground.setScaleY(actionScaleY); - }); - - ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1); - alphaAnim.setInterpolator(linearInterpolator); - alphaAnim.setDuration(166); - alphaAnim.addUpdateListener(animation -> { - float alpha = 1 - animation.getAnimatedFraction(); - mClipboardPreview.setAlpha(alpha); - mPreviewBorder.setAlpha(alpha); - mDismissButton.setAlpha(alpha); - mActionContainer.setAlpha(alpha); - }); - - exitAnim.play(alphaAnim).with(scaleAnim); - exitAnim.play(rootAnim).after(150).after(alphaAnim); - return exitAnim; - } - - private void hideImmediate() { - // Note this may be called multiple times if multiple dismissal events happen at the same - // time. - mTimeoutHandler.cancelTimeout(); - final View decorView = mWindow.peekDecorView(); - if (decorView != null && decorView.isAttachedToWindow()) { - mWindowManager.removeViewImmediate(decorView); - } - if (mCloseDialogsReceiver != null) { - mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver); - mCloseDialogsReceiver = null; - } - if (mScreenshotReceiver != null) { - mBroadcastDispatcher.unregisterReceiver(mScreenshotReceiver); - mScreenshotReceiver = null; - } - if (mInputEventReceiver != null) { - mInputEventReceiver.dispose(); - mInputEventReceiver = null; - } - if (mInputMonitor != null) { - mInputMonitor.dispose(); - mInputMonitor = null; - } - if (mOnSessionCompleteListener != null) { - mOnSessionCompleteListener.run(); - } - } - - private void resetActionChips() { - for (OverlayActionChip chip : mActionChips) { - mActionContainer.removeView(chip); - } - mActionChips.clear(); - } - - private void reset() { - mView.setTranslationX(0); - mView.setAlpha(0); - mActionContainerBackground.setVisibility(View.GONE); - mShareChip.setVisibility(View.GONE); - mEditChip.setVisibility(View.GONE); - mRemoteCopyChip.setVisibility(View.GONE); - resetActionChips(); - mTimeoutHandler.cancelTimeout(); - mClipboardLogger.reset(); - } - - @MainThread - private void attachWindow() { - View decorView = mWindow.getDecorView(); - if (decorView.isAttachedToWindow() || mBlockAttach) { - return; - } - mBlockAttach = true; - mWindowManager.addView(decorView, mWindowLayoutParams); - decorView.requestApplyInsets(); - mView.requestApplyInsets(); - decorView.getViewTreeObserver().addOnWindowAttachListener( - new ViewTreeObserver.OnWindowAttachListener() { - @Override - public void onWindowAttached() { - mBlockAttach = false; - } - - @Override - public void onWindowDetached() { - } - } - ); - } - - private void withWindowAttached(Runnable action) { - View decorView = mWindow.getDecorView(); - if (decorView.isAttachedToWindow()) { - action.run(); - } else { - decorView.getViewTreeObserver().addOnWindowAttachListener( - new ViewTreeObserver.OnWindowAttachListener() { - @Override - public void onWindowAttached() { - mBlockAttach = false; - decorView.getViewTreeObserver().removeOnWindowAttachListener(this); - action.run(); - } - - @Override - public void onWindowDetached() { - } - }); - } - } - - private void updateInsets(WindowInsets insets) { - int orientation = mContext.getResources().getConfiguration().orientation; - FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams(); - if (p == null) { - return; - } - DisplayCutout cutout = insets.getDisplayCutout(); - Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars()); - Insets imeInsets = insets.getInsets(WindowInsets.Type.ime()); - if (cutout == null) { - p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom)); - } else { - Insets waterfall = cutout.getWaterfallInsets(); - if (orientation == ORIENTATION_PORTRAIT) { - p.setMargins( - waterfall.left, - Math.max(cutout.getSafeInsetTop(), waterfall.top), - waterfall.right, - Math.max(imeInsets.bottom, - Math.max(cutout.getSafeInsetBottom(), - Math.max(navBarInsets.bottom, waterfall.bottom)))); - } else { - p.setMargins( - waterfall.left, - waterfall.top, - waterfall.right, - Math.max(imeInsets.bottom, - Math.max(navBarInsets.bottom, waterfall.bottom))); - } - } - mView.setLayoutParams(p); - mView.requestLayout(); - } - - private Display getDefaultDisplay() { - return mDisplayManager.getDisplay(DEFAULT_DISPLAY); - } - - /** - * Updates the window focusability. If the window is already showing, then it updates the - * window immediately, otherwise the layout params will be applied when the window is next - * shown. - */ - private void setWindowFocusable(boolean focusable) { - int flags = mWindowLayoutParams.flags; - if (focusable) { - mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - } else { - mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - } - if (mWindowLayoutParams.flags == flags) { - return; - } - final View decorView = mWindow.peekDecorView(); - if (decorView != null && decorView.isAttachedToWindow()) { - mWindowManager.updateViewLayout(decorView, mWindowLayoutParams); - } - } - - static class ClipboardLogger { - private final UiEventLogger mUiEventLogger; - private boolean mGuarded = false; - - ClipboardLogger(UiEventLogger uiEventLogger) { - mUiEventLogger = uiEventLogger; - } - - void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) { - if (!mGuarded) { - mGuarded = true; - mUiEventLogger.log(event); - } - } - - void reset() { - mGuarded = false; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java deleted file mode 100644 index 0d989a78947d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java +++ /dev/null @@ -1,54 +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.clipboardoverlay; - -import android.content.Context; - -import com.android.internal.logging.UiEventLogger; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.broadcast.BroadcastSender; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.screenshot.TimeoutHandler; - -import javax.inject.Inject; - -/** - * A factory that churns out ClipboardOverlayControllerLegacys on demand. - */ -@SysUISingleton -public class ClipboardOverlayControllerLegacyFactory { - - private final UiEventLogger mUiEventLogger; - private final BroadcastDispatcher mBroadcastDispatcher; - private final BroadcastSender mBroadcastSender; - - @Inject - public ClipboardOverlayControllerLegacyFactory(BroadcastDispatcher broadcastDispatcher, - BroadcastSender broadcastSender, UiEventLogger uiEventLogger) { - this.mBroadcastDispatcher = broadcastDispatcher; - this.mBroadcastSender = broadcastSender; - this.mUiEventLogger = uiEventLogger; - } - - /** - * One new ClipboardOverlayControllerLegacy, coming right up! - */ - public ClipboardOverlayControllerLegacy create(Context context) { - return new ClipboardOverlayControllerLegacy(context, mBroadcastDispatcher, mBroadcastSender, - new TimeoutHandler(context), mUiEventLogger); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java index 22448130f7e5..09b2e44c1be1 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java @@ -16,7 +16,6 @@ package com.android.systemui.clipboardoverlay.dagger; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -28,6 +27,7 @@ import android.view.LayoutInflater; import com.android.systemui.R; import com.android.systemui.clipboardoverlay.ClipboardOverlayView; +import com.android.systemui.settings.DisplayTracker; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -46,8 +46,9 @@ public interface ClipboardOverlayModule { */ @Provides @OverlayWindowContext - static Context provideWindowContext(DisplayManager displayManager, Context context) { - Display display = displayManager.getDisplay(DEFAULT_DISPLAY); + static Context provideWindowContext(DisplayManager displayManager, + DisplayTracker displayTracker, Context context) { + Display display = displayManager.getDisplay(displayTracker.getDefaultDisplayId()); return context.createWindowContext(display, TYPE_SCREENSHOT, null); } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt new file mode 100644 index 000000000000..9763665c5b7c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt @@ -0,0 +1,55 @@ +/* + * 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.common.ui.view + +import android.content.Context +import android.util.AttributeSet +import androidx.constraintlayout.widget.ConstraintLayout +import com.android.systemui.animation.LaunchableView +import com.android.systemui.animation.LaunchableViewDelegate + +/** A [ConstraintLayout] that also implements [LaunchableView]. */ +open class LaunchableConstraintLayout : ConstraintLayout, LaunchableView { + private val delegate = + LaunchableViewDelegate( + this, + superSetVisibility = { super.setVisibility(it) }, + ) + + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + override fun setShouldBlockVisibilityChanges(block: Boolean) { + delegate.setShouldBlockVisibilityChanges(block) + } + + override fun setVisibility(visibility: Int) { + delegate.setVisibility(visibility) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt index ddde6280f3a2..2edac528b037 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt @@ -23,7 +23,7 @@ import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate /** A [LinearLayout] that also implements [LaunchableView]. */ -class LaunchableLinearLayout : LinearLayout, LaunchableView { +open class LaunchableLinearLayout : LinearLayout, LaunchableView { private val delegate = LaunchableViewDelegate( this, diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt new file mode 100644 index 000000000000..2dd98dcf41b6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.common.ui.view + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import kotlin.math.pow +import kotlin.math.sqrt +import kotlinx.coroutines.DisposableHandle + +/** + * View designed to handle long-presses. + * + * The view will not handle any long pressed by default. To set it up, set up a listener and, when + * ready to start consuming long-presses, set [setLongPressHandlingEnabled] to `true`. + */ +class LongPressHandlingView( + context: Context, + attrs: AttributeSet?, +) : + View( + context, + attrs, + ) { + interface Listener { + /** Notifies that a long-press has been detected by the given view. */ + fun onLongPressDetected( + view: View, + x: Int, + y: Int, + ) + + /** Notifies that the gesture was too short for a long press, it is actually a click. */ + fun onSingleTapDetected(view: View) = Unit + } + + var listener: Listener? = null + + private val interactionHandler: LongPressHandlingViewInteractionHandler by lazy { + LongPressHandlingViewInteractionHandler( + postDelayed = { block, timeoutMs -> + val dispatchToken = Any() + + handler.postDelayed( + block, + dispatchToken, + timeoutMs, + ) + + DisposableHandle { handler.removeCallbacksAndMessages(dispatchToken) } + }, + isAttachedToWindow = ::isAttachedToWindow, + onLongPressDetected = { x, y -> + listener?.onLongPressDetected( + view = this, + x = x, + y = y, + ) + }, + onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) }, + ) + } + + fun setLongPressHandlingEnabled(isEnabled: Boolean) { + interactionHandler.isLongPressHandlingEnabled = isEnabled + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent?): Boolean { + return interactionHandler.onTouchEvent(event?.toModel()) + } +} + +private fun MotionEvent.toModel(): LongPressHandlingViewInteractionHandler.MotionEventModel { + return when (actionMasked) { + MotionEvent.ACTION_DOWN -> + LongPressHandlingViewInteractionHandler.MotionEventModel.Down( + x = x.toInt(), + y = y.toInt(), + ) + MotionEvent.ACTION_MOVE -> + LongPressHandlingViewInteractionHandler.MotionEventModel.Move( + distanceMoved = distanceMoved(), + ) + MotionEvent.ACTION_UP -> + LongPressHandlingViewInteractionHandler.MotionEventModel.Up( + distanceMoved = distanceMoved(), + gestureDuration = gestureDuration(), + ) + MotionEvent.ACTION_CANCEL -> LongPressHandlingViewInteractionHandler.MotionEventModel.Cancel + else -> LongPressHandlingViewInteractionHandler.MotionEventModel.Other + } +} + +private fun MotionEvent.distanceMoved(): Float { + return if (historySize > 0) { + sqrt((x - getHistoricalX(0)).pow(2) + (y - getHistoricalY(0)).pow(2)) + } else { + 0f + } +} + +private fun MotionEvent.gestureDuration(): Long { + return eventTime - downTime +} diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt new file mode 100644 index 000000000000..c2d4d12d03c3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.common.ui.view + +import android.view.ViewConfiguration +import kotlinx.coroutines.DisposableHandle + +/** Encapsulates logic to handle complex touch interactions with a [LongPressHandlingView]. */ +class LongPressHandlingViewInteractionHandler( + /** + * Callback to run the given [Runnable] with the given delay, returning a [DisposableHandle] + * allowing the delayed runnable to be canceled before it is run. + */ + private val postDelayed: (block: Runnable, delayMs: Long) -> DisposableHandle, + /** Callback to be queried to check if the view is attached to its window. */ + private val isAttachedToWindow: () -> Boolean, + /** Callback reporting the a long-press gesture was detected at the given coordinates. */ + private val onLongPressDetected: (x: Int, y: Int) -> Unit, + /** Callback reporting the a single tap gesture was detected at the given coordinates. */ + private val onSingleTapDetected: () -> Unit, +) { + sealed class MotionEventModel { + object Other : MotionEventModel() + + data class Down( + val x: Int, + val y: Int, + ) : MotionEventModel() + + data class Move( + val distanceMoved: Float, + ) : MotionEventModel() + + data class Up( + val distanceMoved: Float, + val gestureDuration: Long, + ) : MotionEventModel() + + object Cancel : MotionEventModel() + } + + var isLongPressHandlingEnabled: Boolean = false + var scheduledLongPressHandle: DisposableHandle? = null + + fun onTouchEvent(event: MotionEventModel?): Boolean { + if (!isLongPressHandlingEnabled) { + return false + } + + return when (event) { + is MotionEventModel.Down -> { + scheduleLongPress(event.x, event.y) + true + } + is MotionEventModel.Move -> { + if (event.distanceMoved > ViewConfiguration.getTouchSlop()) { + cancelScheduledLongPress() + } + false + } + is MotionEventModel.Up -> { + cancelScheduledLongPress() + if ( + event.distanceMoved <= ViewConfiguration.getTouchSlop() && + event.gestureDuration < ViewConfiguration.getLongPressTimeout() + ) { + dispatchSingleTap() + } + false + } + is MotionEventModel.Cancel -> { + cancelScheduledLongPress() + false + } + else -> false + } + } + + private fun scheduleLongPress( + x: Int, + y: Int, + ) { + scheduledLongPressHandle = + postDelayed( + { + dispatchLongPress( + x = x, + y = y, + ) + }, + ViewConfiguration.getLongPressTimeout().toLong(), + ) + } + + private fun dispatchLongPress( + x: Int, + y: Int, + ) { + if (!isAttachedToWindow()) { + return + } + + onLongPressDetected(x, y) + } + + private fun cancelScheduledLongPress() { + scheduledLongPressHandle?.dispose() + } + + private fun dispatchSingleTap() { + if (!isAttachedToWindow()) { + return + } + + onSingleTapDetected() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index f29f6d0dd0cb..822190f21da1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -188,6 +188,8 @@ interface ControlsController : UserAwareController { /** See [ControlsUiController.getPreferredSelectedItem]. */ fun getPreferredSelection(): SelectedItem + fun setPreferredSelection(selectedItem: SelectedItem) + /** * Bind to a service that provides a Device Controls panel (embedded activity). This will allow * the app to remain "warm", and reduce latency. diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 111fcbbe30be..1cbfe01a9a1a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -36,6 +36,7 @@ import com.android.systemui.backup.BackupHelper import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.controls.panels.AuthorizedPanelsRepository import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.controls.ui.SelectedItem import com.android.systemui.dagger.SysUISingleton @@ -61,6 +62,7 @@ class ControlsControllerImpl @Inject constructor ( private val listingController: ControlsListingController, private val userFileManager: UserFileManager, private val userTracker: UserTracker, + private val authorizedPanelsRepository: AuthorizedPanelsRepository, optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, dumpManager: DumpManager, ) : Dumpable, ControlsController { @@ -249,6 +251,11 @@ class ControlsControllerImpl @Inject constructor ( private fun resetFavorites() { Favorites.clear() Favorites.load(persistenceWrapper.readFavorites()) + // After loading favorites, add the package names of any apps with favorites to the list + // of authorized panels. That way, if the user has previously favorited controls for an app, + // that panel will be authorized. + authorizedPanelsRepository.addAuthorizedPanels( + Favorites.getAllStructures().map { it.componentName.packageName }.toSet()) } private fun confirmAvailability(): Boolean { @@ -489,6 +496,7 @@ class ControlsControllerImpl @Inject constructor ( if (!confirmAvailability()) return executor.execute { if (Favorites.addFavorite(componentName, structureName, controlInfo)) { + authorizedPanelsRepository.addAuthorizedPanels(setOf(componentName.packageName)) persistenceWrapper.storeFavorites(Favorites.getAllStructures()) } } @@ -555,6 +563,10 @@ class ControlsControllerImpl @Inject constructor ( return uiController.getPreferredSelectedItem(getFavorites()) } + override fun setPreferredSelection(selectedItem: SelectedItem) { + uiController.updatePreferences(selectedItem) + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("ControlsController state:") pw.println(" Changing users: $userChanging") diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt index 6d6410de1a91..6af8e73c8d25 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt @@ -34,6 +34,8 @@ import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsListingControllerImpl import com.android.systemui.controls.management.ControlsProviderSelectorActivity import com.android.systemui.controls.management.ControlsRequestDialog +import com.android.systemui.controls.panels.AuthorizedPanelsRepository +import com.android.systemui.controls.panels.AuthorizedPanelsRepositoryImpl import com.android.systemui.controls.settings.ControlsSettingsDialogManager import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl import com.android.systemui.controls.ui.ControlActionCoordinator @@ -104,6 +106,11 @@ abstract class ControlsModule { coordinator: ControlActionCoordinatorImpl ): ControlActionCoordinator + @Binds + abstract fun provideAuthorizedPanelsRepository( + repository: AuthorizedPanelsRepositoryImpl + ): AuthorizedPanelsRepository + @BindsOptionalOf abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt index 753d5addeb11..3fe0f03488d1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt @@ -45,14 +45,15 @@ import java.util.concurrent.Executor * @param onAppSelected a callback to indicate that an app has been selected in the list. */ class AppAdapter( - backgroundExecutor: Executor, - uiExecutor: Executor, - lifecycle: Lifecycle, - controlsListingController: ControlsListingController, - private val layoutInflater: LayoutInflater, - private val onAppSelected: (ComponentName?) -> Unit = {}, - private val favoritesRenderer: FavoritesRenderer, - private val resources: Resources + backgroundExecutor: Executor, + uiExecutor: Executor, + lifecycle: Lifecycle, + controlsListingController: ControlsListingController, + private val layoutInflater: LayoutInflater, + private val onAppSelected: (ControlsServiceInfo) -> Unit = {}, + private val favoritesRenderer: FavoritesRenderer, + private val resources: Resources, + private val authorizedPanels: Set<String> = emptySet(), ) : RecyclerView.Adapter<AppAdapter.Holder>() { private var listOfServices = emptyList<ControlsServiceInfo>() @@ -64,8 +65,10 @@ class AppAdapter( val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) { it.loadLabel() ?: "" } - listOfServices = serviceInfos.filter { it.panelActivity == null } - .sortedWith(localeComparator) + // No panel or the panel is not authorized + listOfServices = serviceInfos.filter { + it.panelActivity == null || it.panelActivity?.packageName !in authorizedPanels + }.sortedWith(localeComparator) uiExecutor.execute(::notifyDataSetChanged) } } @@ -86,8 +89,8 @@ class AppAdapter( override fun onBindViewHolder(holder: Holder, index: Int) { holder.bindData(listOfServices[index]) - holder.itemView.setOnClickListener { - onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key)) + holder.view.setOnClickListener { + onAppSelected(listOfServices[index]) } } @@ -95,6 +98,8 @@ class AppAdapter( * Holder for binding views in the [RecyclerView]- */ class Holder(view: View, val favRenderer: FavoritesRenderer) : RecyclerView.ViewHolder(view) { + val view: View = itemView + private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon) private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title) private val favorites: TextView = itemView.requireViewById(R.id.favorites) @@ -106,7 +111,11 @@ class AppAdapter( fun bindData(data: ControlsServiceInfo) { icon.setImageDrawable(data.loadIcon()) title.text = data.loadLabel() - val text = favRenderer.renderFavoritesForComponent(data.componentName) + val text = if (data.panelActivity == null) { + favRenderer.renderFavoritesForComponent(data.componentName) + } else { + null + } favorites.text = text favorites.visibility = if (text == null) View.GONE else View.VISIBLE } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 90bc5d0f8daa..3808e73ca085 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -17,6 +17,7 @@ package com.android.systemui.controls.management import android.app.ActivityOptions +import android.app.Dialog import android.content.ComponentName import android.content.Context import android.content.Intent @@ -31,12 +32,15 @@ import android.widget.TextView import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.activity.ComponentActivity +import androidx.annotation.VisibleForTesting import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R +import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.controls.panels.AuthorizedPanelsRepository import com.android.systemui.controls.ui.ControlsActivity -import com.android.systemui.controls.ui.ControlsUiController +import com.android.systemui.controls.ui.SelectedItem import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.settings.UserTracker @@ -52,7 +56,8 @@ open class ControlsProviderSelectorActivity @Inject constructor( private val listingController: ControlsListingController, private val controlsController: ControlsController, private val userTracker: UserTracker, - private val uiController: ControlsUiController + private val authorizedPanelsRepository: AuthorizedPanelsRepository, + private val panelConfirmationDialogFactory: PanelConfirmationDialogFactory ) : ComponentActivity() { companion object { @@ -72,6 +77,7 @@ open class ControlsProviderSelectorActivity @Inject constructor( } } } + private var dialog: Dialog? = null private val mOnBackInvokedCallback = OnBackInvokedCallback { if (DEBUG) { @@ -138,9 +144,11 @@ open class ControlsProviderSelectorActivity @Inject constructor( lifecycle, listingController, LayoutInflater.from(this), - ::launchFavoritingActivity, + ::onAppSelected, FavoritesRenderer(resources, controlsController::countFavoritesForComponent), - resources).apply { + resources, + authorizedPanelsRepository.getAuthorizedPanels() + ).apply { registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { var hasAnimated = false override fun onChanged() { @@ -167,13 +175,35 @@ open class ControlsProviderSelectorActivity @Inject constructor( Log.d(TAG, "Unregistered onBackInvokedCallback") } onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback) + dialog?.cancel() + } + + fun onAppSelected(serviceInfo: ControlsServiceInfo) { + dialog?.cancel() + if (serviceInfo.panelActivity == null) { + launchFavoritingActivity(serviceInfo.componentName) + } else { + val appName = serviceInfo.loadLabel() ?: "" + dialog = panelConfirmationDialogFactory.createConfirmationDialog(this, appName) { ok -> + if (ok) { + authorizedPanelsRepository.addAuthorizedPanels( + setOf(serviceInfo.componentName.packageName) + ) + val selected = SelectedItem.PanelItem(appName, componentName) + controlsController.setPreferredSelection(selected) + animateExitAndFinish() + openControlsOrigin() + } + dialog = null + }.also { it.show() } + } } /** * Launch the [ControlsFavoritingActivity] for the specified component. * @param component a component name for a [ControlsProviderService] */ - fun launchFavoritingActivity(component: ComponentName?) { + private fun launchFavoritingActivity(component: ComponentName?) { executor.execute { component?.let { val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java) @@ -194,7 +224,15 @@ open class ControlsProviderSelectorActivity @Inject constructor( super.onDestroy() } - private fun animateExitAndFinish() { + private fun openControlsOrigin() { + startActivity( + Intent(applicationContext, ControlsActivity::class.java), + ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + ) + } + + @VisibleForTesting + internal open fun animateExitAndFinish() { val rootView = requireViewById<ViewGroup>(R.id.controls_management_root) ControlsAnimations.exitAnimation( rootView, diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/PanelConfirmationDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/controls/management/PanelConfirmationDialogFactory.kt new file mode 100644 index 000000000000..6f87aa996f94 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/PanelConfirmationDialogFactory.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.controls.management + +import android.app.Dialog +import android.content.Context +import android.content.DialogInterface +import com.android.systemui.R +import com.android.systemui.statusbar.phone.SystemUIDialog +import java.util.function.Consumer +import javax.inject.Inject + +/** + * Factory to create dialogs for consenting to show app panels for specific apps. + * + * [internalDialogFactory] is for facilitating testing. + */ +class PanelConfirmationDialogFactory( + private val internalDialogFactory: (Context) -> SystemUIDialog +) { + @Inject constructor() : this({ SystemUIDialog(it) }) + + /** + * Creates a dialog to show to the user. [response] will be true if an only if the user responds + * affirmatively. + */ + fun createConfirmationDialog( + context: Context, + appName: CharSequence, + response: Consumer<Boolean> + ): Dialog { + val listener = + DialogInterface.OnClickListener { _, which -> + response.accept(which == DialogInterface.BUTTON_POSITIVE) + } + return internalDialogFactory(context).apply { + setTitle(this.context.getString(R.string.controls_panel_authorization_title, appName)) + setMessage(this.context.getString(R.string.controls_panel_authorization, appName)) + setCanceledOnTouchOutside(true) + setOnCancelListener { response.accept(false) } + setPositiveButton(R.string.controls_dialog_ok, listener) + setNeutralButton(R.string.cancel, listener) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt new file mode 100644 index 000000000000..3e672f391e81 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.controls.panels + +/** + * Repository for keeping track of which packages the panel has authorized to show control panels + * (embedded activity). + */ +interface AuthorizedPanelsRepository { + + /** A set of package names that the user has previously authorized to show panels. */ + fun getAuthorizedPanels(): Set<String> + + /** Adds [packageNames] to the set of packages that the user has authorized to show panels. */ + fun addAuthorizedPanels(packageNames: Set<String>) +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt new file mode 100644 index 000000000000..f7e43a77b573 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.controls.panels + +import android.content.Context +import android.content.SharedPreferences +import com.android.systemui.R +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl +import javax.inject.Inject + +class AuthorizedPanelsRepositoryImpl +@Inject +constructor( + private val context: Context, + private val userFileManager: UserFileManager, + private val userTracker: UserTracker +) : AuthorizedPanelsRepository { + + override fun getAuthorizedPanels(): Set<String> { + return getAuthorizedPanelsInternal(instantiateSharedPrefs()) + } + + override fun addAuthorizedPanels(packageNames: Set<String>) { + addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames) + } + + private fun getAuthorizedPanelsInternal(sharedPreferences: SharedPreferences): Set<String> { + return sharedPreferences.getStringSet(KEY, emptySet())!! + } + + private fun addAuthorizedPanelsInternal( + sharedPreferences: SharedPreferences, + packageNames: Set<String> + ) { + val currentSet = getAuthorizedPanelsInternal(sharedPreferences) + sharedPreferences.edit().putStringSet(KEY, currentSet + packageNames).apply() + } + + private fun instantiateSharedPrefs(): SharedPreferences { + val sharedPref = + userFileManager.getSharedPreferences( + DeviceControlsControllerImpl.PREFS_CONTROLS_FILE, + Context.MODE_PRIVATE, + userTracker.userId, + ) + + // If we've never run this (i.e., the key doesn't exist), add the default packages + if (sharedPref.getStringSet(KEY, null) == null) { + sharedPref + .edit() + .putStringSet( + KEY, + context.resources + .getStringArray(R.array.config_controlsPreferredPackages) + .toSet() + ) + .apply() + } + return sharedPref + } + + companion object { + private const val KEY = "authorized_panels" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index f5c5905779a1..c1cec9dd0f94 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -58,6 +58,8 @@ interface ControlsUiController { * This element will be the one that appears when the user first opens the controls activity. */ fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem + + fun updatePreferences(selectedItem: SelectedItem) } sealed class SelectedItem { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 6289788f650a..9e71bef016bb 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -25,6 +25,7 @@ import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.service.controls.Control @@ -38,6 +39,7 @@ import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.widget.AdapterView import android.widget.ArrayAdapter +import android.widget.BaseAdapter import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout @@ -60,10 +62,13 @@ import com.android.systemui.controls.management.ControlsEditingActivity import com.android.systemui.controls.management.ControlsFavoritingActivity import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsProviderSelectorActivity +import com.android.systemui.controls.panels.AuthorizedPanelsRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.globalactions.GlobalActionsPopupMenu import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserFileManager @@ -87,6 +92,7 @@ private data class ControlKey(val componentName: ComponentName, val controlId: S class ControlsUiControllerImpl @Inject constructor ( val controlsController: Lazy<ControlsController>, val context: Context, + private val packageManager: PackageManager, @Main val uiExecutor: DelayableExecutor, @Background val bgExecutor: DelayableExecutor, val controlsListingController: Lazy<ControlsListingController>, @@ -99,6 +105,8 @@ class ControlsUiControllerImpl @Inject constructor ( private val userTracker: UserTracker, private val taskViewFactory: Optional<TaskViewFactory>, private val controlsSettingsRepository: ControlsSettingsRepository, + private val authorizedPanelsRepository: AuthorizedPanelsRepository, + private val featureFlags: FeatureFlags, dumpManager: DumpManager ) : ControlsUiController, Dumpable { @@ -108,6 +116,11 @@ class ControlsUiControllerImpl @Inject constructor ( private const val PREF_IS_PANEL = "controls_is_panel" private const val FADE_IN_MILLIS = 200L + + private const val OPEN_APP_ID = 0L + private const val ADD_CONTROLS_ID = 1L + private const val ADD_APP_ID = 2L + private const val EDIT_CONTROLS_ID = 3L } private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION @@ -135,6 +148,9 @@ class ControlsUiControllerImpl @Inject constructor ( it.getTitle() } + private var openAppIntent: Intent? = null + private var overflowMenuAdapter: BaseAdapter? = null + private val onSeedingComplete = Consumer<Boolean> { accepted -> if (accepted) { @@ -160,6 +176,7 @@ class ControlsUiControllerImpl @Inject constructor ( ): ControlsListingController.ControlsListingCallback { return object : ControlsListingController.ControlsListingCallback { override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { + val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels() val lastItems = serviceInfos.map { val uid = it.serviceInfo.applicationInfo.uid @@ -169,7 +186,11 @@ class ControlsUiControllerImpl @Inject constructor ( it.loadIcon(), it.componentName, uid, - it.panelActivity + if (it.componentName.packageName in authorizedPanels) { + it.panelActivity + } else { + null + } ) } uiExecutor.execute { @@ -206,6 +227,8 @@ class ControlsUiControllerImpl @Inject constructor ( this.parent = parent this.onDismiss = onDismiss this.activityContext = activityContext + this.openAppIntent = null + this.overflowMenuAdapter = null hidden = false retainCache = false @@ -296,6 +319,12 @@ class ControlsUiControllerImpl @Inject constructor ( startTargetedActivity(si, ControlsEditingActivity::class.java) } + private fun startDefaultActivity() { + openAppIntent?.let { + startActivity(it, animateExtra = false) + } + } + private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) { val i = Intent(activityContext, klazz) putIntentExtras(i, si) @@ -319,9 +348,11 @@ class ControlsUiControllerImpl @Inject constructor ( startActivity(i) } - private fun startActivity(intent: Intent) { + private fun startActivity(intent: Intent, animateExtra: Boolean = true) { // Force animations when transitioning from a dialog to an activity - intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true) + if (animateExtra) { + intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true) + } if (keyguardStateController.isShowing()) { activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */) @@ -373,8 +404,31 @@ class ControlsUiControllerImpl @Inject constructor ( Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem") } + bgExecutor.execute { + val intent = Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_LAUNCHER) + .setPackage(selectionItem.componentName.packageName) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) + val intents = packageManager + .queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(0L)) + intents.firstOrNull { it.activityInfo.exported }?.let { resolved -> + intent.setPackage(null) + intent.setComponent(resolved.activityInfo.componentName) + openAppIntent = intent + parent.post { + // This will call show on the PopupWindow in the same thread, so make sure this + // happens in the view thread. + overflowMenuAdapter?.notifyDataSetChanged() + } + } + } createDropDown(panelsAndStructures, selectionItem) - createMenu() + + val currentApps = panelsAndStructures.map { it.componentName }.toSet() + val allApps = controlsListingController.get() + .getCurrentServices().map { it.componentName }.toSet() + createMenu(extraApps = (allApps - currentApps).isNotEmpty()) } private fun createPanelView(componentName: ComponentName) { @@ -413,22 +467,41 @@ class ControlsUiControllerImpl @Inject constructor ( } } - private fun createMenu() { + private fun createMenu(extraApps: Boolean) { val isPanel = selectedItem is SelectedItem.PanelItem val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure ?: EMPTY_STRUCTURE + val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS) + + val items = buildList { + add(OverflowMenuAdapter.MenuItem( + context.getText(R.string.controls_open_app), + OPEN_APP_ID + )) + if (newFlows || isPanel) { + if (extraApps) { + add(OverflowMenuAdapter.MenuItem( + context.getText(R.string.controls_menu_add_another_app), + ADD_APP_ID + )) + } + } else { + add(OverflowMenuAdapter.MenuItem( + context.getText(R.string.controls_menu_add), + ADD_CONTROLS_ID + )) + } + if (!isPanel) { + add(OverflowMenuAdapter.MenuItem( + context.getText(R.string.controls_menu_edit), + EDIT_CONTROLS_ID + )) + } + } - val items = if (isPanel) { - arrayOf( - context.resources.getString(R.string.controls_menu_add), - ) - } else { - arrayOf( - context.resources.getString(R.string.controls_menu_add), - context.resources.getString(R.string.controls_menu_edit) - ) + val adapter = OverflowMenuAdapter(context, R.layout.controls_more_item, items) { position -> + getItemId(position) != OPEN_APP_ID || openAppIntent != null } - var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items) val anchor = parent.requireViewById<ImageView>(R.id.controls_more) anchor.setOnClickListener(object : View.OnClickListener { @@ -446,25 +519,21 @@ class ControlsUiControllerImpl @Inject constructor ( pos: Int, id: Long ) { - when (pos) { - // 0: Add Control - 0 -> { - if (isPanel) { - startProviderSelectorActivity() - } else { - startFavoritingActivity(selectedStructure) - } - } - // 1: Edit controls - 1 -> startEditingActivity(selectedStructure) + when (id) { + OPEN_APP_ID -> startDefaultActivity() + ADD_APP_ID -> startProviderSelectorActivity() + ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure) + EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure) } dismiss() } }) show() + listView?.post { listView?.requestAccessibilityFocus() } } } }) + overflowMenuAdapter = adapter } private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) { @@ -526,6 +595,7 @@ class ControlsUiControllerImpl @Inject constructor ( } }) show() + listView?.post { listView?.requestAccessibilityFocus() } } } }) @@ -610,12 +680,12 @@ class ControlsUiControllerImpl @Inject constructor ( } } - private fun updatePreferences(si: SelectedItem) { + override fun updatePreferences(selectedItem: SelectedItem) { sharedPreferences.edit() - .putString(PREF_COMPONENT, si.componentName.flattenToString()) - .putString(PREF_STRUCTURE_OR_APP_NAME, si.name.toString()) - .putBoolean(PREF_IS_PANEL, si is SelectedItem.PanelItem) - .commit() + .putString(PREF_COMPONENT, selectedItem.componentName.flattenToString()) + .putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString()) + .putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem) + .apply() } private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/OverflowMenuAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/OverflowMenuAdapter.kt new file mode 100644 index 000000000000..6b84e360eb80 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/OverflowMenuAdapter.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.controls.ui + +import android.content.Context +import android.widget.ArrayAdapter +import androidx.annotation.LayoutRes + +open class OverflowMenuAdapter( + context: Context, + @LayoutRes layoutId: Int, + itemsWithIds: List<MenuItem>, + private val isEnabledInternal: OverflowMenuAdapter.(Int) -> Boolean +) : ArrayAdapter<CharSequence>(context, layoutId, itemsWithIds.map(MenuItem::text)) { + + private val ids = itemsWithIds.map(MenuItem::id) + + override fun getItemId(position: Int): Long { + return ids[position] + } + + override fun isEnabled(position: Int): Boolean { + return isEnabledInternal(position) + } + + data class MenuItem(val text: CharSequence, val id: Long) +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 8e9992fdd296..2dfcf70177fe 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -27,9 +27,11 @@ import com.android.systemui.accessibility.WindowMagnification import com.android.systemui.biometrics.AuthController import com.android.systemui.clipboardoverlay.ClipboardListener import com.android.systemui.dagger.qualifiers.PerUser +import com.android.systemui.dreams.DreamMonitor import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.keyboard.KeyboardUI import com.android.systemui.keyguard.KeyguardViewMediator +import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable import com.android.systemui.log.SessionTracker import com.android.systemui.media.dialog.MediaOutputSwitcherDialogUI import com.android.systemui.media.RingtonePlayer @@ -286,4 +288,18 @@ abstract class SystemUICoreStartableModule { @IntoMap @ClassKey(StylusUsiPowerStartable::class) abstract fun bindStylusUsiPowerStartable(sysui: StylusUsiPowerStartable): CoreStartable + + /** Inject into MuteQuickAffordanceCoreStartable*/ + @Binds + @IntoMap + @ClassKey(MuteQuickAffordanceCoreStartable::class) + abstract fun bindMuteQuickAffordanceCoreStartable( + sysui: MuteQuickAffordanceCoreStartable + ): CoreStartable + + /**Inject into DreamMonitor */ + @Binds + @IntoMap + @ClassKey(DreamMonitor::class) + abstract fun bindDreamMonitor(sysui: DreamMonitor): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 2d0dfa1d921b..9ad7b8c7104b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -63,6 +63,7 @@ import com.android.systemui.qs.footer.dagger.FooterActionsModule; import com.android.systemui.recents.Recents; import com.android.systemui.screenshot.dagger.ScreenshotModule; import com.android.systemui.security.data.repository.SecurityRepositoryModule; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.dagger.MultiUserUtilsModule; import com.android.systemui.shade.ShadeController; import com.android.systemui.smartspace.dagger.SmartspaceModule; @@ -107,6 +108,8 @@ import com.android.wm.shell.bubbles.Bubbles; import java.util.Optional; import java.util.concurrent.Executor; +import javax.inject.Named; + import dagger.Binds; import dagger.BindsOptionalOf; import dagger.Module; @@ -196,8 +199,8 @@ public abstract class SystemUIModule { @SysUISingleton @Provides - static SysUiState provideSysUiState(DumpManager dumpManager) { - final SysUiState state = new SysUiState(); + static SysUiState provideSysUiState(DisplayTracker displayTracker, DumpManager dumpManager) { + final SysUiState state = new SysUiState(displayTracker); dumpManager.registerDumpable(state); return state; } @@ -215,6 +218,10 @@ public abstract class SystemUIModule { abstract BcSmartspaceConfigPlugin optionalBcSmartspaceConfigPlugin(); @BindsOptionalOf + @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN) + abstract BcSmartspaceDataPlugin optionalWeatherSmartspaceConfigPlugin(); + + @BindsOptionalOf abstract Recents optionalRecents(); @BindsOptionalOf diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java new file mode 100644 index 000000000000..102f2082ebd1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams; + +import android.util.Log; + +import com.android.systemui.CoreStartable; +import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback; +import com.android.systemui.dreams.conditions.DreamCondition; +import com.android.systemui.shared.condition.Monitor; + +import javax.inject.Inject; + +/** + * A {@link CoreStartable} to retain a monitor for tracking dreaming. + */ +public class DreamMonitor implements CoreStartable { + private static final String TAG = "DreamMonitor"; + + // We retain a reference to the monitor so it is not garbage-collected. + private final Monitor mConditionMonitor; + private final DreamCondition mDreamCondition; + private final DreamStatusBarStateCallback mCallback; + + + @Inject + public DreamMonitor(Monitor monitor, DreamCondition dreamCondition, + DreamStatusBarStateCallback callback) { + mConditionMonitor = monitor; + mDreamCondition = dreamCondition; + mCallback = callback; + + } + @Override + public void start() { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "started"); + } + + mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback) + .addCondition(mDreamCondition) + .build()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index c882f8adceb6..c3bd5d96590e 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -182,6 +182,18 @@ constructor( } } + /** + * Ends the dream content and dream overlay animations, if they're currently running. + * @see [AnimatorSet.end] + */ + fun endAnimations() { + mAnimator = + mAnimator?.let { + it.end() + null + } + } + private fun blurAnimator( view: View, fromBlurRadius: Float, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 33c8379d2e5c..50cfb6a905c9 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -37,11 +37,11 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.complication.ComplicationHostViewController; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.dagger.DreamOverlayModule; +import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; +import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.statusbar.BlurUtils; -import com.android.systemui.statusbar.phone.KeyguardBouncer; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -56,7 +56,6 @@ import javax.inject.Named; @DreamOverlayComponent.DreamOverlayScope public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> { private final DreamOverlayStatusBarViewController mStatusBarViewController; - private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final BlurUtils mBlurUtils; private final DreamOverlayAnimationsController mDreamOverlayAnimationsController; private final DreamOverlayStateController mStateController; @@ -85,6 +84,22 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve private long mJitterStartTimeMillis; private boolean mBouncerAnimating; + private boolean mWakingUpFromSwipe; + + private final BouncerlessScrimController mBouncerlessScrimController; + + private final BouncerlessScrimController.Callback mBouncerlessExpansionCallback = + new BouncerlessScrimController.Callback() { + @Override + public void onExpansion(ShadeExpansionChangeEvent event) { + updateTransitionState(event.getFraction()); + } + + @Override + public void onWakeup() { + mWakingUpFromSwipe = true; + } + }; private final PrimaryBouncerExpansionCallback mBouncerExpansionCallback = @@ -127,13 +142,29 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve } }; + /** + * If true, overlay entry animations should be skipped once. + * + * This is turned on when exiting low light and should be turned off once the entry animations + * are skipped once. + */ + private boolean mSkipEntryAnimations; + + private final DreamOverlayStateController.Callback + mDreamOverlayStateCallback = + new DreamOverlayStateController.Callback() { + @Override + public void onExitLowLight() { + mSkipEntryAnimations = true; + } + }; + @Inject public DreamOverlayContainerViewController( DreamOverlayContainerView containerView, ComplicationHostViewController complicationHostViewController, @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView, DreamOverlayStatusBarViewController statusBarViewController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, BlurUtils blurUtils, @Main Handler handler, @Main Resources resources, @@ -143,15 +174,18 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter, PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor, DreamOverlayAnimationsController animationsController, - DreamOverlayStateController stateController) { + DreamOverlayStateController stateController, + BouncerlessScrimController bouncerlessScrimController) { super(containerView); mDreamOverlayContentView = contentView; mStatusBarViewController = statusBarViewController; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mBlurUtils = blurUtils; mDreamOverlayAnimationsController = animationsController; mStateController = stateController; + mBouncerlessScrimController = bouncerlessScrimController; + mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback); + mComplicationHostViewController = complicationHostViewController; mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize( R.dimen.dream_overlay_y_offset); @@ -170,6 +204,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve @Override protected void onInit() { + mStateController.addCallback(mDreamOverlayStateCallback); mStatusBarViewController.init(); mComplicationHostViewController.init(); mDreamOverlayAnimationsController.init(mView); @@ -177,27 +212,27 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve @Override protected void onViewAttached() { + mWakingUpFromSwipe = false; mJitterStartTimeMillis = System.currentTimeMillis(); mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval); - final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer(); - if (bouncer != null) { - bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback); - } mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback); // Start dream entry animations. Skip animations for low light clock. if (!mStateController.isLowLightActive()) { mDreamOverlayAnimationsController.startEntryAnimations(); + + if (mSkipEntryAnimations) { + // If we're transitioning from the low light dream back to the user dream, skip the + // overlay animations and show immediately. + mDreamOverlayAnimationsController.endAnimations(); + mSkipEntryAnimations = false; + } } } @Override protected void onViewDetached() { mHandler.removeCallbacks(this::updateBurnInOffsets); - final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer(); - if (bouncer != null) { - bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback); - } mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback); mDreamOverlayAnimationsController.cancelAnimations(); @@ -266,6 +301,13 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve */ public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull DelayableExecutor callbackExecutor) { + // When swiping causes wakeup, do not run any animations as the dream should exit as soon + // as possible. + if (mWakingUpFromSwipe) { + onAnimationEnd.run(); + return; + } + mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index ccfdd0966e98..2c7ecb1182f2 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -83,6 +83,12 @@ public class DreamOverlayStateController implements */ default void onAvailableComplicationTypesChanged() { } + + /** + * Called when the low light dream is exiting and transitioning back to the user dream. + */ + default void onExitLowLight() { + } } private final Executor mExecutor; @@ -278,6 +284,10 @@ public class DreamOverlayStateController implements * @param active {@code true} if low light mode is active, {@code false} otherwise. */ public void setLowLightActive(boolean active) { + if (isLowLightActive() && !active) { + // Notify that we're exiting low light only on the transition from active to not active. + mCallbacks.forEach(Callback::onExitLowLight); + } modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_LOW_LIGHT_ACTIVE); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/callbacks/DreamStatusBarStateCallback.java b/packages/SystemUI/src/com/android/systemui/dreams/callbacks/DreamStatusBarStateCallback.java new file mode 100644 index 000000000000..c8c9470f9bf2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/callbacks/DreamStatusBarStateCallback.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.dreams.callbacks; + +import android.util.Log; + +import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.statusbar.SysuiStatusBarStateController; + +import javax.inject.Inject; + +/** + * A callback that informs {@link SysuiStatusBarStateController} when the dream state has changed. + */ +public class DreamStatusBarStateCallback implements Monitor.Callback { + private static final String TAG = "DreamStatusBarCallback"; + + private final SysuiStatusBarStateController mStateController; + + @Inject + public DreamStatusBarStateCallback(SysuiStatusBarStateController statusBarStateController) { + mStateController = statusBarStateController; + } + + @Override + public void onConditionsChanged(boolean allConditionsMet) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onConditionChanged:" + allConditionsMet); + } + + mStateController.setIsDreaming(allConditionsMet); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java new file mode 100644 index 000000000000..2befce7065ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.dreams.conditions; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.text.TextUtils; + +import com.android.systemui.shared.condition.Condition; + +import javax.inject.Inject; + +/** + * {@link DreamCondition} provides a signal when a dream begins and ends. + */ +public class DreamCondition extends Condition { + private final Context mContext; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + processIntent(intent); + } + }; + + @Inject + public DreamCondition(Context context) { + mContext = context; + } + + private void processIntent(Intent intent) { + // In the case of a non-existent sticky broadcast, ignore when there is no intent. + if (intent == null) { + return; + } + if (TextUtils.equals(intent.getAction(), Intent.ACTION_DREAMING_STARTED)) { + updateCondition(true); + } else if (TextUtils.equals(intent.getAction(), Intent.ACTION_DREAMING_STOPPED)) { + updateCondition(false); + } else { + throw new IllegalStateException("unexpected intent:" + intent); + } + } + + @Override + protected void start() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_DREAMING_STARTED); + filter.addAction(Intent.ACTION_DREAMING_STOPPED); + final Intent stickyIntent = mContext.registerReceiver(mReceiver, filter); + processIntent(stickyIntent); + } + + @Override + protected void stop() { + mContext.unregisterReceiver(mReceiver); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index e7b29bb84b3d..0ab8c8e42b03 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -28,6 +28,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.DreamOverlayNotificationCountProvider; import com.android.systemui.dreams.DreamOverlayService; import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule; +import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule; import java.util.Optional; @@ -42,6 +43,7 @@ import dagger.Provides; @Module(includes = { RegisteredComplicationsModule.class, LowLightDreamModule.class, + ScrimModule.class }, subcomponents = { DreamOverlayComponent.class, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java index 44207f4aecf5..73c2289ad6bd 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java @@ -36,11 +36,12 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.dreams.touch.scrim.ScrimController; +import com.android.systemui.dreams.touch.scrim.ScrimManager; import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.wm.shell.animation.FlingAnimationUtils; import java.util.Optional; @@ -78,7 +79,8 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { private final NotificationShadeWindowController mNotificationShadeWindowController; private final float mBouncerZoneScreenPercentage; - private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final ScrimManager mScrimManager; + private ScrimController mCurrentScrimController; private float mCurrentExpansion; private final Optional<CentralSurfaces> mCentralSurfaces; @@ -90,6 +92,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { private final DisplayMetrics mDisplayMetrics; private Boolean mCapture; + private Boolean mExpanded; private boolean mBouncerInitiallyShowing; @@ -101,6 +104,17 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { private final UiEventLogger mUiEventLogger; + private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() { + @Override + public void onScrimControllerChanged(ScrimController controller) { + if (mCurrentScrimController != null) { + mCurrentScrimController.reset(); + } + + mCurrentScrimController = controller; + } + }; + private final GestureDetector.OnGestureListener mOnGestureListener = new GestureDetector.SimpleOnGestureListener() { @Override @@ -115,8 +129,10 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { .orElse(false); if (mCapture) { + // reset expanding + mExpanded = false; // Since the user is dragging the bouncer up, set scrimmed to false. - mStatusBarKeyguardViewManager.showPrimaryBouncer(false); + mCurrentScrimController.show(); } } @@ -157,10 +173,10 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { ShadeExpansionChangeEvent event = new ShadeExpansionChangeEvent( /* fraction= */ mCurrentExpansion, - /* expanded= */ false, + /* expanded= */ mExpanded, /* tracking= */ true, /* dragDownPxAmount= */ dragDownAmount); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(event); + mCurrentScrimController.expand(event); } @@ -187,7 +203,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { @Inject public BouncerSwipeTouchHandler( DisplayMetrics displayMetrics, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, + ScrimManager scrimManager, Optional<CentralSurfaces> centralSurfaces, NotificationShadeWindowController notificationShadeWindowController, ValueAnimatorCreator valueAnimatorCreator, @@ -200,7 +216,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { UiEventLogger uiEventLogger) { mDisplayMetrics = displayMetrics; mCentralSurfaces = centralSurfaces; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mScrimManager = scrimManager; mNotificationShadeWindowController = notificationShadeWindowController; mBouncerZoneScreenPercentage = swipeRegionPercentage; mFlingAnimationUtils = flingAnimationUtils; @@ -234,9 +250,12 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { mTouchSession = session; mVelocityTracker.clear(); mNotificationShadeWindowController.setForcePluginOpen(true, this); + mScrimManager.addCallback(mScrimManagerCallback); + mCurrentScrimController = mScrimManager.getCurrentController(); session.registerCallback(() -> { mVelocityTracker.recycle(); + mScrimManager.removeCallback(mScrimManagerCallback); mCapture = null; mNotificationShadeWindowController.setForcePluginOpen(false, this); }); @@ -273,9 +292,10 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { final float velocityVector = (float) Math.hypot(horizontalVelocity, verticalVelocity); - final float expansion = flingRevealsOverlay(verticalVelocity, velocityVector) - ? KeyguardBouncerConstants.EXPANSION_HIDDEN - : KeyguardBouncerConstants.EXPANSION_VISIBLE; + mExpanded = !flingRevealsOverlay(verticalVelocity, velocityVector); + final float expansion = mExpanded + ? KeyguardBouncerConstants.EXPANSION_VISIBLE + : KeyguardBouncerConstants.EXPANSION_HIDDEN; // Log the swiping up to show Bouncer event. if (!mBouncerInitiallyShowing @@ -286,7 +306,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { flingToExpansion(verticalVelocity, expansion); if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) { - mStatusBarKeyguardViewManager.reset(false); + mCurrentScrimController.reset(); } break; default: diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java index 695b59ac45ad..b8b459e1c68c 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java @@ -226,6 +226,15 @@ public class DreamOverlayTouchMonitor { return; } + // When we stop monitoring touches, we must ensure that all active touch sessions and + // descendants informed of the removal so any cleanup for active tracking can proceed. + mExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> { + while (touchSession != null) { + touchSession.onRemoved(); + touchSession = touchSession.getPredecessor(); + } + })); + mCurrentInputSession.dispose(); mCurrentInputSession = null; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java index 43827573470f..e1d03392044a 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java @@ -21,10 +21,10 @@ import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_O import android.os.Looper; import android.view.Choreographer; -import android.view.Display; import android.view.GestureDetector; import android.view.MotionEvent; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; @@ -55,8 +55,9 @@ public class InputSession { public InputSession(@Named(INPUT_SESSION_NAME) String sessionName, InputChannelCompat.InputEventListener inputEventListener, GestureDetector.OnGestureListener gestureListener, + DisplayTracker displayTracker, @Named(PILFER_ON_GESTURE_CONSUME) boolean pilferOnGestureConsume) { - mInputMonitor = new InputMonitorCompat(sessionName, Display.DEFAULT_DISPLAY); + mInputMonitor = new InputMonitorCompat(sessionName, displayTracker.getDefaultDisplayId()); mGestureDetector = new GestureDetector(gestureListener); mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(), diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java new file mode 100644 index 000000000000..f5bbba780b27 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.touch.scrim; + +import com.android.systemui.shade.ShadeExpansionChangeEvent; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; + +import javax.inject.Inject; + +/** + * Implementation for handling swipe movements on the overlay when the keyguard is present. + */ +public class BouncerScrimController implements ScrimController { + private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + + @Inject + BouncerScrimController(StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + } + + @Override + public void show() { + mStatusBarKeyguardViewManager.showBouncer(false); + } + + @Override + public void expand(ShadeExpansionChangeEvent event) { + mStatusBarKeyguardViewManager.onPanelExpansionChanged(event); + } + + @Override + public void reset() { + mStatusBarKeyguardViewManager.reset(false); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java new file mode 100644 index 000000000000..01e4d04dcc2c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.touch.scrim; + +import android.os.PowerManager; +import android.os.SystemClock; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.shade.ShadeExpansionChangeEvent; +import com.android.systemui.unfold.util.CallbackController; + +import java.util.HashSet; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * {@link BouncerlessScrimController} handles scrim progression when no keyguard is set. When + * fully expanded, the controller dismisses the dream. + */ +@SysUISingleton +public class BouncerlessScrimController implements ScrimController, + CallbackController<BouncerlessScrimController.Callback> { + private static final String TAG = "BLScrimController"; + + /** + * {@link Callback} allows {@link BouncerlessScrimController} clients to be informed of + * expansion progression and wakeup + */ + public interface Callback { + /** + * Invoked when there is a change to the scrim expansion. + */ + void onExpansion(ShadeExpansionChangeEvent event); + + /** + * Invoked after {@link BouncerlessScrimController} has started waking up the device. + */ + void onWakeup(); + } + + private final Executor mExecutor; + private final PowerManager mPowerManager; + + @Override + public void addCallback(Callback listener) { + mExecutor.execute(() -> mCallbacks.add(listener)); + } + + @Override + public void removeCallback(Callback listener) { + mExecutor.execute(() -> mCallbacks.remove(listener)); + } + + private final HashSet<Callback> mCallbacks; + + + @Inject + public BouncerlessScrimController(@Main Executor executor, + PowerManager powerManager) { + mExecutor = executor; + mPowerManager = powerManager; + mCallbacks = new HashSet<>(); + } + + @Override + public void expand(ShadeExpansionChangeEvent event) { + if (event.getExpanded()) { + mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, + "com.android.systemui:SwipeUp"); + mExecutor.execute(() -> mCallbacks.forEach(callback -> callback.onWakeup())); + } else { + mExecutor.execute(() -> mCallbacks.forEach(callback -> callback.onExpansion(event))); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java new file mode 100644 index 000000000000..61629ef79637 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.touch.scrim; + +import com.android.systemui.shade.ShadeExpansionChangeEvent; + +/** + * {@link ScrimController} provides an interface for the different consumers of scrolling/expansion + * events over the dream. + */ +public interface ScrimController { + /** + * Called at the start of expansion before any expansion amount updates. + */ + default void show() { + } + + /** + * Called for every expansion update. + * @param event {@link ShadeExpansionChangeEvent} detailing the change. + */ + default void expand(ShadeExpansionChangeEvent event) { + } + + /** + * Called at the end of the movement. + */ + default void reset() { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java new file mode 100644 index 000000000000..0d0dff6c8fb0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.touch.scrim; + +import static com.android.systemui.dreams.touch.scrim.dagger.ScrimModule.BOUNCERLESS_SCRIM_CONTROLLER; +import static com.android.systemui.dreams.touch.scrim.dagger.ScrimModule.BOUNCER_SCRIM_CONTROLLER; + +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +import java.util.HashSet; +import java.util.concurrent.Executor; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * {@link ScrimManager} helps manage multiple {@link ScrimController} instances, specifying the + * appropriate one to use at the current moment and managing the handoff between controllers. + */ +public class ScrimManager { + private final ScrimController mBouncerScrimController; + private final ScrimController mBouncerlessScrimController; + private final KeyguardStateController mKeyguardStateController; + private final Executor mExecutor; + + private ScrimController mCurrentController; + private final HashSet<Callback> mCallbacks; + + /** + * Interface implemented for receiving updates to the active {@link ScrimController}. + */ + public interface Callback { + /** + * Invoked when the controller changes. + * @param controller The currently active {@link ScrimController}. + */ + void onScrimControllerChanged(ScrimController controller); + } + + private final KeyguardStateController.Callback mKeyguardStateCallback = + new KeyguardStateController.Callback() { + @Override + public void onKeyguardShowingChanged() { + mExecutor.execute(() -> updateController()); + } + }; + + @Inject + ScrimManager(@Main Executor executor, + @Named(BOUNCER_SCRIM_CONTROLLER) ScrimController bouncerScrimController, + @Named(BOUNCERLESS_SCRIM_CONTROLLER)ScrimController bouncerlessScrimController, + KeyguardStateController keyguardStateController) { + mExecutor = executor; + mCallbacks = new HashSet<>(); + mBouncerlessScrimController = bouncerlessScrimController; + mBouncerScrimController = bouncerScrimController; + mKeyguardStateController = keyguardStateController; + + mKeyguardStateController.addCallback(mKeyguardStateCallback); + updateController(); + } + + private void updateController() { + final ScrimController existingController = mCurrentController; + mCurrentController = mKeyguardStateController.canDismissLockScreen() + ? mBouncerlessScrimController + : mBouncerScrimController; + + if (existingController == mCurrentController) { + return; + } + + mCallbacks.forEach(callback -> callback.onScrimControllerChanged(mCurrentController)); + } + + /** + * Adds a {@link Callback} to receive future changes to the active {@link ScrimController}. + */ + public void addCallback(Callback callback) { + mExecutor.execute(() -> mCallbacks.add(callback)); + } + + /** + * Removes the {@link Callback} from receiving further updates. + */ + public void removeCallback(Callback callback) { + mExecutor.execute(() -> mCallbacks.remove(callback)); + } + + /** + * Returns the currently get {@link ScrimController}. + * @return + */ + public ScrimController getCurrentController() { + return mCurrentController; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java new file mode 100644 index 000000000000..40bc0ea6e95e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.touch.scrim.dagger; + +import com.android.systemui.dreams.touch.scrim.BouncerScrimController; +import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController; +import com.android.systemui.dreams.touch.scrim.ScrimController; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; + +/** + * Module for scrim related dependencies. + */ +@Module +public interface ScrimModule { + String BOUNCERLESS_SCRIM_CONTROLLER = "bouncerless_scrim_controller"; + String BOUNCER_SCRIM_CONTROLLER = "bouncer_scrim_controller"; + + /** */ + @Provides + @Named(BOUNCERLESS_SCRIM_CONTROLLER) + static ScrimController providesBouncerlessScrimController( + BouncerlessScrimController controller) { + return controller; + } + + /** */ + @Provides + @Named(BOUNCER_SCRIM_CONTROLLER) + static ScrimController providesBouncerScrimController( + BouncerScrimController controller) { + return controller; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java index d1a14a1ba65f..4bac69776319 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java @@ -19,7 +19,7 @@ package com.android.systemui.flags; import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS; import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG; import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS; -import static com.android.systemui.flags.FlagManager.EXTRA_ID; +import static com.android.systemui.flags.FlagManager.EXTRA_NAME; import static com.android.systemui.flags.FlagManager.EXTRA_VALUE; import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS; @@ -39,6 +39,7 @@ import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; import org.jetbrains.annotations.NotNull; @@ -72,14 +73,15 @@ public class FeatureFlagsDebug implements FeatureFlags { private final FlagManager mFlagManager; private final Context mContext; + private final GlobalSettings mGlobalSettings; private final SecureSettings mSecureSettings; private final Resources mResources; private final SystemPropertiesHelper mSystemProperties; private final ServerFlagReader mServerFlagReader; - private final Map<Integer, Flag<?>> mAllFlags; - private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>(); - private final Map<Integer, String> mStringFlagCache = new TreeMap<>(); - private final Map<Integer, Integer> mIntFlagCache = new TreeMap<>(); + private final Map<String, Flag<?>> mAllFlags; + private final Map<String, Boolean> mBooleanFlagCache = new TreeMap<>(); + private final Map<String, String> mStringFlagCache = new TreeMap<>(); + private final Map<String, Integer> mIntFlagCache = new TreeMap<>(); private final Restarter mRestarter; private final ServerFlagReader.ChangeListener mOnPropertiesChanged = @@ -94,14 +96,16 @@ public class FeatureFlagsDebug implements FeatureFlags { public FeatureFlagsDebug( FlagManager flagManager, Context context, + GlobalSettings globalSettings, SecureSettings secureSettings, SystemPropertiesHelper systemProperties, @Main Resources resources, ServerFlagReader serverFlagReader, - @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags, + @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags, Restarter restarter) { mFlagManager = flagManager; mContext = context; + mGlobalSettings = globalSettings; mSecureSettings = secureSettings; mResources = resources; mSystemProperties = systemProperties; @@ -133,96 +137,103 @@ public class FeatureFlagsDebug implements FeatureFlags { } private boolean isEnabledInternal(@NotNull BooleanFlag flag) { - int id = flag.getId(); - if (!mBooleanFlagCache.containsKey(id)) { - mBooleanFlagCache.put(id, + String name = flag.getName(); + if (!mBooleanFlagCache.containsKey(name)) { + mBooleanFlagCache.put(name, readBooleanFlagInternal(flag, flag.getDefault())); } - return mBooleanFlagCache.get(id); + return mBooleanFlagCache.get(name); } @Override public boolean isEnabled(@NonNull ResourceBooleanFlag flag) { - int id = flag.getId(); - if (!mBooleanFlagCache.containsKey(id)) { - mBooleanFlagCache.put(id, + String name = flag.getName(); + if (!mBooleanFlagCache.containsKey(name)) { + mBooleanFlagCache.put(name, readBooleanFlagInternal(flag, mResources.getBoolean(flag.getResourceId()))); } - return mBooleanFlagCache.get(id); + return mBooleanFlagCache.get(name); } @Override public boolean isEnabled(@NonNull SysPropBooleanFlag flag) { - int id = flag.getId(); - if (!mBooleanFlagCache.containsKey(id)) { + String name = flag.getName(); + if (!mBooleanFlagCache.containsKey(name)) { // Use #readFlagValue to get the default. That will allow it to fall through to // teamfood if need be. mBooleanFlagCache.put( - id, + name, mSystemProperties.getBoolean( flag.getName(), readBooleanFlagInternal(flag, flag.getDefault()))); } - return mBooleanFlagCache.get(id); + return mBooleanFlagCache.get(name); } @NonNull @Override public String getString(@NonNull StringFlag flag) { - int id = flag.getId(); - if (!mStringFlagCache.containsKey(id)) { - mStringFlagCache.put(id, - readFlagValueInternal(id, flag.getDefault(), StringFlagSerializer.INSTANCE)); + String name = flag.getName(); + if (!mStringFlagCache.containsKey(name)) { + mStringFlagCache.put(name, + readFlagValueInternal( + flag.getId(), name, flag.getDefault(), StringFlagSerializer.INSTANCE)); } - return mStringFlagCache.get(id); + return mStringFlagCache.get(name); } @NonNull @Override public String getString(@NonNull ResourceStringFlag flag) { - int id = flag.getId(); - if (!mStringFlagCache.containsKey(id)) { - mStringFlagCache.put(id, - readFlagValueInternal(id, mResources.getString(flag.getResourceId()), + String name = flag.getName(); + if (!mStringFlagCache.containsKey(name)) { + mStringFlagCache.put(name, + readFlagValueInternal( + flag.getId(), name, mResources.getString(flag.getResourceId()), StringFlagSerializer.INSTANCE)); } - return mStringFlagCache.get(id); + return mStringFlagCache.get(name); } @NonNull @Override public int getInt(@NonNull IntFlag flag) { - int id = flag.getId(); - if (!mIntFlagCache.containsKey(id)) { - mIntFlagCache.put(id, - readFlagValueInternal(id, flag.getDefault(), IntFlagSerializer.INSTANCE)); + String name = flag.getName(); + if (!mIntFlagCache.containsKey(name)) { + mIntFlagCache.put(name, + readFlagValueInternal( + flag.getId(), name, flag.getDefault(), IntFlagSerializer.INSTANCE)); } - return mIntFlagCache.get(id); + return mIntFlagCache.get(name); } @NonNull @Override public int getInt(@NonNull ResourceIntFlag flag) { - int id = flag.getId(); - if (!mIntFlagCache.containsKey(id)) { - mIntFlagCache.put(id, - readFlagValueInternal(id, mResources.getInteger(flag.getResourceId()), + String name = flag.getName(); + if (!mIntFlagCache.containsKey(name)) { + mIntFlagCache.put(name, + readFlagValueInternal( + flag.getId(), name, mResources.getInteger(flag.getResourceId()), IntFlagSerializer.INSTANCE)); } - return mIntFlagCache.get(id); + return mIntFlagCache.get(name); } /** Specific override for Boolean flags that checks against the teamfood list.*/ private boolean readBooleanFlagInternal(Flag<Boolean> flag, boolean defaultValue) { - Boolean result = readBooleanFlagOverride(flag.getId()); + Boolean result = readBooleanFlagOverride(flag.getName()); + if (result == null) { + result = readBooleanFlagOverride(flag.getId()); + } boolean hasServerOverride = mServerFlagReader.hasOverride( flag.getNamespace(), flag.getName()); @@ -231,7 +242,7 @@ public class FeatureFlagsDebug implements FeatureFlags { if (!hasServerOverride && !defaultValue && result == null - && flag.getId() != Flags.TEAMFOOD.getId() + && !flag.getName().equals(Flags.TEAMFOOD.getName()) && flag.getTeamfood()) { return isEnabled(Flags.TEAMFOOD); } @@ -244,16 +255,31 @@ public class FeatureFlagsDebug implements FeatureFlags { return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE); } + private Boolean readBooleanFlagOverride(String name) { + return readFlagValueInternal(name, BooleanFlagSerializer.INSTANCE); + } + + // TODO(b/265188950): Remove id from this method once ids are fully deprecated. @NonNull private <T> T readFlagValueInternal( - int id, @NonNull T defaultValue, FlagSerializer<T> serializer) { + int id, String name, @NonNull T defaultValue, FlagSerializer<T> serializer) { requireNonNull(defaultValue, "defaultValue"); - T result = readFlagValueInternal(id, serializer); - return result == null ? defaultValue : result; + T resultForName = readFlagValueInternal(name, serializer); + if (resultForName == null) { + T resultForId = readFlagValueInternal(id, serializer); + if (resultForId == null) { + return defaultValue; + } else { + setFlagValue(name, resultForId, serializer); + return resultForId; + } + } + return resultForName; } /** Returns the stored value or null if not set. */ + // TODO(b/265188950): Remove method this once ids are fully deprecated. @Nullable private <T> T readFlagValueInternal(int id, FlagSerializer<T> serializer) { try { @@ -264,51 +290,71 @@ public class FeatureFlagsDebug implements FeatureFlags { return null; } - private <T> void setFlagValue(int id, @NonNull T value, FlagSerializer<T> serializer) { + /** Returns the stored value or null if not set. */ + @Nullable + private <T> T readFlagValueInternal(String name, FlagSerializer<T> serializer) { + try { + return mFlagManager.readFlagValue(name, serializer); + } catch (Exception e) { + eraseInternal(name); + } + return null; + } + + private <T> void setFlagValue(String name, @NonNull T value, FlagSerializer<T> serializer) { requireNonNull(value, "Cannot set a null value"); - T currentValue = readFlagValueInternal(id, serializer); + T currentValue = readFlagValueInternal(name, serializer); if (Objects.equals(currentValue, value)) { - Log.i(TAG, "Flag id " + id + " is already " + value); + Log.i(TAG, "Flag id " + name + " is already " + value); return; } final String data = serializer.toSettingsData(value); if (data == null) { - Log.w(TAG, "Failed to set id " + id + " to " + value); + Log.w(TAG, "Failed to set id " + name + " to " + value); return; } - mSecureSettings.putStringForUser(mFlagManager.idToSettingsKey(id), data, + mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), data, UserHandle.USER_CURRENT); - Log.i(TAG, "Set id " + id + " to " + value); - removeFromCache(id); - mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI); + Log.i(TAG, "Set id " + name + " to " + value); + removeFromCache(name); + mFlagManager.dispatchListenersAndMaybeRestart(name, this::restartSystemUI); } <T> void eraseFlag(Flag<T> flag) { if (flag instanceof SysPropFlag) { mSystemProperties.erase(((SysPropFlag<T>) flag).getName()); - dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid); + dispatchListenersAndMaybeRestart(flag.getName(), this::restartAndroid); } else { - eraseFlag(flag.getId()); + eraseFlag(flag.getName()); } } /** Erase a flag's overridden value if there is one. */ - private void eraseFlag(int id) { - eraseInternal(id); - removeFromCache(id); - dispatchListenersAndMaybeRestart(id, this::restartSystemUI); + private void eraseFlag(String name) { + eraseInternal(name); + removeFromCache(name); + dispatchListenersAndMaybeRestart(name, this::restartSystemUI); } - private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) { - mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction); + private void dispatchListenersAndMaybeRestart(String name, Consumer<Boolean> restartAction) { + mFlagManager.dispatchListenersAndMaybeRestart(name, restartAction); } - /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */ + /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */ + // TODO(b/265188950): Remove method this once ids are fully deprecated. private void eraseInternal(int id) { - // We can't actually "erase" things from sysprops, but we can set them to empty! - mSecureSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "", + // We can't actually "erase" things from settings, but we can set them to empty! + mGlobalSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "", + UserHandle.USER_CURRENT); + Log.i(TAG, "Erase name " + id); + } + + /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */ + private void eraseInternal(String name) { + // We can't actually "erase" things from settings, but we can set them to empty! + mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "", UserHandle.USER_CURRENT); - Log.i(TAG, "Erase id " + id); + Log.i(TAG, "Erase name " + name); } @Override @@ -339,13 +385,13 @@ public class FeatureFlagsDebug implements FeatureFlags { void setBooleanFlagInternal(Flag<?> flag, boolean value) { if (flag instanceof BooleanFlag) { - setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE); + setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE); } else if (flag instanceof ResourceBooleanFlag) { - setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE); + setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE); } else if (flag instanceof SysPropBooleanFlag) { // Store SysProp flags in SystemProperties where they can read by outside parties. mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value); - dispatchListenersAndMaybeRestart(flag.getId(), + dispatchListenersAndMaybeRestart(flag.getName(), FeatureFlagsDebug.this::restartAndroid); } else { throw new IllegalArgumentException("Unknown flag type"); @@ -354,9 +400,9 @@ public class FeatureFlagsDebug implements FeatureFlags { void setStringFlagInternal(Flag<?> flag, String value) { if (flag instanceof StringFlag) { - setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE); + setFlagValue(flag.getName(), value, StringFlagSerializer.INSTANCE); } else if (flag instanceof ResourceStringFlag) { - setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE); + setFlagValue(flag.getName(), value, StringFlagSerializer.INSTANCE); } else { throw new IllegalArgumentException("Unknown flag type"); } @@ -364,9 +410,9 @@ public class FeatureFlagsDebug implements FeatureFlags { void setIntFlagInternal(Flag<?> flag, int value) { if (flag instanceof IntFlag) { - setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE); + setFlagValue(flag.getName(), value, IntFlagSerializer.INSTANCE); } else if (flag instanceof ResourceIntFlag) { - setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE); + setFlagValue(flag.getName(), value, IntFlagSerializer.INSTANCE); } else { throw new IllegalArgumentException("Unknown flag type"); } @@ -405,17 +451,17 @@ public class FeatureFlagsDebug implements FeatureFlags { Log.w(TAG, "No extras"); return; } - int id = extras.getInt(EXTRA_ID); - if (id <= 0) { - Log.w(TAG, "ID not set or less than or equal to 0: " + id); + String name = extras.getString(EXTRA_NAME); + if (name == null || name.isEmpty()) { + Log.w(TAG, "NAME not set or is empty: " + name); return; } - if (!mAllFlags.containsKey(id)) { - Log.w(TAG, "Tried to set unknown id: " + id); + if (!mAllFlags.containsKey(name)) { + Log.w(TAG, "Tried to set unknown name: " + name); return; } - Flag<?> flag = mAllFlags.get(id); + Flag<?> flag = mAllFlags.get(name); if (!extras.containsKey(EXTRA_VALUE)) { eraseFlag(flag); @@ -452,13 +498,16 @@ public class FeatureFlagsDebug implements FeatureFlags { if (f instanceof ReleasedFlag) { enabled = isEnabled((ReleasedFlag) f); - overridden = readBooleanFlagOverride(f.getId()) != null; + overridden = readBooleanFlagOverride(f.getName()) != null + || readBooleanFlagOverride(f.getId()) != null; } else if (f instanceof UnreleasedFlag) { enabled = isEnabled((UnreleasedFlag) f); - overridden = readBooleanFlagOverride(f.getId()) != null; + overridden = readBooleanFlagOverride(f.getName()) != null + || readBooleanFlagOverride(f.getId()) != null; } else if (f instanceof ResourceBooleanFlag) { enabled = isEnabled((ResourceBooleanFlag) f); - overridden = readBooleanFlagOverride(f.getId()) != null; + overridden = readBooleanFlagOverride(f.getName()) != null + || readBooleanFlagOverride(f.getId()) != null; } else if (f instanceof SysPropBooleanFlag) { // TODO(b/223379190): Teamfood not supported for sysprop flags yet. enabled = isEnabled((SysPropBooleanFlag) f); @@ -480,9 +529,9 @@ public class FeatureFlagsDebug implements FeatureFlags { } }; - private void removeFromCache(int id) { - mBooleanFlagCache.remove(id); - mStringFlagCache.remove(id); + private void removeFromCache(String name) { + mBooleanFlagCache.remove(name); + mStringFlagCache.remove(name); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt index b94d781154dd..dc7fc28f3c0d 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt @@ -21,6 +21,7 @@ import com.android.systemui.CoreStartable import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.util.InitializationChecker import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -34,7 +35,8 @@ constructor( private val commandRegistry: CommandRegistry, private val flagCommand: FlagCommand, private val featureFlags: FeatureFlagsDebug, - private val broadcastSender: BroadcastSender + private val broadcastSender: BroadcastSender, + private val initializationChecker: InitializationChecker ) : CoreStartable { init { @@ -46,8 +48,11 @@ constructor( override fun start() { featureFlags.init() commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand } - val intent = Intent(FlagManager.ACTION_SYSUI_STARTED) - broadcastSender.sendBroadcast(intent) + if (initializationChecker.initializeComponents()) { + // protected broadcast should only be sent for the main process + val intent = Intent(FlagManager.ACTION_SYSUI_STARTED) + broadcastSender.sendBroadcast(intent) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java index 8bddacc4e32c..7e1423706fc9 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java @@ -21,18 +21,16 @@ import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS; import static java.util.Objects.requireNonNull; import android.content.res.Resources; -import android.util.SparseArray; -import android.util.SparseBooleanArray; import androidx.annotation.NonNull; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.DeviceConfigProxy; import org.jetbrains.annotations.NotNull; import java.io.PrintWriter; +import java.util.HashMap; import java.util.Map; import javax.inject.Inject; @@ -50,12 +48,11 @@ public class FeatureFlagsRelease implements FeatureFlags { private final Resources mResources; private final SystemPropertiesHelper mSystemProperties; - private final DeviceConfigProxy mDeviceConfigProxy; private final ServerFlagReader mServerFlagReader; private final Restarter mRestarter; - private final Map<Integer, Flag<?>> mAllFlags; - SparseBooleanArray mBooleanCache = new SparseBooleanArray(); - SparseArray<String> mStringCache = new SparseArray<>(); + private final Map<String, Flag<?>> mAllFlags; + private final Map<String, Boolean> mBooleanCache = new HashMap<>(); + private final Map<String, String> mStringCache = new HashMap<>(); private final ServerFlagReader.ChangeListener mOnPropertiesChanged = new ServerFlagReader.ChangeListener() { @@ -69,13 +66,11 @@ public class FeatureFlagsRelease implements FeatureFlags { public FeatureFlagsRelease( @Main Resources resources, SystemPropertiesHelper systemProperties, - DeviceConfigProxy deviceConfigProxy, ServerFlagReader serverFlagReader, - @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags, + @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags, Restarter restarter) { mResources = resources; mSystemProperties = systemProperties; - mDeviceConfigProxy = deviceConfigProxy; mServerFlagReader = serverFlagReader; mAllFlags = allFlags; mRestarter = restarter; @@ -106,50 +101,48 @@ public class FeatureFlagsRelease implements FeatureFlags { @Override public boolean isEnabled(ResourceBooleanFlag flag) { - int cacheIndex = mBooleanCache.indexOfKey(flag.getId()); - if (cacheIndex < 0) { - return isEnabled(flag.getId(), mResources.getBoolean(flag.getResourceId())); + if (!mBooleanCache.containsKey(flag.getName())) { + return isEnabled(flag.getName(), mResources.getBoolean(flag.getResourceId())); } - return mBooleanCache.valueAt(cacheIndex); + return mBooleanCache.get(flag.getName()); } @Override public boolean isEnabled(SysPropBooleanFlag flag) { - int cacheIndex = mBooleanCache.indexOfKey(flag.getId()); - if (cacheIndex < 0) { + if (!mBooleanCache.containsKey(flag.getName())) { return isEnabled( - flag.getId(), mSystemProperties.getBoolean(flag.getName(), flag.getDefault())); + flag.getName(), + mSystemProperties.getBoolean(flag.getName(), flag.getDefault())); } - return mBooleanCache.valueAt(cacheIndex); + return mBooleanCache.get(flag.getName()); } - private boolean isEnabled(int key, boolean defaultValue) { - mBooleanCache.append(key, defaultValue); + private boolean isEnabled(String name, boolean defaultValue) { + mBooleanCache.put(name, defaultValue); return defaultValue; } @NonNull @Override public String getString(@NonNull StringFlag flag) { - return getString(flag.getId(), flag.getDefault()); + return getString(flag.getName(), flag.getDefault()); } @NonNull @Override public String getString(@NonNull ResourceStringFlag flag) { - int cacheIndex = mStringCache.indexOfKey(flag.getId()); - if (cacheIndex < 0) { - return getString(flag.getId(), + if (!mStringCache.containsKey(flag.getName())) { + return getString(flag.getName(), requireNonNull(mResources.getString(flag.getResourceId()))); } - return mStringCache.valueAt(cacheIndex); + return mStringCache.get(flag.getName()); } - private String getString(int key, String defaultValue) { - mStringCache.append(key, defaultValue); + private String getString(String name, String defaultValue) { + mStringCache.put(name, defaultValue); return defaultValue; } @@ -169,11 +162,17 @@ public class FeatureFlagsRelease implements FeatureFlags { public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("can override: false"); Map<String, Flag<?>> knownFlags = FlagsFactory.INSTANCE.getKnownFlags(); + pw.println("Booleans: "); for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) { Flag<?> flag = nameToFlag.getValue(); - int id = flag.getId(); + if (!(flag instanceof BooleanFlag) + || !(flag instanceof ResourceBooleanFlag) + || !(flag instanceof SysPropBooleanFlag)) { + continue; + } + boolean def = false; - if (mBooleanCache.indexOfKey(flag.getId()) < 0) { + if (!mBooleanCache.containsKey(flag.getName())) { if (flag instanceof SysPropBooleanFlag) { SysPropBooleanFlag f = (SysPropBooleanFlag) flag; def = mSystemProperties.getBoolean(f.getName(), f.getDefault()); @@ -185,15 +184,32 @@ public class FeatureFlagsRelease implements FeatureFlags { def = f.getDefault(); } } - pw.println(" sysui_flag_" + id + ": " + (mBooleanCache.get(id, def))); + pw.println( + " " + flag.getName() + ": " + + (mBooleanCache.getOrDefault(flag.getName(), def))); } - int numStrings = mStringCache.size(); - pw.println("Strings: " + numStrings); - for (int i = 0; i < numStrings; i++) { - final int id = mStringCache.keyAt(i); - final String value = mStringCache.valueAt(i); - final int length = value.length(); - pw.println(" sysui_flag_" + id + ": [length=" + length + "] \"" + value + "\""); + + pw.println("Strings: "); + for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) { + Flag<?> flag = nameToFlag.getValue(); + if (!(flag instanceof StringFlag) + || !(flag instanceof ResourceStringFlag)) { + continue; + } + + String def = ""; + if (!mBooleanCache.containsKey(flag.getName())) { + if (flag instanceof ResourceStringFlag) { + ResourceStringFlag f = (ResourceStringFlag) flag; + def = mResources.getString(f.getResourceId()); + } else if (flag instanceof StringFlag) { + StringFlag f = (StringFlag) flag; + def = f.getDefault(); + } + } + String value = mStringCache.getOrDefault(flag.getName(), def); + pw.println( + " " + flag.getName() + ": [length=" + value.length() + "] \"" + value + "\""); } } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java index b7fc0e41ea69..daf9429344a7 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java @@ -39,12 +39,12 @@ public class FlagCommand implements Command { private final List<String> mOffCommands = List.of("false", "off", "0", "disable"); private final List<String> mSetCommands = List.of("set", "put"); private final FeatureFlagsDebug mFeatureFlags; - private final Map<Integer, Flag<?>> mAllFlags; + private final Map<String, Flag<?>> mAllFlags; @Inject FlagCommand( FeatureFlagsDebug featureFlags, - @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags + @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags ) { mFeatureFlags = featureFlags; mAllFlags = allFlags; @@ -53,30 +53,22 @@ public class FlagCommand implements Command { @Override public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) { if (args.size() == 0) { - pw.println("Error: no flag id supplied"); + pw.println("Error: no flag name supplied"); help(pw); pw.println(); printKnownFlags(pw); return; } - int id = 0; - try { - id = Integer.parseInt(args.get(0)); - if (!mAllFlags.containsKey(id)) { - pw.println("Unknown flag id: " + id); - pw.println(); - printKnownFlags(pw); - return; - } - } catch (NumberFormatException e) { - id = flagNameToId(args.get(0)); - if (id == 0) { - pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0)); - return; - } + String name = args.get(0); + if (!mAllFlags.containsKey(name)) { + pw.println("Unknown flag name: " + name); + pw.println(); + printKnownFlags(pw); + return; } - Flag<?> flag = mAllFlags.get(id); + + Flag<?> flag = mAllFlags.get(name); String cmd = ""; if (args.size() > 1) { @@ -117,7 +109,7 @@ public class FlagCommand implements Command { return; } - pw.println("Flag " + id + " is " + newValue); + pw.println("Flag " + name + " is " + newValue); pw.flush(); // Next command will restart sysui, so flush before we do so. if (shouldSet) { mFeatureFlags.setBooleanFlagInternal(flag, newValue); @@ -136,11 +128,11 @@ public class FlagCommand implements Command { return; } String value = args.get(2); - pw.println("Setting Flag " + id + " to " + value); + pw.println("Setting Flag " + name + " to " + value); pw.flush(); // Next command will restart sysui, so flush before we do so. mFeatureFlags.setStringFlagInternal(flag, args.get(2)); } else { - pw.println("Flag " + id + " is " + getStringFlag(flag)); + pw.println("Flag " + name + " is " + getStringFlag(flag)); } return; } else if (isIntFlag(flag)) { @@ -155,11 +147,11 @@ public class FlagCommand implements Command { return; } int value = Integer.parseInt(args.get(2)); - pw.println("Setting Flag " + id + " to " + value); + pw.println("Setting Flag " + name + " to " + value); pw.flush(); // Next command will restart sysui, so flush before we do so. mFeatureFlags.setIntFlagInternal(flag, value); } else { - pw.println("Flag " + id + " is " + getIntFlag(flag)); + pw.println("Flag " + name + " is " + getIntFlag(flag)); } return; } @@ -182,8 +174,7 @@ public class FlagCommand implements Command { private boolean isBooleanFlag(Flag<?> flag) { return (flag instanceof BooleanFlag) || (flag instanceof ResourceBooleanFlag) - || (flag instanceof SysPropFlag) - || (flag instanceof DeviceConfigBooleanFlag); + || (flag instanceof SysPropFlag); } private boolean isBooleanFlagEnabled(Flag<?> flag) { @@ -252,15 +243,14 @@ public class FlagCommand implements Command { for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) { pw.print(" "); } - pw.println("ID Value"); + pw.println(" Value"); for (int i = 0; i < longestFieldName; i++) { pw.print("="); } - pw.println(" ==== ========"); + pw.println(" ========"); for (String fieldName : fields.keySet()) { Flag<?> flag = fields.get(fieldName); - int id = flag.getId(); - if (id == 0 || !mAllFlags.containsKey(id)) { + if (!mAllFlags.containsKey(flag.getName())) { continue; } pw.print(fieldName); @@ -268,9 +258,9 @@ public class FlagCommand implements Command { for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) { pw.print(" "); } - pw.printf("%-4d ", id); + pw.print(" "); if (isBooleanFlag(flag)) { - pw.println(isBooleanFlagEnabled(mAllFlags.get(id))); + pw.println(isBooleanFlagEnabled(flag)); } else if (isStringFlag(flag)) { pw.println(getStringFlag(flag)); } else if (isIntFlag(flag)) { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index ede42231f362..6032da6d0b6b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -65,8 +65,7 @@ object Flags { val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true) // TODO(b/265804648): Tracking Bug - @JvmField - val DISABLE_FSI = unreleasedFlag(265804648, "disable_fsi") + @JvmField val DISABLE_FSI = unreleasedFlag(265804648, "disable_fsi") // TODO(b/254512538): Tracking Bug val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply", teamfood = true) @@ -129,13 +128,6 @@ object Flags { val LOCKSCREEN_CUSTOM_CLOCKS = unreleasedFlag(207, "lockscreen_custom_clocks", teamfood = true) /** - * Flag to enable the usage of the new bouncer data source. This is a refactor of and eventual - * replacement of KeyguardBouncer.java. - */ - // TODO(b/254512385): Tracking Bug - @JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer") - - /** * Whether the clock on a wide lock screen should use the new "stepping" animation for moving * the digits when the clock moves. */ @@ -199,8 +191,7 @@ object Flags { /** A different path for unocclusion transitions back to keyguard */ // TODO(b/262859270): Tracking Bug - @JvmField - val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = true) + @JvmField val UNOCCLUSION_TRANSITION = releasedFlag(223, "unocclusion_transition") // flag for controlling auto pin confirmation and material u shapes in bouncer @JvmField @@ -221,6 +212,15 @@ object Flags { val WALLPAPER_FULLSCREEN_PREVIEW = unreleasedFlag(227, "wallpaper_fullscreen_preview", teamfood = true) + /** Whether the long-press gesture to open wallpaper picker is enabled. */ + // TODO(b/266242192): Tracking Bug + @JvmField + val LOCK_SCREEN_LONG_PRESS_ENABLED = + unreleasedFlag( + 228, + "lock_screen_long_press_enabled", + ) + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -230,7 +230,6 @@ object Flags { // TODO(b/254513100): Tracking Bug val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = releasedFlag(401, "smartspace_shared_element_transition_enabled") - val SMARTSPACE = resourceBooleanFlag(402, R.bool.flag_smartspace, "smartspace") // TODO(b/258517050): Clean up after the feature is launched. @JvmField @@ -268,10 +267,11 @@ object Flags { // 600- status bar // TODO(b/256614753): Tracking Bug - val NEW_STATUS_BAR_MOBILE_ICONS = unreleasedFlag(606, "new_status_bar_mobile_icons") + val NEW_STATUS_BAR_MOBILE_ICONS = + unreleasedFlag(606, "new_status_bar_mobile_icons", teamfood = true) // TODO(b/256614210): Tracking Bug - val NEW_STATUS_BAR_WIFI_ICON = unreleasedFlag(607, "new_status_bar_wifi_icon") + val NEW_STATUS_BAR_WIFI_ICON = unreleasedFlag(607, "new_status_bar_wifi_icon", teamfood = true) // TODO(b/256614751): Tracking Bug val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND = @@ -338,8 +338,7 @@ object Flags { // TODO(b/254513168): Tracking Bug @JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple") - @JvmField - val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media", teamfood = true) + @JvmField val MEDIA_FALSING_PENALTY = releasedFlag(908, "media_falsing_media") // TODO(b/261734857): Tracking Bug @JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise") @@ -355,6 +354,13 @@ object Flags { val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = unreleasedFlag(912, "media_ttt_dismiss_gesture", teamfood = true) + // TODO(b/266157412): Tracking Bug + val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions") + + // TODO(b/266739309): Tracking Bug + @JvmField + val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update") + // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") @@ -436,6 +442,18 @@ object Flags { teamfood = false ) + // TODO(b/198643358): Tracking bug + @Keep + @JvmField + val ENABLE_PIP_SIZE_LARGE_SCREEN = + sysPropBooleanFlag(1114, "persist.wm.debug.enable_pip_size_large_screen", default = false) + + // TODO(b/265998256): Tracking bug + @Keep + @JvmField + val ENABLE_PIP_APP_ICON_OVERLAY = + sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = false) + // 1200 - predictive back @Keep @JvmField @@ -445,7 +463,7 @@ object Flags { @Keep @JvmField val WM_ENABLE_PREDICTIVE_BACK_ANIM = - sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = false) + sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = true) @Keep @JvmField @@ -478,7 +496,12 @@ object Flags { // TODO(b/238475428): Tracking Bug @JvmField val WM_SHADE_ANIMATE_BACK_GESTURE = - unreleasedFlag(1208, "persist.wm.debug.shade_animate_back_gesture", teamfood = true) + unreleasedFlag(1208, "persist.wm.debug.shade_animate_back_gesture", teamfood = false) + + // TODO(b/265639042): Tracking Bug + @JvmField + val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM = + unreleasedFlag(1209, "persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true) // 1300 - screenshots // TODO(b/254513155): Tracking Bug @@ -486,6 +509,12 @@ object Flags { val SCREENSHOT_WORK_PROFILE_POLICY = unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true) + // TODO(b/264916608): Tracking Bug + @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata") + + // TODO(b/266955521): Tracking bug + @JvmField val SCREENSHOT_DETECTION = unreleasedFlag(1303, "screenshot_detection") + // 1400 - columbus // TODO(b/254512756): Tracking Bug val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc") @@ -494,33 +523,47 @@ object Flags { val QUICK_TAP_FLOW_FRAMEWORK = unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false) - // 1500 - chooser + // 1500 - chooser aka sharesheet // TODO(b/254512507): Tracking Bug val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true) + // TODO(b/266983432) Tracking Bug + val SHARESHEET_CUSTOM_ACTIONS = unreleasedFlag(1501, "sharesheet_custom_actions") + + // TODO(b/266982749) Tracking Bug + val SHARESHEET_RESELECTION_ACTION = unreleasedFlag(1502, "sharesheet_reselection_action") + + // TODO(b/266983474) Tracking Bug + val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = unreleasedFlag(1503, "sharesheet_image_text_preview") + // 1600 - accessibility @JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations") // 1700 - clipboard - @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor") @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior") // 1800 - shade container @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = releasedFlag(1800, "leave_shade_open_for_bugreport") + // TODO(b/265944639): Tracking Bug + @JvmField val DUAL_SHADE = releasedFlag(1801, "dual_shade") // 1900 @JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag") // 2000 - device controls - @Keep @JvmField val USE_APP_PANELS = unreleasedFlag(2000, "use_app_panels", teamfood = true) + @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels", teamfood = true) @JvmField val APP_PANELS_ALL_APPS_ALLOWED = unreleasedFlag(2001, "app_panels_all_apps_allowed", teamfood = true) + @JvmField + val CONTROLS_MANAGEMENT_NEW_FLOWS = + unreleasedFlag(2002, "controls_management_new_flows", teamfood = true) + // 2100 - Falsing Manager @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps") @@ -570,6 +613,5 @@ object Flags { // 2600 - keyboard shortcut // TODO(b/259352579): Tracking Bug - @JvmField - val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout") + @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout") } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt index 8442230fc5b1..0054d266c283 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt @@ -28,8 +28,8 @@ interface FlagsCommonModule { @JvmStatic @Provides @Named(ALL_FLAGS) - fun providesAllFlags(): Map<Int, Flag<*>> { - return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap() + fun providesAllFlags(): Map<String, Flag<*>> { + return FlagsFactory.knownFlags } } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt index ae05c4656349..a02b795f074e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt @@ -54,10 +54,11 @@ class ServerFlagReaderImpl @Inject constructor( return } + for ((listener, flags) in listeners) { propLoop@ for (propName in properties.keyset) { for (flag in flags) { - if (propName == getServerOverrideName(flag.id)) { + if (propName == getServerOverrideName(flag.id) || propName == flag.name) { listener.onChange() break@propLoop } diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java index 18fb423b87a5..98896b118331 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java @@ -37,9 +37,14 @@ public class ExtensionFragmentListener<T extends FragmentBase> implements Consum private final int mId; private String mOldClass; - private ExtensionFragmentListener(View view, String tag, int id, Extension<T> extension) { + private ExtensionFragmentListener( + FragmentService fragmentService, + View view, + String tag, + int id, + Extension<T> extension) { mTag = tag; - mFragmentHostManager = FragmentHostManager.get(view); + mFragmentHostManager = fragmentService.getFragmentHostManager(view); mExtension = extension; mId = id; mFragmentHostManager.getFragmentManager().beginTransaction() @@ -61,8 +66,13 @@ public class ExtensionFragmentListener<T extends FragmentBase> implements Consum mExtension.clearItem(true); } - public static <T> void attachExtensonToFragment(View view, String tag, int id, + public static <T> void attachExtensonToFragment( + FragmentService fragmentService, + View view, + String tag, + int id, Extension<T> extension) { - extension.addCallback(new ExtensionFragmentListener(view, tag, id, extension)); + extension.addCallback( + new ExtensionFragmentListener(fragmentService, view, tag, id, extension)); } } diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index 9c7411bf3649..6a27ee7c0c89 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -36,7 +36,6 @@ import android.view.View; import androidx.annotation.NonNull; import com.android.settingslib.applications.InterestingConfigChanges; -import com.android.systemui.Dependency; import com.android.systemui.plugins.Plugin; import com.android.systemui.util.leak.LeakDetector; @@ -46,12 +45,17 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; + public class FragmentHostManager { private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Context mContext; private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>(); private final View mRootView; + private final LeakDetector mLeakDetector; private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_ASSETS_PATHS); @@ -61,14 +65,24 @@ public class FragmentHostManager { private FragmentController mFragments; private FragmentLifecycleCallbacks mLifecycleCallbacks; - FragmentHostManager(FragmentService manager, View rootView) { + @AssistedInject + FragmentHostManager( + @Assisted View rootView, + FragmentService manager, + LeakDetector leakDetector) { mContext = rootView.getContext(); mManager = manager; mRootView = rootView; + mLeakDetector = leakDetector; mConfigChanges.applyNewConfig(mContext.getResources()); createFragmentHost(null); } + @AssistedFactory + public interface Factory { + FragmentHostManager create(View rootView); + } + private void createFragmentHost(Parcelable savedState) { mFragments = FragmentController.createController(new HostCallbacks()); mFragments.attachHost(null); @@ -86,7 +100,7 @@ public class FragmentHostManager { @Override public void onFragmentDestroyed(FragmentManager fm, Fragment f) { - Dependency.get(LeakDetector.class).trackGarbage(f); + mLeakDetector.trackGarbage(f); } }; mFragments.getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks, @@ -211,19 +225,6 @@ public class FragmentHostManager { } } - public static FragmentHostManager get(View view) { - try { - return Dependency.get(FragmentService.class).getFragmentHostManager(view); - } catch (ClassCastException e) { - // TODO: Some auto handling here? - throw e; - } - } - - public static void removeAndDestroy(View view) { - Dependency.get(FragmentService.class).removeAndDestroy(view); - } - public void reloadFragments() { Trace.beginSection("FrargmentHostManager#reloadFragments"); // Save the old state. diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java index fe945fb2fa28..d302b13a68b6 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java @@ -53,6 +53,7 @@ public class FragmentService implements Dumpable { */ private final ArrayMap<String, FragmentInstantiationInfo> mInjectionMap = new ArrayMap<>(); private final Handler mHandler = new Handler(); + private final FragmentHostManager.Factory mFragmentHostManagerFactory; private ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @@ -67,8 +68,10 @@ public class FragmentService implements Dumpable { @Inject public FragmentService( FragmentCreator.Factory fragmentCreatorFactory, + FragmentHostManager.Factory fragmentHostManagerFactory, ConfigurationController configurationController, DumpManager dumpManager) { + mFragmentHostManagerFactory = fragmentHostManagerFactory; addFragmentInstantiationProvider(fragmentCreatorFactory.build()); configurationController.addCallback(mConfigurationListener); @@ -152,7 +155,7 @@ public class FragmentService implements Dumpable { public FragmentHostState(View view) { mView = view; - mFragmentHostManager = new FragmentHostManager(FragmentService.this, mView); + mFragmentHostManager = mFragmentHostManagerFactory.create(mView); } public void sendConfigurationChange(Configuration newConfig) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt index 482138e6c277..680c504d50fc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -31,10 +31,12 @@ import android.os.Bundle import android.util.Log import com.android.systemui.SystemUIAppComponentFactoryBase import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.runBlocking class CustomizationProvider : @@ -42,6 +44,7 @@ class CustomizationProvider : @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor @Inject lateinit var previewManager: KeyguardRemotePreviewManager + @Inject @Main lateinit var mainDispatcher: CoroutineDispatcher private lateinit var contextAvailableCallback: ContextAvailableCallback @@ -138,12 +141,14 @@ class CustomizationProvider : selectionArgs: Array<out String>?, sortOrder: String?, ): Cursor? { - return when (uriMatcher.match(uri)) { - MATCH_CODE_ALL_AFFORDANCES -> runBlocking { queryAffordances() } - MATCH_CODE_ALL_SLOTS -> querySlots() - MATCH_CODE_ALL_SELECTIONS -> runBlocking { querySelections() } - MATCH_CODE_ALL_FLAGS -> queryFlags() - else -> null + return runBlocking(mainDispatcher) { + when (uriMatcher.match(uri)) { + MATCH_CODE_ALL_AFFORDANCES -> queryAffordances() + MATCH_CODE_ALL_SLOTS -> querySlots() + MATCH_CODE_ALL_SELECTIONS -> querySelections() + MATCH_CODE_ALL_FLAGS -> queryFlags() + else -> null + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index f4a1227a467c..47872d2d68bb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -17,7 +17,6 @@ package com.android.systemui.keyguard; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.TRANSIT_CLOSE; @@ -80,6 +79,7 @@ import com.android.internal.policy.IKeyguardService; import com.android.internal.policy.IKeyguardStateCallback; import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.SystemUIApplication; +import com.android.systemui.settings.DisplayTracker; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; @@ -123,6 +123,7 @@ public class KeyguardService extends Service { private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher; private final ScreenOnCoordinator mScreenOnCoordinator; private final ShellTransitions mShellTransitions; + private final DisplayTracker mDisplayTracker; private static int newModeToLegacyMode(int newMode) { switch (newMode) { @@ -286,12 +287,14 @@ public class KeyguardService extends Service { public KeyguardService(KeyguardViewMediator keyguardViewMediator, KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher, ScreenOnCoordinator screenOnCoordinator, - ShellTransitions shellTransitions) { + ShellTransitions shellTransitions, + DisplayTracker displayTracker) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; mScreenOnCoordinator = screenOnCoordinator; mShellTransitions = shellTransitions; + mDisplayTracker = displayTracker; } @Override @@ -328,7 +331,7 @@ public class KeyguardService extends Service { unoccludeAnimationAdapter); } ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay( - DEFAULT_DISPLAY, definition); + mDisplayTracker.getDefaultDisplayId(), definition); return; } if (sEnableRemoteKeyguardGoingAwayAnimation) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt index 76c2430e37ad..80675d373b8e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt @@ -29,6 +29,7 @@ object BuiltInKeyguardQuickAffordanceKeys { const val DO_NOT_DISTURB = "do_not_disturb" const val FLASHLIGHT = "flashlight" const val HOME_CONTROLS = "home" + const val MUTE = "mute" const val QR_CODE_SCANNER = "qr_code_scanner" const val QUICK_ACCESS_WALLET = "wallet" const val VIDEO_CAMERA = "video_camera" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt index f6e6d6b7dc1b..5a9f7752277e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.quickaffordance import android.app.StatusBarManager import android.content.Context +import android.content.pm.PackageManager import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.camera.CameraGestureHelper @@ -26,7 +27,6 @@ import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.StatusBarState import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -37,6 +37,7 @@ class CameraQuickAffordanceConfig @Inject constructor( @Application private val context: Context, + private val packageManager: PackageManager, private val cameraGestureHelper: Lazy<CameraGestureHelper>, ) : KeyguardQuickAffordanceConfig { @@ -79,6 +80,6 @@ constructor( } private fun isLaunchable(): Boolean { - return cameraGestureHelper.get().canCameraGestureBeLaunched(StatusBarState.KEYGUARD) + return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt index a1cce5c670ba..45561959a7df 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -33,12 +33,13 @@ interface KeyguardDataQuickAffordanceModule { @Provides @ElementsIntoSet fun quickAffordanceConfigs( + camera: CameraQuickAffordanceConfig, doNotDisturb: DoNotDisturbQuickAffordanceConfig, flashlight: FlashlightQuickAffordanceConfig, home: HomeControlsKeyguardQuickAffordanceConfig, + mute: MuteQuickAffordanceConfig, quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig, - camera: CameraQuickAffordanceConfig, videoCamera: VideoCameraQuickAffordanceConfig, ): Set<KeyguardQuickAffordanceConfig> { return setOf( @@ -46,6 +47,7 @@ interface KeyguardDataQuickAffordanceModule { doNotDisturb, flashlight, home, + mute, quickAccessWallet, qrCodeScanner, videoCamera, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt new file mode 100644 index 000000000000..d085db9a1eda --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.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.keyguard.data.quickaffordance + +import android.content.Context +import android.media.AudioManager +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import com.android.systemui.R +import com.android.systemui.animation.Expandable +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.RingerModeTracker +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import javax.inject.Inject + +@SysUISingleton +class MuteQuickAffordanceConfig @Inject constructor( + context: Context, + private val userTracker: UserTracker, + private val userFileManager: UserFileManager, + private val ringerModeTracker: RingerModeTracker, + private val audioManager: AudioManager, +) : KeyguardQuickAffordanceConfig { + + private var previousNonSilentMode: Int = DEFAULT_LAST_NON_SILENT_VALUE + + override val key: String = BuiltInKeyguardQuickAffordanceKeys.MUTE + + override val pickerName: String = context.getString(R.string.volume_ringer_status_silent) + + override val pickerIconResourceId: Int = R.drawable.ic_notifications_silence + + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = + ringerModeTracker.ringerModeInternal.asFlow() + .onStart { emit(getLastNonSilentRingerMode()) } + .distinctUntilChanged() + .onEach { mode -> + // only remember last non-SILENT ringer mode + if (mode != null && mode != AudioManager.RINGER_MODE_SILENT) { + previousNonSilentMode = mode + } + } + .map { mode -> + val (activationState, contentDescriptionRes) = when { + audioManager.isVolumeFixed -> + ActivationState.NotSupported to + R.string.volume_ringer_hint_mute + mode == AudioManager.RINGER_MODE_SILENT -> + ActivationState.Active to + R.string.volume_ringer_hint_mute + else -> + ActivationState.Inactive to + R.string.volume_ringer_hint_unmute + } + + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.ic_notifications_silence, + ContentDescription.Resource(contentDescriptionRes), + ), + activationState, + ) + } + + override fun onTriggered( + expandable: Expandable? + ): KeyguardQuickAffordanceConfig.OnTriggeredResult { + val newRingerMode: Int + val currentRingerMode = + ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE + if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) { + newRingerMode = previousNonSilentMode + } else { + previousNonSilentMode = currentRingerMode + newRingerMode = AudioManager.RINGER_MODE_SILENT + } + + if (currentRingerMode != newRingerMode) { + audioManager.ringerModeInternal = newRingerMode + } + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + + override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState = + if (audioManager.isVolumeFixed) { + KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice + } else { + KeyguardQuickAffordanceConfig.PickerScreenState.Default() + } + + /** + * Gets the last non-silent ringer mode from shared-preferences if it exists. This is + * cached by [MuteQuickAffordanceCoreStartable] while this affordance is selected + */ + private fun getLastNonSilentRingerMode(): Int = + userFileManager.getSharedPreferences( + MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME, + Context.MODE_PRIVATE, + userTracker.userId + ).getInt( + LAST_NON_SILENT_RINGER_MODE_KEY, + ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE + ) + + private fun <T> LiveData<T>.asFlow(): Flow<T?> = + conflatedCallbackFlow { + val observer = Observer { value: T -> trySend(value) } + observeForever(observer) + send(value) + awaitClose { removeObserver(observer) } + } + + companion object { + const val LAST_NON_SILENT_RINGER_MODE_KEY = "key_last_non_silent_ringer_mode" + const val MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME = "quick_affordance_mute_ringer_mode_cache" + private const val DEFAULT_LAST_NON_SILENT_VALUE = AudioManager.RINGER_MODE_NORMAL + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt new file mode 100644 index 000000000000..12a6310ccc85 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import android.content.Context +import android.media.AudioManager +import androidx.lifecycle.Observer +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.RingerModeTracker +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +/** + * Store previous non-silent Ringer Mode into shared prefs to be used for Mute Lockscreen Shortcut + */ +@SysUISingleton +class MuteQuickAffordanceCoreStartable @Inject constructor( + private val featureFlags: FeatureFlags, + private val userTracker: UserTracker, + private val ringerModeTracker: RingerModeTracker, + private val userFileManager: UserFileManager, + private val keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository, + @Application private val coroutineScope: CoroutineScope, +) : CoreStartable { + + private val observer = Observer(this::updateLastNonSilentRingerMode) + + override fun start() { + if (!featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)) return + + // only listen to ringerModeInternal changes when Mute is one of the selected affordances + keyguardQuickAffordanceRepository + .selections + .map { selections -> + // determines if Mute is selected in any lockscreen shortcut position + val muteSelected: Boolean = selections.values.any { configList -> + configList.any { config -> + config.key == BuiltInKeyguardQuickAffordanceKeys.MUTE + } + } + if (muteSelected) { + ringerModeTracker.ringerModeInternal.observeForever(observer) + } else { + ringerModeTracker.ringerModeInternal.removeObserver(observer) + } + } + .launchIn(coroutineScope) + } + + private fun updateLastNonSilentRingerMode(lastRingerMode: Int) { + if (AudioManager.RINGER_MODE_SILENT != lastRingerMode) { + userFileManager.getSharedPreferences( + MuteQuickAffordanceConfig.MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME, + Context.MODE_PRIVATE, + userTracker.userId + ) + .edit() + .putInt(MuteQuickAffordanceConfig.LAST_NON_SILENT_RINGER_MODE_KEY, lastRingerMode) + .apply() + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt index 08edbc6d0163..b3a9cf58310a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt @@ -22,14 +22,18 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn /** Encapsulates state about device entry fingerprint auth mechanism. */ interface DeviceEntryFingerprintAuthRepository { /** Whether the device entry fingerprint auth is locked out. */ - val isLockedOut: Flow<Boolean> + val isLockedOut: StateFlow<Boolean> } /** @@ -44,29 +48,34 @@ class DeviceEntryFingerprintAuthRepositoryImpl @Inject constructor( val keyguardUpdateMonitor: KeyguardUpdateMonitor, + @Application scope: CoroutineScope, ) : DeviceEntryFingerprintAuthRepository { - override val isLockedOut: Flow<Boolean> = conflatedCallbackFlow { - val sendLockoutUpdate = - fun() { - trySendWithFailureLogging( - keyguardUpdateMonitor.isFingerprintLockedOut, - TAG, - "onLockedOutStateChanged" - ) - } - val callback = - object : KeyguardUpdateMonitorCallback() { - override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) { - if (biometricSourceType == BiometricSourceType.FINGERPRINT) { - sendLockoutUpdate() + override val isLockedOut: StateFlow<Boolean> = + conflatedCallbackFlow { + val sendLockoutUpdate = + fun() { + trySendWithFailureLogging( + keyguardUpdateMonitor.isFingerprintLockedOut, + TAG, + "onLockedOutStateChanged" + ) + } + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onLockedOutStateChanged( + biometricSourceType: BiometricSourceType? + ) { + if (biometricSourceType == BiometricSourceType.FINGERPRINT) { + sendLockoutUpdate() + } + } } - } + keyguardUpdateMonitor.registerCallback(callback) + sendLockoutUpdate() + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } - keyguardUpdateMonitor.registerCallback(callback) - sendLockoutUpdate() - awaitClose { keyguardUpdateMonitor.removeCallback(callback) } - } + .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false) companion object { const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt index 41574d18e2c7..3e171361a6f1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt @@ -26,7 +26,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import com.android.systemui.log.dagger.BouncerLog import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable -import com.android.systemui.statusbar.phone.KeyguardBouncer import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt index 2b2b9d0703fa..8ece3183fd56 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt @@ -146,7 +146,7 @@ constructor( * Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the * slot with the given ID. The configs are sorted in descending priority order. */ - fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> { + fun getCurrentSelections(slotId: String): List<KeyguardQuickAffordanceConfig> { val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList()) return configs.filter { selections.contains(it.key) } } @@ -155,7 +155,7 @@ constructor( * Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs * are sorted in descending priority order. */ - fun getSelections(): Map<String, List<String>> { + fun getCurrentSelections(): Map<String, List<String>> { return selectionManager.value.getSelections() } @@ -217,7 +217,7 @@ constructor( private inner class Dumpster : Dumpable { override fun dump(pw: PrintWriter, args: Array<out String>) { val slotPickerRepresentations = getSlotPickerRepresentations() - val selectionsBySlotId = getSelections() + val selectionsBySlotId = getCurrentSelections() pw.println("Slots & selections:") slotPickerRepresentations.forEach { slotPickerRepresentation -> val slotId = slotPickerRepresentation.id diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index d99af90ab6dc..db955622f707 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -148,6 +148,9 @@ interface KeyguardRepository { /** Source of the most recent biometric unlock, such as fingerprint or face. */ val biometricUnlockSource: Flow<BiometricUnlockSource?> + /** Whether quick settings or quick-quick settings is visible. */ + val isQuickSettingsVisible: Flow<Boolean> + /** * Returns `true` if the keyguard is showing; `false` otherwise. * @@ -172,6 +175,9 @@ interface KeyguardRepository { * Returns whether the keyguard bottom area should be constrained to the top of the lock icon */ fun isUdfpsSupported(): Boolean + + /** Sets whether quick settings or quick-quick settings is visible. */ + fun setQuickSettingsVisible(isVisible: Boolean) } /** Encapsulates application state for the keyguard. */ @@ -581,6 +587,9 @@ constructor( awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } + private val _isQuickSettingsVisible = MutableStateFlow(false) + override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow() + override fun setAnimateDozingTransitions(animate: Boolean) { _animateBottomAreaDozingTransitions.value = animate } @@ -595,6 +604,10 @@ constructor( override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported + override fun setQuickSettingsVisible(isVisible: Boolean) { + _isQuickSettingsVisible.value = isVisible + } + private fun statusBarStateIntToObject(value: Int): StatusBarState { return when (value) { 0 -> StatusBarState.SHADE diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index 4639597a9b8c..cc99eb72da16 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -32,4 +32,9 @@ interface KeyguardRepositoryModule { fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository @Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository + + @Binds + fun deviceEntryFingerprintAuthRepository( + impl: DeviceEntryFingerprintAuthRepositoryImpl + ): DeviceEntryFingerprintAuthRepository } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt index 28c0b288147b..6020ef8017c8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.BiometricRepository +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer import com.android.systemui.util.time.SystemClock @@ -34,6 +35,7 @@ class AlternateBouncerInteractor constructor( private val bouncerRepository: KeyguardBouncerRepository, private val biometricRepository: BiometricRepository, + private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, private val systemClock: SystemClock, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, featureFlags: FeatureFlags, @@ -99,7 +101,8 @@ constructor( bouncerRepository.isAlternateBouncerUIAvailable.value && biometricRepository.isFingerprintEnrolled.value && biometricRepository.isStrongBiometricAllowed.value && - biometricRepository.isFingerprintEnabledByDevicePolicy.value + biometricRepository.isFingerprintEnabledByDevicePolicy.value && + !deviceEntryFingerprintAuthRepository.isLockedOut.value } else { legacyAlternateBouncer != null && keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index 14f918d78bc6..b5bcd45f03dd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -45,6 +45,27 @@ constructor( override fun start() { listenForGoneToAodOrDozing() listenForGoneToDreaming() + listenForGoneToLockscreen() + } + + // Primarily for when the user chooses to lock down the device + private fun listenForGoneToLockscreen() { + scope.launch { + keyguardInteractor.isKeyguardShowing + .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) + .collect { (isKeyguardShowing, lastStartedStep) -> + if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.GONE, + KeyguardState.LOCKSCREEN, + getAnimator(), + ) + ) + } + } + } } private fun listenForGoneToDreaming() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 4cf56fe2c031..3d39da626f0d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -155,6 +155,11 @@ constructor( } } + /** Sets whether quick settings or quick-quick settings is visible. */ + fun setQuickSettingsVisible(isVisible: Boolean) { + repository.setQuickSettingsVisible(isVisible) + } + companion object { private const val TAG = "KeyguardInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt new file mode 100644 index 000000000000..6525a13fc44f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.systemui.R +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.common.shared.model.Position +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.domain.model.KeyguardSettingsPopupMenuModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.plugins.ActivityStarter +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn + +/** Business logic for use-cases related to the keyguard long-press feature. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class KeyguardLongPressInteractor +@Inject +constructor( + @Application unsafeContext: Context, + @Application scope: CoroutineScope, + transitionInteractor: KeyguardTransitionInteractor, + repository: KeyguardRepository, + private val activityStarter: ActivityStarter, + private val logger: UiEventLogger, + private val featureFlags: FeatureFlags, + broadcastDispatcher: BroadcastDispatcher, +) { + private val appContext = unsafeContext.applicationContext + + private val _isLongPressHandlingEnabled: StateFlow<Boolean> = + if (isFeatureEnabled()) { + combine( + transitionInteractor.finishedKeyguardState.map { + it == KeyguardState.LOCKSCREEN + }, + repository.isQuickSettingsVisible, + ) { isFullyTransitionedToLockScreen, isQuickSettingsVisible -> + isFullyTransitionedToLockScreen && !isQuickSettingsVisible + } + } else { + flowOf(false) + } + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + /** Whether the long-press handling feature should be enabled. */ + val isLongPressHandlingEnabled: Flow<Boolean> = _isLongPressHandlingEnabled + + private val _menu = MutableStateFlow<KeyguardSettingsPopupMenuModel?>(null) + /** Model for a menu that should be shown; `null` when no menu should be shown. */ + val menu: Flow<KeyguardSettingsPopupMenuModel?> = + isLongPressHandlingEnabled.flatMapLatest { isEnabled -> + if (isEnabled) { + _menu + } else { + flowOf(null) + } + } + + init { + if (isFeatureEnabled()) { + broadcastDispatcher + .broadcastFlow( + IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), + ) + .onEach { hideMenu() } + .launchIn(scope) + } + } + + /** Notifies that the user has long-pressed on the lock screen. */ + fun onLongPress(x: Int, y: Int) { + if (!_isLongPressHandlingEnabled.value) { + return + } + + showMenu( + x = x, + y = y, + ) + } + + private fun isFeatureEnabled(): Boolean { + return featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED) && + featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI) + } + + /** Updates application state to ask to show the menu at the given coordinates. */ + private fun showMenu( + x: Int, + y: Int, + ) { + _menu.value = + KeyguardSettingsPopupMenuModel( + position = + Position( + x = x, + y = y, + ), + onClicked = { + hideMenu() + navigateToLockScreenSettings() + }, + onDismissed = { hideMenu() }, + ) + logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN) + } + + /** Updates application state to ask to hide the menu. */ + private fun hideMenu() { + _menu.value = null + } + + /** Opens the wallpaper picker screen after the device is unlocked by the user. */ + private fun navigateToLockScreenSettings() { + logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED) + activityStarter.dismissKeyguardThenExecute( + /* action= */ { + appContext.startActivity( + Intent(Intent.ACTION_SET_WALLPAPER).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + appContext + .getString(R.string.config_wallpaperPickerPackage) + .takeIf { it.isNotEmpty() } + ?.let { packageName -> setPackage(packageName) } + } + ) + true + }, + /* cancel= */ {}, + /* afterKeyguardGone= */ true, + ) + } + + enum class LogEvents( + private val _id: Int, + ) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "The lock screen was long-pressed and we showed the settings popup menu.") + LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN(1292), + @UiEvent(doc = "The lock screen long-press popup menu was clicked.") + LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED(1293), + ; + + override fun getId() = _id + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 9ddc575307b5..57c3b3143c62 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -154,7 +154,7 @@ constructor( val slots = repository.get().getSlotPickerRepresentations() val slot = slots.find { it.id == slotId } ?: return false val selections = - repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList() + repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList() val alreadySelected = selections.remove(affordanceId) if (!alreadySelected) { while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) { @@ -193,7 +193,7 @@ constructor( if (affordanceId.isNullOrEmpty()) { return if ( - repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty() + repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).isEmpty() ) { false } else { @@ -203,7 +203,7 @@ constructor( } val selections = - repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList() + repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList() return if (selections.remove(affordanceId)) { repository .get() @@ -220,7 +220,7 @@ constructor( /** Returns affordance IDs indexed by slot ID, for all known slots. */ suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> { val slots = repository.get().getSlotPickerRepresentations() - val selections = repository.get().getSelections() + val selections = repository.get().getCurrentSelections() val affordanceById = getAffordancePickerRepresentations().associateBy { affordance -> affordance.id } return slots.associate { slot -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt new file mode 100644 index 000000000000..7c61e7108265 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.model + +import com.android.systemui.common.shared.model.Position + +/** Models a settings popup menu for the lock screen. */ +data class KeyguardSettingsPopupMenuModel( + /** Where the menu should be anchored, roughly in screen space. */ + val position: Position, + /** Callback to invoke when the menu gets clicked by the user. */ + val onClicked: () -> Unit, + /** Callback to invoke when the menu gets dismissed by the user. */ + val onDismissed: () -> Unit, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt index bb5ac84c6e54..8222dd54f46d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt @@ -25,4 +25,5 @@ object KeyguardBouncerConstants { */ const val EXPANSION_HIDDEN = 1f const val EXPANSION_VISIBLE = 0f + const val ALPHA_EXPANSION_THRESHOLD = 0.95f } 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 d020529d2bae..3319f9d467ec 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 @@ -49,6 +49,7 @@ import kotlin.math.pow import kotlin.math.sqrt import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest @@ -66,6 +67,8 @@ import kotlinx.coroutines.launch object KeyguardBottomAreaViewBinder { private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L + private const val SCALE_SELECTED_BUTTON = 1.23f + private const val DIM_ALPHA = 0.3f /** * Defines interface for an object that acts as the binding between the view and its view-model. @@ -161,12 +164,26 @@ object KeyguardBottomAreaViewBinder { ambientIndicationArea?.alpha = alpha indicationArea.alpha = alpha - startButton.alpha = alpha - endButton.alpha = alpha } } launch { + updateButtonAlpha( + view = startButton, + viewModel = viewModel.startButton, + alphaFlow = viewModel.alpha, + ) + } + + launch { + updateButtonAlpha( + view = endButton, + viewModel = viewModel.endButton, + alphaFlow = viewModel.alpha, + ) + } + + launch { viewModel.indicationAreaTranslationX.collect { translationX -> indicationArea.translationX = translationX ambientIndicationArea?.translationX = translationX @@ -315,6 +332,11 @@ object KeyguardBottomAreaViewBinder { } else { null } + view + .animate() + .scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f) + .scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f) + .start() view.isClickable = viewModel.isClickable if (viewModel.isClickable) { @@ -333,6 +355,17 @@ object KeyguardBottomAreaViewBinder { view.isSelected = viewModel.isSelected } + private suspend fun updateButtonAlpha( + view: View, + viewModel: Flow<KeyguardQuickAffordanceViewModel>, + alphaFlow: Flow<Float>, + ) { + combine(viewModel.map { it.isDimmed }, alphaFlow) { isDimmed, alpha -> + if (isDimmed) DIM_ALPHA else alpha + } + .collect { view.alpha = it } + } + private class OnTouchListener( private val view: View, private val viewModel: KeyguardQuickAffordanceViewModel, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index 5e46c5d1f67a..c1731e031333 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -106,13 +106,6 @@ object KeyguardBouncerViewBinder { hostViewController.appear( SystemBarUtils.getStatusBarHeight(view.context) ) - } - } - - launch { - viewModel.showWithFullExpansion.collect { model -> - hostViewController.resetSecurityContainer() - hostViewController.showPromptReason(model.promptReason) hostViewController.onResume() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt new file mode 100644 index 000000000000..d85682b3bab8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import android.annotation.SuppressLint +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.PopupWindow +import com.android.systemui.R +import com.android.systemui.common.ui.binder.IconViewBinder +import com.android.systemui.common.ui.binder.TextViewBinder +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsPopupMenuViewModel + +object KeyguardLongPressPopupViewBinder { + @SuppressLint("InflateParams") // We don't care that the parent is null. + fun createAndShow( + container: View, + viewModel: KeyguardSettingsPopupMenuViewModel, + onDismissed: () -> Unit, + ): () -> Unit { + val contentView: View = + LayoutInflater.from(container.context) + .inflate( + R.layout.keyguard_settings_popup_menu, + null, + ) + + contentView.setOnClickListener { viewModel.onClicked() } + IconViewBinder.bind( + icon = viewModel.icon, + view = contentView.requireViewById(R.id.icon), + ) + TextViewBinder.bind( + view = contentView.requireViewById(R.id.text), + viewModel = viewModel.text, + ) + + val popupWindow = + PopupWindow(container.context).apply { + windowLayoutType = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG + setBackgroundDrawable(null) + animationStyle = com.android.internal.R.style.Animation_Dialog + isOutsideTouchable = true + isFocusable = true + setContentView(contentView) + setOnDismissListener { onDismissed() } + contentView.measure( + View.MeasureSpec.makeMeasureSpec( + 0, + View.MeasureSpec.UNSPECIFIED, + ), + View.MeasureSpec.makeMeasureSpec( + 0, + View.MeasureSpec.UNSPECIFIED, + ), + ) + showAtLocation( + container, + Gravity.NO_GRAVITY, + viewModel.position.x - contentView.measuredWidth / 2, + viewModel.position.y - + contentView.measuredHeight - + container.context.resources.getDimensionPixelSize( + R.dimen.keyguard_long_press_settings_popup_vertical_offset + ), + ) + } + + return { popupWindow.dismiss() } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt new file mode 100644 index 000000000000..ef3f242a39a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import android.view.View +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.common.ui.view.LongPressHandlingView +import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.FalsingManager +import kotlinx.coroutines.launch + +object KeyguardLongPressViewBinder { + /** + * Drives UI for the lock screen long-press feature. + * + * @param view The view that listens for long-presses. + * @param viewModel The view-model that models the UI state. + * @param onSingleTap A callback to invoke when the system decides that there was a single tap. + * @param falsingManager [FalsingManager] for making sure the long-press didn't just happen in + * the user's pocket. + */ + @JvmStatic + fun bind( + view: LongPressHandlingView, + viewModel: KeyguardLongPressViewModel, + onSingleTap: () -> Unit, + falsingManager: FalsingManager, + ) { + view.listener = + object : LongPressHandlingView.Listener { + override fun onLongPressDetected(view: View, x: Int, y: Int) { + if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { + return + } + + viewModel.onLongPress( + x = x, + y = y, + ) + } + + override fun onSingleTapDetected(view: View) { + if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + return + } + + onSingleTap() + } + } + + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.isLongPressHandlingEnabled.collect { isEnabled -> + view.setLongPressHandlingEnabled(isEnabled) + } + } + + launch { + var dismissMenu: (() -> Unit)? = null + + viewModel.menu.collect { menuOrNull -> + if (menuOrNull != null) { + dismissMenu = + KeyguardLongPressPopupViewBinder.createAndShow( + container = view, + viewModel = menuOrNull, + onDismissed = menuOrNull.onDismissed, + ) + } else { + dismissMenu?.invoke() + } + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index a5ae8ba58d45..88085749ece0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -24,7 +24,6 @@ import android.content.IntentFilter import android.hardware.display.DisplayManager import android.os.Bundle import android.os.IBinder -import android.view.Gravity import android.view.LayoutInflater import android.view.SurfaceControlViewHost import android.view.View @@ -65,6 +64,11 @@ constructor( val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) private val width: Int = bundle.getInt(KEY_VIEW_WIDTH) private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT) + private val shouldHighlightSelectedAffordance: Boolean = + bundle.getBoolean( + KeyguardQuickAffordancePreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES, + false, + ) private var host: SurfaceControlViewHost @@ -82,6 +86,7 @@ constructor( bundle.getString( KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID, ), + shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, ) runBlocking(mainDispatcher) { host = @@ -154,8 +159,7 @@ constructor( bottomAreaView, FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT, - Gravity.BOTTOM, + FrameLayout.LayoutParams.MATCH_PARENT, ), ) } @@ -195,7 +199,13 @@ constructor( ?.events ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView)) clockView?.let { parentView.removeView(it) } - clockView = clockController.clock?.largeClock?.view?.apply { parentView.addView(this) } + clockView = + clockController.clock?.largeClock?.view?.apply { + if (shouldHighlightSelectedAffordance) { + alpha = DIM_ALPHA + } + parentView.addView(this) + } } companion object { @@ -203,5 +213,7 @@ constructor( private const val KEY_VIEW_WIDTH = "width" private const val KEY_VIEW_HEIGHT = "height" private const val KEY_DISPLAY_ID = "display_id" + + private const val DIM_ALPHA = 0.3f } } 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 5d85680efcf4..1e3b60c27d84 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 @@ -45,12 +45,17 @@ constructor( private val bottomAreaInteractor: KeyguardBottomAreaInteractor, private val burnInHelperWrapper: BurnInHelperWrapper, ) { + data class PreviewMode( + val isInPreviewMode: Boolean = false, + val shouldHighlightSelectedAffordance: Boolean = false, + ) + /** * Whether this view-model instance is powering the preview experience that renders exclusively * in the wallpaper picker application. This should _always_ be `false` for the real lock screen * experience. */ - private val isInPreviewMode = MutableStateFlow(false) + private val previewMode = MutableStateFlow(PreviewMode()) /** * ID of the slot that's currently selected in the preview that renders exclusively in the @@ -87,8 +92,8 @@ constructor( keyguardInteractor.isDozing.map { !it }.distinctUntilChanged() /** An observable for the alpha level for the entire bottom area. */ val alpha: Flow<Float> = - isInPreviewMode.flatMapLatest { isInPreviewMode -> - if (isInPreviewMode) { + previewMode.flatMapLatest { + if (it.isInPreviewMode) { flowOf(1f) } else { bottomAreaInteractor.alpha.distinctUntilChanged() @@ -129,9 +134,18 @@ constructor( * lock screen. * * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one. + * @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be + * highlighted (while all others are dimmed to make the selected one stand out). */ - fun enablePreviewMode(initiallySelectedSlotId: String?) { - isInPreviewMode.value = true + fun enablePreviewMode( + initiallySelectedSlotId: String?, + shouldHighlightSelectedAffordance: Boolean, + ) { + previewMode.value = + PreviewMode( + isInPreviewMode = true, + shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, + ) onPreviewSlotSelected( initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START ) @@ -150,9 +164,9 @@ constructor( private fun button( position: KeyguardQuickAffordancePosition ): Flow<KeyguardQuickAffordanceViewModel> { - return isInPreviewMode.flatMapLatest { isInPreviewMode -> + return previewMode.flatMapLatest { previewMode -> combine( - if (isInPreviewMode) { + if (previewMode.isInPreviewMode) { quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position) } else { quickAffordanceInteractor.quickAffordance(position = position) @@ -161,11 +175,18 @@ constructor( areQuickAffordancesFullyOpaque, selectedPreviewSlotId, ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId -> + val isSelected = selectedPreviewSlotId == position.toSlotId() model.toViewModel( - animateReveal = !isInPreviewMode && animateReveal, - isClickable = isFullyOpaque && !isInPreviewMode, + animateReveal = !previewMode.isInPreviewMode && animateReveal, + isClickable = isFullyOpaque && !previewMode.isInPreviewMode, isSelected = - (isInPreviewMode && selectedPreviewSlotId == position.toSlotId()), + previewMode.isInPreviewMode && + previewMode.shouldHighlightSelectedAffordance && + isSelected, + isDimmed = + previewMode.isInPreviewMode && + previewMode.shouldHighlightSelectedAffordance && + !isSelected, ) } .distinctUntilChanged() @@ -176,6 +197,7 @@ constructor( animateReveal: Boolean, isClickable: Boolean, isSelected: Boolean, + isDimmed: Boolean, ): KeyguardQuickAffordanceViewModel { return when (this) { is KeyguardQuickAffordanceModel.Visible -> @@ -194,6 +216,7 @@ constructor( isActivated = activationState is ActivationState.Active, isSelected = isSelected, useLongPress = quickAffordanceInteractor.useLongPress, + isDimmed = isDimmed, ) is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt index c6002d6db91a..737c35d866c2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -20,12 +20,10 @@ import android.view.View import com.android.systemui.keyguard.data.BouncerView import com.android.systemui.keyguard.data.BouncerViewDelegate import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor -import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map /** Models UI state for the lock screen bouncer; handles user input. */ @@ -44,10 +42,6 @@ constructor( /** Observe whether bouncer is showing. */ val show: Flow<KeyguardBouncerModel> = interactor.show - /** Observe visible expansion when bouncer is showing. */ - val showWithFullExpansion: Flow<KeyguardBouncerModel> = - interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE } - /** Observe whether bouncer is hiding. */ val hide: Flow<Unit> = interactor.hide diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt new file mode 100644 index 000000000000..d896390fd471 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.R +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** Models UI state to support the lock screen long-press feature. */ +class KeyguardLongPressViewModel +@Inject +constructor( + private val interactor: KeyguardLongPressInteractor, +) { + + /** Whether the long-press handling feature should be enabled. */ + val isLongPressHandlingEnabled: Flow<Boolean> = interactor.isLongPressHandlingEnabled + + /** View-model for a menu that should be shown; `null` when no menu should be shown. */ + val menu: Flow<KeyguardSettingsPopupMenuViewModel?> = + interactor.menu.map { model -> + model?.let { + KeyguardSettingsPopupMenuViewModel( + icon = + Icon.Resource( + res = R.drawable.ic_settings, + contentDescription = null, + ), + text = + Text.Resource( + res = R.string.lock_screen_settings, + ), + position = model.position, + onClicked = model.onClicked, + onDismissed = model.onDismissed, + ) + } + } + + /** Notifies that the user has long-pressed on the lock screen. */ + fun onLongPress( + x: Int, + y: Int, + ) { + interactor.onLongPress( + x = x, + y = y, + ) + } +} 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 cf3a6daa40bb..cb68a82118e2 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 isActivated: Boolean = false, val isSelected: Boolean = false, val useLongPress: Boolean = false, + val isDimmed: Boolean = false, ) { data class OnClickedParameters( val configKey: String, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt new file mode 100644 index 000000000000..0571b05b4751 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Position +import com.android.systemui.common.shared.model.Text + +/** Models the UI state of a keyguard settings popup menu. */ +data class KeyguardSettingsPopupMenuViewModel( + val icon: Icon, + val text: Text, + /** Where the menu should be anchored, roughly in screen space. */ + val position: Position, + /** Callback to invoke when the menu gets clicked by the user. */ + val onClicked: () -> Unit, + /** Callback to invoke when the menu gets dismissed by the user. */ + val onDismissed: () -> Unit, +) 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 15306f9cc23e..6c6f7e9744fe 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -200,28 +200,6 @@ public class LogModule { } /** - * Provides a logging buffer for logs related to the media tap-to-transfer chip on the sender - * device. See {@link com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger}. - */ - @Provides - @SysUISingleton - @MediaTttSenderLogBuffer - public static LogBuffer provideMediaTttSenderLogBuffer(LogBufferFactory factory) { - return factory.create("MediaTttSender", 20); - } - - /** - * Provides a logging buffer for logs related to the media tap-to-transfer chip on the receiver - * device. See {@link com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger}. - */ - @Provides - @SysUISingleton - @MediaTttReceiverLogBuffer - public static LogBuffer provideMediaTttReceiverLogBuffer(LogBufferFactory factory) { - return factory.create("MediaTttReceiver", 20); - } - - /** * Provides a logging buffer for logs related to the media mute-await connections. See * {@link com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager}. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt index 1a10b18a5a69..8c1ec166fb2e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt @@ -21,6 +21,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import com.android.internal.widget.CachingIconView import com.android.systemui.R import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.media.controls.ui.IlluminationDrawable @@ -29,18 +30,15 @@ import com.android.systemui.util.animation.TransitionLayout private const val TAG = "RecommendationViewHolder" /** ViewHolder for a Smartspace media recommendation. */ -class RecommendationViewHolder private constructor(itemView: View) { +class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) { val recommendations = itemView as TransitionLayout // Recommendation screen - val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon) - val mediaCoverItems = - listOf<ImageView>( - itemView.requireViewById(R.id.media_cover1), - itemView.requireViewById(R.id.media_cover2), - itemView.requireViewById(R.id.media_cover3) - ) + lateinit var cardIcon: ImageView + lateinit var mediaAppIcons: List<CachingIconView> + lateinit var cardTitle: TextView + val mediaCoverContainers = listOf<ViewGroup>( itemView.requireViewById(R.id.media_cover1_container), @@ -48,21 +46,45 @@ class RecommendationViewHolder private constructor(itemView: View) { itemView.requireViewById(R.id.media_cover3_container) ) val mediaTitles: List<TextView> = - listOf( - itemView.requireViewById(R.id.media_title1), - itemView.requireViewById(R.id.media_title2), - itemView.requireViewById(R.id.media_title3) - ) + if (updatedView) { + mediaCoverContainers.map { it.requireViewById(R.id.media_title) } + } else { + listOf( + itemView.requireViewById(R.id.media_title1), + itemView.requireViewById(R.id.media_title2), + itemView.requireViewById(R.id.media_title3) + ) + } val mediaSubtitles: List<TextView> = - listOf( - itemView.requireViewById(R.id.media_subtitle1), - itemView.requireViewById(R.id.media_subtitle2), - itemView.requireViewById(R.id.media_subtitle3) - ) + if (updatedView) { + mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) } + } else { + listOf( + itemView.requireViewById(R.id.media_subtitle1), + itemView.requireViewById(R.id.media_subtitle2), + itemView.requireViewById(R.id.media_subtitle3) + ) + } + val mediaCoverItems: List<ImageView> = + if (updatedView) { + mediaCoverContainers.map { it.requireViewById(R.id.media_cover) } + } else { + listOf( + itemView.requireViewById(R.id.media_cover1), + itemView.requireViewById(R.id.media_cover2), + itemView.requireViewById(R.id.media_cover3) + ) + } val gutsViewHolder = GutsViewHolder(itemView) init { + if (updatedView) { + mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) } + cardTitle = itemView.requireViewById(R.id.media_rec_title) + } else { + cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon) + } (recommendations.background as IlluminationDrawable).let { background -> mediaCoverContainers.forEach { background.registerLightSource(it) } background.registerLightSource(gutsViewHolder.cancel) @@ -83,36 +105,52 @@ class RecommendationViewHolder private constructor(itemView: View) { * @param parent Parent of inflated view. */ @JvmStatic - fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder { + fun create( + inflater: LayoutInflater, + parent: ViewGroup, + updatedView: Boolean, + ): RecommendationViewHolder { val itemView = - inflater.inflate( - R.layout.media_smartspace_recommendations, - parent, - false /* attachToRoot */ - ) + if (updatedView) { + inflater.inflate( + R.layout.media_recommendations, + parent, + false /* attachToRoot */ + ) + } else { + inflater.inflate( + R.layout.media_smartspace_recommendations, + parent, + false /* attachToRoot */ + ) + } // Because this media view (a TransitionLayout) is used to measure and layout the views // in various states before being attached to its parent, we can't depend on the default // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction. itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE - return RecommendationViewHolder(itemView) + return RecommendationViewHolder(itemView, updatedView) } // Res Ids for the control components on the recommendation view. val controlsIds = setOf( R.id.recommendation_card_icon, + R.id.media_rec_title, R.id.media_cover1, R.id.media_cover2, R.id.media_cover3, + R.id.media_cover, R.id.media_cover1_container, R.id.media_cover2_container, R.id.media_cover3_container, R.id.media_title1, R.id.media_title2, R.id.media_title3, + R.id.media_title, R.id.media_subtitle1, R.id.media_subtitle2, - R.id.media_subtitle3 + R.id.media_subtitle3, + R.id.media_subtitle, ) val mediaTitlesAndSubtitlesIds = @@ -120,9 +158,11 @@ class RecommendationViewHolder private constructor(itemView: View) { R.id.media_title1, R.id.media_title2, R.id.media_title3, + R.id.media_title, R.id.media_subtitle1, R.id.media_subtitle2, - R.id.media_subtitle3 + R.id.media_subtitle3, + R.id.media_subtitle, ) val mediaContainersIds = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index a13279717d05..b11f628623bb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -303,6 +303,7 @@ class MediaDataManager( mediaTimeoutListener.stateCallback = { key: String, state: PlaybackState -> updateState(key, state) } + mediaTimeoutListener.sessionCallback = { key: String -> onSessionDestroyed(key) } mediaResumeListener.setManager(this) mediaDataFilter.mediaDataManager = this @@ -1289,43 +1290,104 @@ class MediaDataManager( fun onNotificationRemoved(key: String) { Assert.isMainThread() - val removed = mediaEntries.remove(key) - if (useMediaResumption && removed?.resumeAction != null && removed.isLocalSession()) { - Log.d(TAG, "Not removing $key because resumable") - // Move to resume key (aka package name) if that key doesn't already exist. - val resumeAction = getResumeMediaAction(removed.resumeAction!!) - val updated = - removed.copy( - token = null, - actions = listOf(resumeAction), - semanticActions = MediaButton(playOrPause = resumeAction), - actionsToShowInCompact = listOf(0), - active = false, - resumption = true, - isPlaying = false, - isClearable = true + val removed = mediaEntries.remove(key) ?: return + + if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) { + convertToResumePlayer(removed) + } else if (mediaFlags.isRetainingPlayersEnabled()) { + handlePossibleRemoval(removed, notificationRemoved = true) + } else { + notifyMediaDataRemoved(key) + logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) + } + } + + private fun onSessionDestroyed(key: String) { + if (!mediaFlags.isRetainingPlayersEnabled()) return + + if (DEBUG) Log.d(TAG, "session destroyed for $key") + val entry = mediaEntries.remove(key) ?: return + // Clear token since the session is no longer valid + val updated = entry.copy(token = null) + handlePossibleRemoval(updated) + } + + /** + * Convert to resume state if the player is no longer valid and active, then notify listeners + * that the data was updated. Does not convert to resume state if the player is still valid, or + * if it was removed before becoming inactive. (Assumes that [removed] was removed from + * [mediaEntries] before this function was called) + */ + private fun handlePossibleRemoval(removed: MediaData, notificationRemoved: Boolean = false) { + val key = removed.notificationKey!! + val hasSession = removed.token != null + if (hasSession && removed.semanticActions != null) { + // The app was using session actions, and the session is still valid: keep player + if (DEBUG) Log.d(TAG, "Notification removed but using session actions $key") + mediaEntries.put(key, removed) + notifyMediaDataLoaded(key, key, removed) + } else if (!notificationRemoved && removed.semanticActions == null) { + // The app was using notification actions, and notif wasn't removed yet: keep player + if (DEBUG) Log.d(TAG, "Session destroyed but using notification actions $key") + mediaEntries.put(key, removed) + notifyMediaDataLoaded(key, key, removed) + } else if (removed.active) { + // This player was still active - it didn't last long enough to time out: remove + if (DEBUG) Log.d(TAG, "Removing still-active player $key") + notifyMediaDataRemoved(key) + logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) + } else { + // Convert to resume + if (DEBUG) { + Log.d( + TAG, + "Notification ($notificationRemoved) and/or session " + + "($hasSession) gone for inactive player $key" ) - val pkg = removed.packageName - val migrate = mediaEntries.put(pkg, updated) == null - // Notify listeners of "new" controls when migrating or removed and update when not - if (migrate) { - notifyMediaDataLoaded(pkg, key, updated) - } else { - // Since packageName is used for the key of the resumption controls, it is - // possible that another notification has already been reused for the resumption - // controls of this package. In this case, rather than renaming this player as - // packageName, just remove it and then send a update to the existing resumption - // controls. - notifyMediaDataRemoved(key) - notifyMediaDataLoaded(pkg, pkg, updated) } - logger.logActiveConvertedToResume(updated.appUid, pkg, updated.instanceId) - return + convertToResumePlayer(removed) } - if (removed != null) { + } + + /** Set the given [MediaData] as a resume state player and notify listeners */ + private fun convertToResumePlayer(data: MediaData) { + val key = data.notificationKey!! + if (DEBUG) Log.d(TAG, "Converting $key to resume") + // Move to resume key (aka package name) if that key doesn't already exist. + val resumeAction = data.resumeAction?.let { getResumeMediaAction(it) } + val actions = resumeAction?.let { listOf(resumeAction) } ?: emptyList() + val launcherIntent = + context.packageManager.getLaunchIntentForPackage(data.packageName)?.let { + PendingIntent.getActivity(context, 0, it, PendingIntent.FLAG_IMMUTABLE) + } + val updated = + data.copy( + token = null, + actions = actions, + semanticActions = MediaButton(playOrPause = resumeAction), + actionsToShowInCompact = listOf(0), + active = false, + resumption = true, + isPlaying = false, + isClearable = true, + clickIntent = launcherIntent, + ) + val pkg = data.packageName + val migrate = mediaEntries.put(pkg, updated) == null + // Notify listeners of "new" controls when migrating or removed and update when not + Log.d(TAG, "migrating? $migrate from $key -> $pkg") + if (migrate) { + notifyMediaDataLoaded(key = pkg, oldKey = key, info = updated) + } else { + // Since packageName is used for the key of the resumption controls, it is + // possible that another notification has already been reused for the resumption + // controls of this package. In this case, rather than renaming this player as + // packageName, just remove it and then send a update to the existing resumption + // controls. notifyMediaDataRemoved(key) - logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) + notifyMediaDataLoaded(key = pkg, oldKey = pkg, info = updated) } + logger.logActiveConvertedToResume(updated.appUid, pkg, updated.instanceId) } fun setMediaResumptionEnabled(isEnabled: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt index 7f5c82fb5eee..a898b00790a9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt @@ -71,6 +71,12 @@ constructor( */ lateinit var stateCallback: (String, PlaybackState) -> Unit + /** + * Callback representing that the [MediaSession] for an active control has been destroyed + * @param key Media control unique identifier + */ + lateinit var sessionCallback: (String) -> Unit + init { statusBarStateController.addCallback( object : StatusBarStateController.StateListener { @@ -211,6 +217,7 @@ constructor( } else { // For active controls, if the session is destroyed, clean up everything since we // will need to recreate it if this key is updated later + sessionCallback.invoke(key) destroy() } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index e7f7647797cd..b2ad15522743 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -43,6 +43,7 @@ import com.android.systemui.media.controls.models.recommendation.RecommendationV import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.ui.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT +import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.media.controls.util.SmallHash import com.android.systemui.plugins.ActivityStarter @@ -88,7 +89,8 @@ constructor( falsingManager: FalsingManager, dumpManager: DumpManager, private val logger: MediaUiEventLogger, - private val debugLogger: MediaCarouselControllerLogger + private val debugLogger: MediaCarouselControllerLogger, + private val mediaFlags: MediaFlags, ) : Dumpable { /** The current width of the carousel */ private var currentCarouselWidth: Int = 0 @@ -647,7 +649,11 @@ constructor( val newRecs = mediaControlPanelFactory.get() newRecs.attachRecommendation( - RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent) + RecommendationViewHolder.create( + LayoutInflater.from(context), + mediaContent, + mediaFlags.isRecommendationCardUpdateEnabled() + ) ) newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions val lp = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index 45d50f0e4976..9250a580dfa0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -61,6 +61,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import androidx.appcompat.content.res.AppCompatResources; import androidx.constraintlayout.widget.ConstraintSet; import com.android.internal.annotations.VisibleForTesting; @@ -752,43 +753,16 @@ public class MediaControlPanel { int width = mMediaViewHolder.getAlbumView().getMeasuredWidth(); int height = mMediaViewHolder.getAlbumView().getMeasuredHeight(); - // WallpaperColors.fromBitmap takes a good amount of time. We do that work - // on the background executor to avoid stalling animations on the UI Thread. mBackgroundExecutor.execute(() -> { // Album art ColorScheme mutableColorScheme = null; Drawable artwork; boolean isArtworkBound; Icon artworkIcon = data.getArtwork(); - WallpaperColors wallpaperColors = null; - if (artworkIcon != null) { - if (artworkIcon.getType() == Icon.TYPE_BITMAP - || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) { - // Avoids extra processing if this is already a valid bitmap - wallpaperColors = WallpaperColors - .fromBitmap(artworkIcon.getBitmap()); - } else { - Drawable artworkDrawable = artworkIcon.loadDrawable(mContext); - if (artworkDrawable != null) { - wallpaperColors = WallpaperColors - .fromDrawable(artworkIcon.loadDrawable(mContext)); - } - } - } + WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon); if (wallpaperColors != null) { mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT); - Drawable albumArt = getScaledBackground(artworkIcon, width, height); - GradientDrawable gradient = (GradientDrawable) mContext - .getDrawable(R.drawable.qs_media_scrim); - gradient.setColors(new int[] { - ColorUtilKt.getColorWithAlpha( - MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme), - 0.25f), - ColorUtilKt.getColorWithAlpha( - MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme), - 0.9f), - }); - artwork = new LayerDrawable(new Drawable[] { albumArt, gradient }); + artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height); isArtworkBound = true; } else { // If there's no artwork, use colors from the app icon @@ -867,6 +841,96 @@ public class MediaControlPanel { }); } + private void bindRecommendationArtwork( + SmartspaceAction recommendation, + String packageName, + int itemIndex + ) { + final int traceCookie = recommendation.hashCode(); + final String traceName = + "MediaControlPanel#bindRecommendationArtwork<" + packageName + ">"; + Trace.beginAsyncSection(traceName, traceCookie); + + // Capture width & height from views in foreground for artwork scaling in background + int width = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredWidth(); + int height = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredHeight(); + + mBackgroundExecutor.execute(() -> { + // Album art + ColorScheme mutableColorScheme = null; + Drawable artwork; + Icon artworkIcon = recommendation.getIcon(); + WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon); + if (wallpaperColors != null) { + mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT); + artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height); + } else { + artwork = new ColorDrawable(Color.TRANSPARENT); + } + + mMainExecutor.execute(() -> { + // Bind the artwork drawable to media cover. + ImageView mediaCover = + mRecommendationViewHolder.getMediaCoverItems().get(itemIndex); + mediaCover.setImageDrawable(artwork); + + // Set up the app icon. + ImageView appIconView = mRecommendationViewHolder.getMediaAppIcons().get(itemIndex); + appIconView.clearColorFilter(); + try { + Drawable icon = mContext.getPackageManager() + .getApplicationIcon(packageName); + appIconView.setImageDrawable(icon); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Cannot find icon for package " + packageName, e); + appIconView.setImageResource(R.drawable.ic_music_note); + } + Trace.endAsyncSection(traceName, traceCookie); + }); + }); + } + + // This method should be called from a background thread. WallpaperColors.fromBitmap takes a + // good amount of time. We do that work on the background executor to avoid stalling animations + // on the UI Thread. + private WallpaperColors getWallpaperColor(Icon artworkIcon) { + if (artworkIcon != null) { + if (artworkIcon.getType() == Icon.TYPE_BITMAP + || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) { + // Avoids extra processing if this is already a valid bitmap + return WallpaperColors + .fromBitmap(artworkIcon.getBitmap()); + } else { + Drawable artworkDrawable = artworkIcon.loadDrawable(mContext); + if (artworkDrawable != null) { + return WallpaperColors + .fromDrawable(artworkIcon.loadDrawable(mContext)); + } + } + } + return null; + } + + private LayerDrawable addGradientToIcon( + Icon artworkIcon, + ColorScheme mutableColorScheme, + int width, + int height + ) { + Drawable albumArt = getScaledBackground(artworkIcon, width, height); + GradientDrawable gradient = (GradientDrawable) AppCompatResources + .getDrawable(mContext, R.drawable.qs_media_scrim); + gradient.setColors(new int[] { + ColorUtilKt.getColorWithAlpha( + MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme), + 0.25f), + ColorUtilKt.getColorWithAlpha( + MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme), + 0.9f), + }); + return new LayerDrawable(new Drawable[] { albumArt, gradient }); + } + private void scaleTransitionDrawableLayer(TransitionDrawable transitionDrawable, int layer, int targetWidth, int targetHeight) { Drawable drawable = transitionDrawable.getDrawable(layer); @@ -1224,8 +1288,10 @@ public class MediaControlPanel { PackageManager packageManager = mContext.getPackageManager(); // Set up media source app's logo. Drawable icon = packageManager.getApplicationIcon(applicationInfo); - ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon(); - headerLogoImageView.setImageDrawable(icon); + if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { + ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon(); + headerLogoImageView.setImageDrawable(icon); + } fetchAndUpdateRecommendationColors(icon); // Set up media rec card's tap action if applicable. @@ -1245,7 +1311,15 @@ public class MediaControlPanel { // Set up media item cover. ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex); - mediaCoverImageView.setImageIcon(recommendation.getIcon()); + if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { + bindRecommendationArtwork( + recommendation, + data.getPackageName(), + itemIndex + ); + } else { + mediaCoverImageView.setImageIcon(recommendation.getIcon()); + } // Set up the media item's click listener if applicable. ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex); @@ -1275,7 +1349,6 @@ public class MediaControlPanel { recommendation.getTitle(), artistName, appName)); } - // Set up title CharSequence title = recommendation.getTitle(); hasTitle |= !TextUtils.isEmpty(title); @@ -1353,6 +1426,10 @@ public class MediaControlPanel { int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme); int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme); + if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { + mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor); + } + mRecommendationViewHolder.getRecommendations() .setBackgroundTintList(ColorStateList.valueOf(backgroundColor)); mRecommendationViewHolder.getMediaTitles().forEach( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index 2ec7be6eaa32..1e6002cdc717 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -25,6 +25,7 @@ import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.media.controls.models.player.MediaViewHolder import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha +import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.MeasurementOutput import com.android.systemui.util.animation.TransitionLayout @@ -45,7 +46,8 @@ constructor( private val context: Context, private val configurationController: ConfigurationController, private val mediaHostStatesManager: MediaHostStatesManager, - private val logger: MediaViewLogger + private val logger: MediaViewLogger, + private val mediaFlags: MediaFlags, ) { /** @@ -646,8 +648,13 @@ constructor( expandedLayout.load(context, R.xml.media_session_expanded) } TYPE.RECOMMENDATION -> { - collapsedLayout.load(context, R.xml.media_recommendation_collapsed) - expandedLayout.load(context, R.xml.media_recommendation_expanded) + if (mediaFlags.isRecommendationCardUpdateEnabled()) { + collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed) + expandedLayout.load(context, R.xml.media_recommendations_view_expanded) + } else { + collapsedLayout.load(context, R.xml.media_recommendation_collapsed) + expandedLayout.load(context, R.xml.media_recommendation_expanded) + } } } refreshState() diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 5bc35caed515..81efa3688aab 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -45,4 +45,14 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** Check whether we show explicit indicator on UMO */ fun isExplicitIndicatorEnabled() = featureFlags.isEnabled(Flags.MEDIA_EXPLICIT_INDICATOR) + + /** + * If true, keep active media controls for the lifetime of the MediaSession, regardless of + * whether the underlying notification was dismissed + */ + fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS) + + /** Check whether we show the updated recommendation card. */ + fun isRecommendationCardUpdateEnabled() = + featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE) } diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index bb833df1ff69..9ae4577b0394 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -17,8 +17,7 @@ package com.android.systemui.media.dagger; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.log.dagger.MediaTttReceiverLogBuffer; -import com.android.systemui.log.dagger.MediaTttSenderLogBuffer; +import com.android.systemui.log.LogBufferFactory; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.media.controls.ui.MediaHost; @@ -29,12 +28,9 @@ import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; import com.android.systemui.media.taptotransfer.MediaTttFlags; -import com.android.systemui.media.taptotransfer.common.MediaTttLogger; -import com.android.systemui.media.taptotransfer.receiver.ChipReceiverInfo; -import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger; -import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger; +import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogBuffer; +import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogBuffer; import com.android.systemui.plugins.log.LogBuffer; -import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo; import java.util.Optional; @@ -94,22 +90,22 @@ public interface MediaModule { return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager); } + /** Provides a logging buffer related to the media tap-to-transfer chip on the sender device. */ @Provides @SysUISingleton - @MediaTttSenderLogger - static MediaTttLogger<ChipbarInfo> providesMediaTttSenderLogger( - @MediaTttSenderLogBuffer LogBuffer buffer - ) { - return new MediaTttLogger<>("Sender", buffer); + @MediaTttSenderLogBuffer + static LogBuffer provideMediaTttSenderLogBuffer(LogBufferFactory factory) { + return factory.create("MediaTttSender", 30); } + /** + * Provides a logging buffer related to the media tap-to-transfer chip on the receiver device. + */ @Provides @SysUISingleton - @MediaTttReceiverLogger - static MediaTttLogger<ChipReceiverInfo> providesMediaTttReceiverLogger( - @MediaTttReceiverLogBuffer LogBuffer buffer - ) { - return new MediaTttLogger<>("Receiver", buffer); + @MediaTttReceiverLogBuffer + static LogBuffer provideMediaTttReceiverLogBuffer(LogBufferFactory factory) { + return factory.create("MediaTttReceiver", 20); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 51b5a3d1a08c..769e0c8ab3c2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -16,17 +16,21 @@ package com.android.systemui.media.dialog; +import android.content.Context; import android.content.res.ColorStateList; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; +import android.os.Build; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.TextView; +import androidx.annotation.DoNotInline; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.core.widget.CompoundButtonCompat; import androidx.recyclerview.widget.RecyclerView; @@ -186,6 +190,17 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mCurrentActivePosition = position; updateFullItemClickListener(v -> onItemClick(v, device)); setSingleLineLayout(getItemTitle(device)); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && mController.isSubStatusSupported() && device.hasDisabledReason()) { + //update to subtext with device status + setUpDeviceIcon(device); + mSubTitleText.setText( + Api34Impl.composeDisabledReason(device.getDisableReason(), mContext)); + updateConnectionFailedStatusIcon(); + updateFullItemClickListener(null); + setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */, + false /* showProgressBar */, true /* showSubtitle */, + true /* showStatus */); } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { setUpDeviceIcon(device); updateConnectionFailedStatusIcon(); @@ -389,4 +404,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mTitleText.setText(groupDividerTitle); } } + + @RequiresApi(34) + private static class Api34Impl { + @DoNotInline + static String composeDisabledReason(int reason, Context context) { + return ""; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 4e08050a6f65..dc75538c5d29 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -45,6 +45,7 @@ import android.widget.SeekBar; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.recyclerview.widget.RecyclerView; import com.android.settingslib.Utils; @@ -142,11 +143,12 @@ public abstract class MediaOutputBaseAdapter extends final TextView mVolumeValueText; final ImageView mTitleIcon; final ProgressBar mProgressBar; - final MediaOutputSeekbar mSeekBar; final LinearLayout mTwoLineLayout; final ImageView mStatusIcon; final CheckBox mCheckBox; final ViewGroup mEndTouchArea; + @VisibleForTesting + MediaOutputSeekbar mSeekBar; private String mDeviceId; private ValueAnimator mCornerAnimator; private ValueAnimator mVolumeAnimator; @@ -390,6 +392,7 @@ public abstract class MediaOutputBaseAdapter extends mTitleIcon.setVisibility(View.VISIBLE); mVolumeValueText.setVisibility(View.GONE); } + mController.logInteractionAdjustVolume(device); mIsDragging = false; } }); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index f95da273fae6..5f5c686e36e2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -141,7 +141,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @VisibleForTesting LocalMediaManager mLocalMediaManager; @VisibleForTesting - private MediaOutputMetricLogger mMetricLogger; + MediaOutputMetricLogger mMetricLogger; private int mCurrentState; private int mColorItemContent; @@ -757,6 +757,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING); } + public boolean isSubStatusSupported() { + return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_DEVICE_STATUS); + } + List<MediaDevice> getGroupMediaDevices() { final List<MediaDevice> selectedDevices = getSelectedMediaDevice(); final List<MediaDevice> selectableDevices = getSelectableMediaDevice(); @@ -866,12 +870,15 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } void adjustVolume(MediaDevice device, int volume) { - mMetricLogger.logInteractionAdjustVolume(device); ThreadUtils.postOnBackgroundThread(() -> { device.requestSetVolume(volume); }); } + void logInteractionAdjustVolume(MediaDevice device) { + mMetricLogger.logInteractionAdjustVolume(device); + } + String getPackageName() { return mPackageName; } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt deleted file mode 100644 index 8aef9385fe3e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt +++ /dev/null @@ -1,114 +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.media.taptotransfer.common - -import com.android.systemui.plugins.log.LogBuffer -import com.android.systemui.plugins.log.LogLevel -import com.android.systemui.temporarydisplay.TemporaryViewInfo -import com.android.systemui.temporarydisplay.TemporaryViewLogger - -/** - * A logger for media tap-to-transfer events. - * - * @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver". - * - * TODO(b/245610654): We should de-couple the sender and receiver loggers, since they're vastly - * different experiences. - */ -class MediaTttLogger<T : TemporaryViewInfo>( - deviceTypeTag: String, - buffer: LogBuffer -) : TemporaryViewLogger<T>(buffer, BASE_TAG + deviceTypeTag) { - /** Logs a change in the chip state for the given [mediaRouteId]. */ - fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) { - buffer.log( - tag, - LogLevel.DEBUG, - { - str1 = stateName - str2 = mediaRouteId - str3 = packageName - }, - { "State changed to $str1 for ID=$str2 package=$str3" } - ) - } - - /** - * Logs an error in trying to update to [displayState]. - * - * [displayState] is either a [android.app.StatusBarManager.MediaTransferSenderState] or - * a [android.app.StatusBarManager.MediaTransferReceiverState]. - */ - fun logStateChangeError(displayState: Int) { - buffer.log( - tag, - LogLevel.ERROR, - { int1 = displayState }, - { "Cannot display state=$int1; aborting" } - ) - } - - /** - * Logs an invalid sender state transition error in trying to update to [desiredState]. - * - * @param currentState the previous state of the chip. - * @param desiredState the new state of the chip. - */ - fun logInvalidStateTransitionError( - currentState: String, - desiredState: String - ) { - buffer.log( - tag, - LogLevel.ERROR, - { - str1 = currentState - str2 = desiredState - }, - { "Cannot display state=$str2 after state=$str1; invalid transition" } - ) - } - - /** Logs that we couldn't find information for [packageName]. */ - fun logPackageNotFound(packageName: String) { - buffer.log( - tag, - LogLevel.DEBUG, - { str1 = packageName }, - { "Package $str1 could not be found" } - ) - } - - /** - * Logs that a removal request has been bypassed (ignored). - * - * @param removalReason the reason that the chip removal was requested. - * @param bypassReason the reason that the request was bypassed. - */ - fun logRemovalBypass(removalReason: String, bypassReason: String) { - buffer.log( - tag, - LogLevel.DEBUG, - { - str1 = removalReason - str2 = bypassReason - }, - { "Chip removal requested due to $str1; however, removal was ignored because $str2" }) - } -} - -private const val BASE_TAG = "MediaTtt" diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt new file mode 100644 index 000000000000..0e839c6dbc7a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt @@ -0,0 +1,60 @@ +/* + * 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.media.taptotransfer.common + +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel + +/** A helper for logging media tap-to-transfer events. */ +object MediaTttLoggerUtils { + fun logStateChange( + buffer: LogBuffer, + tag: String, + stateName: String, + mediaRouteId: String, + packageName: String?, + ) { + buffer.log( + tag, + LogLevel.DEBUG, + { + str1 = stateName + str2 = mediaRouteId + str3 = packageName + }, + { "State changed to $str1 for ID=$str2 package=$str3" } + ) + } + + fun logStateChangeError(buffer: LogBuffer, tag: String, displayState: Int) { + buffer.log( + tag, + LogLevel.ERROR, + { int1 = displayState }, + { "Cannot display state=$int1; aborting" } + ) + } + + fun logPackageNotFound(buffer: LogBuffer, tag: String, packageName: String) { + buffer.log( + tag, + LogLevel.DEBUG, + { str1 = packageName }, + { "Package $str1 could not be found" } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt index 066c1853818f..a3ae943c9704 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -25,7 +25,6 @@ import com.android.systemui.R import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.TintedIcon -import com.android.systemui.temporarydisplay.TemporaryViewInfo /** Utility methods for media tap-to-transfer. */ class MediaTttUtils { @@ -43,12 +42,13 @@ class MediaTttUtils { * default name and icon if we can't find the app name/icon. * * @param appPackageName the package name of the app playing the media. - * @param logger the logger to use for any errors. + * @param onPackageNotFoundException a function run if a + * [PackageManager.NameNotFoundException] occurs. */ fun getIconInfoFromPackageName( context: Context, appPackageName: String?, - logger: MediaTttLogger<out TemporaryViewInfo> + onPackageNotFoundException: () -> Unit, ): IconInfo { if (appPackageName != null) { val packageManager = context.packageManager @@ -70,7 +70,7 @@ class MediaTttUtils { isAppIcon = true ) } catch (e: PackageManager.NameNotFoundException) { - logger.logPackageNotFound(appPackageName) + onPackageNotFoundException.invoke() } } return IconInfo( 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 6884370c505c..34bf74faa11a 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 @@ -30,6 +30,7 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager +import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE import com.android.internal.widget.CachingIconView import com.android.systemui.R import com.android.systemui.common.shared.model.ContentDescription @@ -39,7 +40,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.media.taptotransfer.common.MediaTttIcon -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController @@ -64,7 +64,7 @@ import javax.inject.Inject open class MediaTttChipControllerReceiver @Inject constructor( private val commandQueue: CommandQueue, context: Context, - @MediaTttReceiverLogger logger: MediaTttLogger<ChipReceiverInfo>, + logger: MediaTttReceiverLogger, windowManager: WindowManager, mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, @@ -78,7 +78,7 @@ open class MediaTttChipControllerReceiver @Inject constructor( wakeLockBuilder: WakeLock.Builder, systemClock: SystemClock, private val rippleController: MediaTttReceiverRippleController, -) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger<ChipReceiverInfo>>( +) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttReceiverLogger>( context, logger, windowManager, @@ -172,9 +172,10 @@ open class MediaTttChipControllerReceiver @Inject constructor( } override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) { - var iconInfo = MediaTttUtils.getIconInfoFromPackageName( - context, newInfo.routeInfo.clientPackageName, logger - ) + val packageName = newInfo.routeInfo.clientPackageName + var iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, packageName) { + logger.logPackageNotFound(packageName) + } if (newInfo.appNameOverride != null) { iconInfo = iconInfo.copy( @@ -198,6 +199,7 @@ open class MediaTttChipControllerReceiver @Inject constructor( val iconView = currentView.getAppIconView() iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding) + iconView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView) } @@ -207,8 +209,6 @@ open class MediaTttChipControllerReceiver @Inject constructor( val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple) animateViewTranslationAndFade(appIconView, -1 * getTranslationAmount(), 1f) animateViewTranslationAndFade(iconRippleView, -1 * getTranslationAmount(), 1f) - // Using withEndAction{} doesn't apply a11y focus when screen is unlocked. - appIconView.postOnAnimation { view.requestAccessibilityFocus() } rippleController.expandToInProgressState(rippleView, iconRippleView) } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java index 1570d434bc62..67e464c344c5 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.log.dagger; +package com.android.systemui.media.taptotransfer.receiver; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -26,8 +26,7 @@ import java.lang.annotation.Retention; import javax.inject.Qualifier; /** - * A {@link LogBuffer} for - * {@link com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger}. + * A {@link LogBuffer} for receiver logs. */ @Qualifier @Documented diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt index 54fc48ddba91..b0c6257df96c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt @@ -13,14 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.systemui.media.taptotransfer.receiver -import java.lang.annotation.Documented -import java.lang.annotation.Retention -import java.lang.annotation.RetentionPolicy -import javax.inject.Qualifier +import android.app.StatusBarManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.temporarydisplay.TemporaryViewLogger +import javax.inject.Inject + +/** A logger for all events related to the media tap-to-transfer receiver experience. */ +@SysUISingleton +class MediaTttReceiverLogger +@Inject +constructor( + @MediaTttReceiverLogBuffer buffer: LogBuffer, +) : TemporaryViewLogger<ChipReceiverInfo>(buffer, TAG) { + + /** Logs a change in the chip state for the given [mediaRouteId]. */ + fun logStateChange( + stateName: String, + mediaRouteId: String, + packageName: String?, + ) { + MediaTttLoggerUtils.logStateChange(buffer, TAG, stateName, mediaRouteId, packageName) + } + + /** Logs an error in trying to update to [displayState]. */ + fun logStateChangeError(@StatusBarManager.MediaTransferReceiverState displayState: Int) { + MediaTttLoggerUtils.logStateChangeError(buffer, TAG, displayState) + } + + /** Logs that we couldn't find information for [packageName]. */ + fun logPackageNotFound(packageName: String) { + MediaTttLoggerUtils.logPackageNotFound(buffer, TAG, packageName) + } -@Qualifier -@Documented -@Retention(RetentionPolicy.RUNTIME) -annotation class MediaTttReceiverLogger + companion object { + private const val TAG = "MediaTttReceiver" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt index f8785fcf5de0..f1acae8e2685 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt @@ -79,9 +79,9 @@ class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleVi animator.addUpdateListener { updateListener -> val now = updateListener.currentPlayTime val progress = updateListener.animatedValue as Float - rippleShader.progress = startingPercentage + (progress * (1 - startingPercentage)) - rippleShader.distortionStrength = 1 - rippleShader.progress - rippleShader.pixelDensity = 1 - rippleShader.progress + rippleShader.rawProgress = startingPercentage + (progress * (1 - startingPercentage)) + rippleShader.distortionStrength = 1 - rippleShader.rawProgress + rippleShader.pixelDensity = 1 - rippleShader.rawProgress rippleShader.time = now.toFloat() invalidate() } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index 902a10a0cea9..89ca5d33645c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -23,11 +23,12 @@ import android.view.View import com.android.internal.logging.UiEventLogger import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.CoreStartable +import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager import com.android.systemui.media.taptotransfer.MediaTttFlags -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.statusbar.CommandQueue import com.android.systemui.temporarydisplay.TemporaryViewDisplayController @@ -35,6 +36,7 @@ import com.android.systemui.temporarydisplay.ViewPriority import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo +import java.io.PrintWriter import javax.inject.Inject /** @@ -48,14 +50,13 @@ constructor( private val chipbarCoordinator: ChipbarCoordinator, private val commandQueue: CommandQueue, private val context: Context, - @MediaTttSenderLogger private val logger: MediaTttLogger<ChipbarInfo>, + private val dumpManager: DumpManager, + private val logger: MediaTttSenderLogger, private val mediaTttFlags: MediaTttFlags, private val uiEventLogger: MediaTttSenderUiEventLogger, -) : CoreStartable { +) : CoreStartable, Dumpable { - private var displayedState: ChipStateSender? = null // A map to store current chip state per id. - // TODO(b/265455911): Log whenever we add or remove from the store. private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf() private val commandQueueCallbacks = @@ -76,6 +77,7 @@ constructor( override fun start() { if (mediaTttFlags.isMediaTttEnabled()) { commandQueue.addCallback(commandQueueCallbacks) + dumpManager.registerNormalDumpable(this) } } @@ -93,11 +95,11 @@ constructor( return } - val currentState = stateMap[routeInfo.id] - if (!ChipStateSender.isValidStateTransition(currentState, chipState)) { + val currentStateForId: ChipStateSender? = stateMap[routeInfo.id] + if (!ChipStateSender.isValidStateTransition(currentStateForId, chipState)) { // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state. logger.logInvalidStateTransitionError( - currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name, + currentState = currentStateForId?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name, chipState.name ) return @@ -105,30 +107,29 @@ constructor( uiEventLogger.logSenderStateChange(chipState) if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { - // No need to store the state since it is the default state - removeIdFromStore(routeInfo.id) - // Return early if we're not displaying a chip anyway - val currentDisplayedState = displayedState ?: return + // Return early if we're not displaying a chip for this ID anyway + if (currentStateForId == null) return val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name if ( - currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS || - currentDisplayedState.transferStatus == TransferStatus.SUCCEEDED + currentStateForId.transferStatus == TransferStatus.IN_PROGRESS || + currentStateForId.transferStatus == TransferStatus.SUCCEEDED ) { // Don't remove the chip if we're in progress or succeeded, since the user should // still be able to see the status of the transfer. logger.logRemovalBypass( removalReason, - bypassReason = "transferStatus=${currentDisplayedState.transferStatus.name}" + bypassReason = "transferStatus=${currentStateForId.transferStatus.name}" ) return } - displayedState = null + // No need to store the state since it is the default state + removeIdFromStore(routeInfo.id, reason = removalReason) chipbarCoordinator.removeView(routeInfo.id, removalReason) } else { stateMap[routeInfo.id] = chipState - displayedState = chipState + logger.logStateMap(stateMap) chipbarCoordinator.registerListener(displayListener) chipbarCoordinator.displayView( createChipbarInfo( @@ -150,7 +151,7 @@ constructor( routeInfo: MediaRoute2Info, undoCallback: IUndoMediaTransferCallback?, context: Context, - logger: MediaTttLogger<ChipbarInfo>, + logger: MediaTttSenderLogger, ): ChipbarInfo { val packageName = routeInfo.clientPackageName val otherDeviceName = @@ -159,12 +160,14 @@ constructor( } else { routeInfo.name.toString() } + val icon = + MediaTttUtils.getIconInfoFromPackageName(context, packageName) { + logger.logPackageNotFound(packageName) + } return ChipbarInfo( // Display the app's icon as the start icon - startIcon = - MediaTttUtils.getIconInfoFromPackageName(context, packageName, logger) - .toTintedIcon(), + startIcon = icon.toTintedIcon(), text = chipStateSender.getChipTextString(context, otherDeviceName), endItem = when (chipStateSender.endItem) { @@ -231,12 +234,19 @@ constructor( } private val displayListener = - TemporaryViewDisplayController.Listener { id -> removeIdFromStore(id) } + TemporaryViewDisplayController.Listener { id, reason -> removeIdFromStore(id, reason) } - private fun removeIdFromStore(id: String) { + private fun removeIdFromStore(id: String, reason: String) { + logger.logStateMapRemoval(id, reason) stateMap.remove(id) + logger.logStateMap(stateMap) if (stateMap.isEmpty()) { chipbarCoordinator.unregisterListener(displayListener) } } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("Current sender states:") + pw.println(stateMap.toString()) + } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java index bf216c6991d2..a262e97864f3 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.log.dagger; +package com.android.systemui.media.taptotransfer.sender; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -26,8 +26,7 @@ import java.lang.annotation.Retention; import javax.inject.Qualifier; /** - * A {@link LogBuffer} for - * {@link com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger}. + * A {@link LogBuffer} for sender logs. */ @Qualifier @Documented diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt index 4393af9a99ec..964a95b9be9a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt @@ -13,14 +13,102 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.systemui.media.taptotransfer.sender -import java.lang.annotation.Documented -import java.lang.annotation.Retention -import java.lang.annotation.RetentionPolicy -import javax.inject.Qualifier +import android.app.StatusBarManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import javax.inject.Inject + +/** A logger for all events related to the media tap-to-transfer sender experience. */ +@SysUISingleton +class MediaTttSenderLogger +@Inject +constructor( + @MediaTttSenderLogBuffer private val buffer: LogBuffer, +) { + /** Logs a change in the chip state for the given [mediaRouteId]. */ + fun logStateChange( + stateName: String, + mediaRouteId: String, + packageName: String?, + ) { + MediaTttLoggerUtils.logStateChange(buffer, TAG, stateName, mediaRouteId, packageName) + } + + /** Logs an error in trying to update to [displayState]. */ + fun logStateChangeError(@StatusBarManager.MediaTransferSenderState displayState: Int) { + MediaTttLoggerUtils.logStateChangeError(buffer, TAG, displayState) + } + + /** Logs that we couldn't find information for [packageName]. */ + fun logPackageNotFound(packageName: String) { + MediaTttLoggerUtils.logPackageNotFound(buffer, TAG, packageName) + } + + /** + * Logs an invalid sender state transition error in trying to update to [desiredState]. + * + * @param currentState the previous state of the chip. + * @param desiredState the new state of the chip. + */ + fun logInvalidStateTransitionError(currentState: String, desiredState: String) { + buffer.log( + TAG, + LogLevel.ERROR, + { + str1 = currentState + str2 = desiredState + }, + { "Cannot display state=$str2 after state=$str1; invalid transition" } + ) + } + + /** + * Logs that a removal request has been bypassed (ignored). + * + * @param removalReason the reason that the chip removal was requested. + * @param bypassReason the reason that the request was bypassed. + */ + fun logRemovalBypass(removalReason: String, bypassReason: String) { + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = removalReason + str2 = bypassReason + }, + { "Chip removal requested due to $str1; however, removal was ignored because $str2" } + ) + } + + /** Logs the current contents of the state map. */ + fun logStateMap(map: Map<String, ChipStateSender>) { + buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = map.toString() }, + { "Current sender states: $str1" } + ) + } + + /** Logs that [id] has been removed from the state map due to [reason]. */ + fun logStateMapRemoval(id: String, reason: String) { + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = id + str2 = reason + }, + { "State removal: id=$str1 reason=$str2" } + ) + } -@Qualifier -@Documented -@Retention(RetentionPolicy.RUNTIME) -annotation class MediaTttSenderLogger + companion object { + private const val TAG = "MediaTttSender" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 1d863435fa6e..e665d832a568 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -24,16 +24,18 @@ import com.android.launcher3.icons.IconFactory import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.media.MediaProjectionAppSelectorActivity import com.android.systemui.media.MediaProjectionAppSelectorActivity.Companion.EXTRA_HOST_APP_USER_HANDLE +import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader import com.android.systemui.mediaprojection.appselector.data.AppIconLoader import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader +import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider -import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.system.ActivityManagerWrapper +import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule +import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile import com.android.systemui.statusbar.phone.ConfigurationControllerImpl import com.android.systemui.statusbar.policy.ConfigurationController import dagger.Binds @@ -43,7 +45,6 @@ import dagger.Provides import dagger.Subcomponent import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap -import java.lang.IllegalArgumentException import javax.inject.Qualifier import javax.inject.Scope import kotlinx.coroutines.CoroutineScope @@ -53,13 +54,12 @@ import kotlinx.coroutines.SupervisorJob @Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle -@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile - -@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile - @Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope -@Module(subcomponents = [MediaProjectionAppSelectorComponent::class]) +@Module( + subcomponents = [MediaProjectionAppSelectorComponent::class], + includes = [MediaProjectionDevicePolicyModule::class] +) interface MediaProjectionModule { @Binds @IntoMap @@ -69,9 +69,10 @@ interface MediaProjectionModule { ): Activity } -/** Scoped values for [MediaProjectionAppSelectorComponent]. - * We create a scope for the activity so certain dependencies like [TaskPreviewSizeProvider] - * could be reused. */ +/** + * Scoped values for [MediaProjectionAppSelectorComponent]. We create a scope for the activity so + * certain dependencies like [TaskPreviewSizeProvider] could be reused. + */ @Module interface MediaProjectionAppSelectorModule { @@ -83,6 +84,10 @@ interface MediaProjectionAppSelectorModule { @Binds @MediaProjectionAppSelectorScope + fun bindRecentTaskLabelLoader(impl: ActivityTaskManagerLabelLoader): RecentTaskLabelLoader + + @Binds + @MediaProjectionAppSelectorScope fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider @Binds @@ -104,20 +109,6 @@ interface MediaProjectionAppSelectorModule { ): ConfigurationController = ConfigurationControllerImpl(activity) @Provides - @PersonalProfile - @MediaProjectionAppSelectorScope - fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle { - // Current foreground user is the 'personal' profile - return UserHandle.of(activityManagerWrapper.currentUserId) - } - - @Provides - @WorkProfile - @MediaProjectionAppSelectorScope - fun workProfileUserHandle(userTracker: UserTracker): UserHandle? = - userTracker.userProfiles.find { it.isManagedProfile }?.userHandle - - @Provides @HostUserHandle @MediaProjectionAppSelectorScope fun hostUserHandle(activity: MediaProjectionAppSelectorActivity): UserHandle { @@ -125,8 +116,10 @@ interface MediaProjectionAppSelectorModule { activity.intent.extras ?: error("MediaProjectionAppSelectorActivity should be launched with extras") return extras.getParcelable(EXTRA_HOST_APP_USER_HANDLE) - ?: error("MediaProjectionAppSelectorActivity should be provided with " + - "$EXTRA_HOST_APP_USER_HANDLE extra") + ?: error( + "MediaProjectionAppSelectorActivity should be provided with " + + "$EXTRA_HOST_APP_USER_HANDLE extra" + ) } @Provides fun bindIconFactory(context: Context): IconFactory = IconFactory.obtain(context) @@ -146,9 +139,7 @@ interface MediaProjectionAppSelectorComponent { /** Generates [MediaProjectionAppSelectorComponent]. */ @Subcomponent.Factory interface Factory { - /** - * Create a factory to inject the activity into the graph - */ + /** Create a factory to inject the activity into the graph */ fun create( @BindsInstance activity: MediaProjectionAppSelectorActivity, @BindsInstance view: MediaProjectionAppSelectorView, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt new file mode 100644 index 000000000000..829b0ddbe3a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.mediaprojection.appselector + +import android.content.Context +import android.os.UserHandle +import com.android.internal.R as AndroidR +import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState +import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider +import com.android.internal.app.ResolverListAdapter +import com.android.systemui.R +import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import javax.inject.Inject + +@MediaProjectionAppSelectorScope +class MediaProjectionBlockerEmptyStateProvider +@Inject +constructor( + @HostUserHandle private val hostAppHandle: UserHandle, + @PersonalProfile private val personalProfileHandle: UserHandle, + private val policyResolver: ScreenCaptureDevicePolicyResolver, + private val context: Context +) : EmptyStateProvider { + + override fun getEmptyState(resolverListAdapter: ResolverListAdapter): EmptyState? { + val screenCaptureAllowed = + policyResolver.isScreenCaptureAllowed( + targetAppUserHandle = resolverListAdapter.userHandle, + hostAppUserHandle = hostAppHandle + ) + + val isHostAppInPersonalProfile = hostAppHandle == personalProfileHandle + + val subtitle = + if (isHostAppInPersonalProfile) { + AndroidR.string.resolver_cant_share_with_personal_apps_explanation + } else { + AndroidR.string.resolver_cant_share_with_work_apps_explanation + } + + if (!screenCaptureAllowed) { + return object : EmptyState { + override fun getSubtitle(): String = context.resources.getString(subtitle) + override fun getTitle(): String = + context.resources.getString( + R.string.screen_capturing_disabled_by_policy_dialog_title + ) + override fun onEmptyStateShown() { + // TODO(b/237397740) report analytics + } + override fun shouldSkipDataRebuild(): Boolean = true + } + } + return null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt new file mode 100644 index 000000000000..eadcb93a7f94 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.appselector.data + +import android.annotation.UserIdInt +import android.content.ComponentName +import android.content.pm.PackageManager +import android.os.UserHandle +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +interface RecentTaskLabelLoader { + suspend fun loadLabel(userId: Int, componentName: ComponentName): CharSequence? +} + +class ActivityTaskManagerLabelLoader +@Inject +constructor( + @Background private val coroutineDispatcher: CoroutineDispatcher, + private val packageManager: PackageManager +) : RecentTaskLabelLoader { + + override suspend fun loadLabel( + @UserIdInt userId: Int, + componentName: ComponentName + ): CharSequence? = + withContext(coroutineDispatcher) { + val userHandle = UserHandle(userId) + val appInfo = + packageManager.getApplicationInfo( + componentName.packageName, + PackageManager.ApplicationInfoFlags.of(0 /* no flags */) + ) + val label = packageManager.getApplicationLabel(appInfo) + return@withContext packageManager.getUserBadgedLabel(label, userHandle) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt index 15cfeee5174e..64f97f2faacc 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt @@ -20,11 +20,12 @@ import android.graphics.Rect import android.view.View import android.view.ViewGroup import android.widget.ImageView -import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.android.systemui.R import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector import com.android.systemui.mediaprojection.appselector.data.AppIconLoader import com.android.systemui.mediaprojection.appselector.data.RecentTask +import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import dagger.assisted.Assisted @@ -40,9 +41,10 @@ constructor( @Assisted private val root: ViewGroup, private val iconLoader: AppIconLoader, private val thumbnailLoader: RecentTaskThumbnailLoader, + private val labelLoader: RecentTaskLabelLoader, private val taskViewSizeProvider: TaskPreviewSizeProvider, @MediaProjectionAppSelector private val scope: CoroutineScope -) : RecyclerView.ViewHolder(root), ConfigurationListener, TaskPreviewSizeProvider.TaskPreviewSizeListener { +) : ViewHolder(root), ConfigurationListener, TaskPreviewSizeProvider.TaskPreviewSizeListener { val thumbnailView: MediaProjectionTaskView = root.requireViewById(R.id.task_thumbnail) private val iconView: ImageView = root.requireViewById(R.id.task_icon) @@ -64,6 +66,10 @@ constructor( val icon = iconLoader.loadIcon(task.userId, component) iconView.setImageDrawable(icon) } + launch { + val label = labelLoader.loadLabel(task.userId, component) + root.contentDescription = label + } } launch { val thumbnail = thumbnailLoader.loadThumbnail(task.taskId) @@ -88,10 +94,10 @@ constructor( private fun updateThumbnailSize() { thumbnailView.layoutParams = - thumbnailView.layoutParams.apply { - width = taskViewSizeProvider.size.width() - height = taskViewSizeProvider.size.height() - } + thumbnailView.layoutParams.apply { + width = taskViewSizeProvider.size.width() + height = taskViewSizeProvider.size.height() + } } @AssistedFactory diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt new file mode 100644 index 000000000000..13b71a727dd9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.mediaprojection.devicepolicy + +import android.os.UserHandle +import com.android.systemui.settings.UserTracker +import com.android.systemui.shared.system.ActivityManagerWrapper +import dagger.Module +import dagger.Provides +import javax.inject.Qualifier + +@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile + +@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile + +/** Module for media projection device policy related dependencies */ +@Module +class MediaProjectionDevicePolicyModule { + @Provides + @PersonalProfile + fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle { + // Current foreground user is the 'personal' profile + return UserHandle.of(activityManagerWrapper.currentUserId) + } + + @Provides + @WorkProfile + fun workProfileUserHandle(userTracker: UserTracker): UserHandle? = + userTracker.userProfiles.find { it.isManagedProfile }?.userHandle +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt new file mode 100644 index 000000000000..6bd33e7e5c97 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.mediaprojection.devicepolicy + +import android.app.admin.DevicePolicyManager +import android.os.UserHandle +import android.os.UserManager +import javax.inject.Inject + +/** + * Utility class to resolve if screen capture allowed for a particular target app/host app pair. It + * caches the state of the policies, so you need to create a new instance of this class if you want + * to react to updated policies state. + */ +class ScreenCaptureDevicePolicyResolver +@Inject +constructor( + private val devicePolicyManager: DevicePolicyManager, + private val userManager: UserManager, + @PersonalProfile private val personalProfileUserHandle: UserHandle, + @WorkProfile private val workProfileUserHandle: UserHandle? +) { + + /** + * Returns true if [hostAppUserHandle] is allowed to perform screen capture of + * [targetAppUserHandle] + */ + fun isScreenCaptureAllowed( + targetAppUserHandle: UserHandle, + hostAppUserHandle: UserHandle, + ): Boolean { + if (hostAppUserHandle.isWorkProfile() && workProfileScreenCaptureDisabled) { + // Disable screen capturing as host apps should not capture the screen + return false + } + + if (!hostAppUserHandle.isWorkProfile() && personalProfileScreenCaptureDisabled) { + // Disable screen capturing as personal apps should not capture the screen + return false + } + + if (targetAppUserHandle.isWorkProfile()) { + // Work profile target + if (workProfileScreenCaptureDisabled) { + // Do not allow sharing work profile apps as work profile capturing is disabled + return false + } + } else { + // Personal profile target + if (hostAppUserHandle.isWorkProfile() && disallowSharingIntoManagedProfile) { + // Do not allow sharing of personal apps into work profile apps + return false + } + + if (personalProfileScreenCaptureDisabled) { + // Disable screen capturing as personal apps should not be captured + return false + } + } + + return true + } + + /** + * Returns true if [hostAppUserHandle] is NOT allowed to capture an app from any profile, + * could be useful to finish the screen capture flow as soon as possible when the screen + * could not be captured at all. + */ + fun isScreenCaptureCompletelyDisabled(hostAppUserHandle: UserHandle): Boolean { + val isWorkAppsCaptureDisabled = + if (workProfileUserHandle != null) { + !isScreenCaptureAllowed( + targetAppUserHandle = workProfileUserHandle, + hostAppUserHandle = hostAppUserHandle + ) + } else true + + val isPersonalAppsCaptureDisabled = + !isScreenCaptureAllowed( + targetAppUserHandle = personalProfileUserHandle, + hostAppUserHandle = hostAppUserHandle + ) + + return isWorkAppsCaptureDisabled && isPersonalAppsCaptureDisabled + } + + private val personalProfileScreenCaptureDisabled: Boolean by lazy { + devicePolicyManager.getScreenCaptureDisabled( + /* admin */ null, + personalProfileUserHandle.identifier + ) + } + + private val workProfileScreenCaptureDisabled: Boolean by lazy { + workProfileUserHandle?.let { + devicePolicyManager.getScreenCaptureDisabled(/* admin */ null, it.identifier) + } + ?: false + } + + private val disallowSharingIntoManagedProfile: Boolean by lazy { + workProfileUserHandle?.let { + userManager.hasUserRestrictionForUser( + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, + it + ) + } + ?: false + } + + private fun UserHandle?.isWorkProfile(): Boolean = this == workProfileUserHandle +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt new file mode 100644 index 000000000000..a6b3da04ad80 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.mediaprojection.devicepolicy + +import android.content.Context +import com.android.systemui.R +import com.android.systemui.statusbar.phone.SystemUIDialog + +/** Dialog that shows that screen capture is disabled on this device. */ +class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) { + + init { + setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title)) + setMessage( + context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description) + ) + setIcon(R.drawable.ic_cast) + setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java index 3ecf15471c31..8d809908d78b 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java @@ -16,13 +16,12 @@ package com.android.systemui.model; -import static android.view.Display.DEFAULT_DISPLAY; - import android.annotation.NonNull; import android.util.Log; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shared.system.QuickStepContract; import java.io.PrintWriter; @@ -39,11 +38,16 @@ public class SysUiState implements Dumpable { private static final String TAG = SysUiState.class.getSimpleName(); public static final boolean DEBUG = false; + private final DisplayTracker mDisplayTracker; private @QuickStepContract.SystemUiStateFlags int mFlags; private final List<SysUiStateCallback> mCallbacks = new ArrayList<>(); private int mFlagsToSet = 0; private int mFlagsToClear = 0; + public SysUiState(DisplayTracker displayTracker) { + mDisplayTracker = displayTracker; + } + /** * Add listener to be notified of changes made to SysUI state. * The callback will also be called as part of this function. @@ -81,7 +85,7 @@ public class SysUiState implements Dumpable { } private void updateFlags(int displayId) { - if (displayId != DEFAULT_DISPLAY) { + if (displayId != mDisplayTracker.getDefaultDisplayId()) { // Ignore non-default displays for now Log.w(TAG, "Ignoring flag update for display: " + displayId, new Throwable()); return; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 5993e2eaadad..97c290d3e1f0 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -25,7 +25,6 @@ import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; @@ -130,6 +129,7 @@ import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; @@ -218,6 +218,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener; private final UserContextProvider mUserContextProvider; private final WakefulnessLifecycle mWakefulnessLifecycle; + private final DisplayTracker mDisplayTracker; private final RegionSamplingHelper mRegionSamplingHelper; private final int mNavColorSampleMargin; private NavigationBarFrame mFrame; @@ -461,7 +462,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements @Override public void onStartedWakingUp() { notifyScreenStateChanged(true); - if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) { + if (isGesturalModeOnDefaultDisplay(getContext(), mDisplayTracker, + mNavBarMode)) { mRegionSamplingHelper.start(mSamplingBounds); } } @@ -545,7 +547,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements Optional<BackAnimation> backAnimation, UserContextProvider userContextProvider, WakefulnessLifecycle wakefulnessLifecycle, - TaskStackChangeListeners taskStackChangeListeners) { + TaskStackChangeListeners taskStackChangeListeners, + DisplayTracker displayTracker) { super(navigationBarView); mFrame = navigationBarFrame; mContext = context; @@ -585,6 +588,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mUserContextProvider = userContextProvider; mWakefulnessLifecycle = wakefulnessLifecycle; mTaskStackChangeListeners = taskStackChangeListeners; + mDisplayTracker = displayTracker; mNavColorSampleMargin = getResources() .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin); @@ -632,12 +636,14 @@ public class NavigationBar extends ViewController<NavigationBarView> implements @Override public boolean isSamplingEnabled() { - return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode); + return isGesturalModeOnDefaultDisplay(getContext(), mDisplayTracker, + mNavBarMode); } }, mainExecutor, bgExecutor); mView.setBackgroundExecutor(bgExecutor); mView.setEdgeBackGestureHandler(mEdgeBackGestureHandler); + mView.setDisplayTracker(mDisplayTracker); mNavBarMode = mNavigationModeController.addListener(mModeChangedListener); } @@ -665,7 +671,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration .getRotation())); mDisplayId = mContext.getDisplayId(); - mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; + mIsOnDefaultDisplay = mDisplayId == mDisplayTracker.getDefaultDisplayId(); // Ensure we try to get currentSysuiState from navBarHelper before command queue callbacks // start firing, since the latter is source of truth @@ -1468,7 +1474,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private void onAccessibilityClick(View v) { final Display display = v.getDisplay(); mAccessibilityManager.notifyAccessibilityButtonClicked( - display != null ? display.getDisplayId() : DEFAULT_DISPLAY); + display != null ? display.getDisplayId() : mDisplayTracker.getDefaultDisplayId()); } private boolean onAccessibilityLongClick(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index dce69bb7ac1e..8c19111cab24 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -19,7 +19,6 @@ package com.android.systemui.navigationbar; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; -import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG; import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; @@ -55,6 +54,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.CommandQueue; @@ -87,6 +87,7 @@ public class NavigationBarController implements private final NavigationBarComponent.Factory mNavigationBarComponentFactory; private FeatureFlags mFeatureFlags; private final SecureSettings mSecureSettings; + private final DisplayTracker mDisplayTracker; private final DisplayManager mDisplayManager; private final TaskbarDelegate mTaskbarDelegate; private int mNavMode; @@ -119,12 +120,14 @@ public class NavigationBarController implements Optional<Pip> pipOptional, Optional<BackAnimation> backAnimation, FeatureFlags featureFlags, - SecureSettings secureSettings) { + SecureSettings secureSettings, + DisplayTracker displayTracker) { mContext = context; mHandler = mainHandler; mNavigationBarComponentFactory = navigationBarComponentFactory; mFeatureFlags = featureFlags; mSecureSettings = secureSettings; + mDisplayTracker = displayTracker; mDisplayManager = mContext.getSystemService(DisplayManager.class); commandQueue.addCallback(this); configurationController.addCallback(this); @@ -296,9 +299,10 @@ public class NavigationBarController implements // Don't need to create nav bar on the default display if we initialize TaskBar. final boolean shouldCreateDefaultNavbar = includeDefaultDisplay && !initializeTaskbarIfNecessary(); - Display[] displays = mDisplayManager.getDisplays(); + Display[] displays = mDisplayTracker.getAllDisplays(); for (Display display : displays) { - if (shouldCreateDefaultNavbar || display.getDisplayId() != DEFAULT_DISPLAY) { + if (shouldCreateDefaultNavbar + || display.getDisplayId() != mDisplayTracker.getDefaultDisplayId()) { createNavigationBar(display, null /* savedState */, result); } } @@ -317,7 +321,7 @@ public class NavigationBarController implements } final int displayId = display.getDisplayId(); - final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY; + final boolean isOnDefaultDisplay = displayId == mDisplayTracker.getDefaultDisplayId(); // We may show TaskBar on the default display for large screen device. Don't need to create // navigation bar for this case. @@ -412,7 +416,7 @@ public class NavigationBarController implements /** @return {@link NavigationBarView} on the default display. */ public @Nullable NavigationBarView getDefaultNavigationBarView() { - return getNavigationBarView(DEFAULT_DISPLAY); + return getNavigationBarView(mDisplayTracker.getDefaultDisplayId()); } /** @@ -433,7 +437,8 @@ public class NavigationBarController implements final NavigationBarView navBarView = getNavigationBarView(displayId); if (navBarView != null) { navBarView.showPinningEnterExitToast(entering); - } else if (displayId == DEFAULT_DISPLAY && mTaskbarDelegate.isInitialized()) { + } else if (displayId == mDisplayTracker.getDefaultDisplayId() + && mTaskbarDelegate.isInitialized()) { mTaskbarDelegate.showPinningEnterExitToast(entering); } } @@ -442,7 +447,8 @@ public class NavigationBarController implements final NavigationBarView navBarView = getNavigationBarView(displayId); if (navBarView != null) { navBarView.showPinningEscapeToast(); - } else if (displayId == DEFAULT_DISPLAY && mTaskbarDelegate.isInitialized()) { + } else if (displayId == mDisplayTracker.getDefaultDisplayId() + && mTaskbarDelegate.isInitialized()) { mTaskbarDelegate.showPinningEscapeToast(); } } @@ -459,7 +465,7 @@ public class NavigationBarController implements /** @return {@link NavigationBar} on the default display. */ @Nullable public NavigationBar getDefaultNavigationBar() { - return mNavigationBars.get(DEFAULT_DISPLAY); + return mNavigationBars.get(mDisplayTracker.getDefaultDisplayId()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java index 6793f0163c43..a4de9ffbfa51 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java @@ -24,7 +24,6 @@ import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.util.SparseArray; -import android.view.Display; import android.view.IWallpaperVisibilityListener; import android.view.IWindowManager; import android.view.View; @@ -32,6 +31,7 @@ import android.view.View; import com.android.systemui.R; import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; import com.android.systemui.navigationbar.buttons.ButtonDispatcher; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.LightBarTransitionsController; @@ -65,6 +65,7 @@ public final class NavigationBarTransitions extends BarTransitions implements @org.jetbrains.annotations.NotNull private final IWindowManager mWindowManagerService; private final LightBarTransitionsController mLightTransitionsController; + private final DisplayTracker mDisplayTracker; private final boolean mAllowAutoDimWallpaperNotVisible; private boolean mWallpaperVisible; @@ -89,18 +90,20 @@ public final class NavigationBarTransitions extends BarTransitions implements public NavigationBarTransitions( NavigationBarView view, IWindowManager windowManagerService, - LightBarTransitionsController.Factory lightBarTransitionsControllerFactory) { + LightBarTransitionsController.Factory lightBarTransitionsControllerFactory, + DisplayTracker displayTracker) { super(view, R.drawable.nav_background); mView = view; mWindowManagerService = windowManagerService; mLightTransitionsController = lightBarTransitionsControllerFactory.create(this); + mDisplayTracker = displayTracker; mAllowAutoDimWallpaperNotVisible = view.getContext().getResources() .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper); mDarkIntensityListeners = new ArrayList(); try { mWallpaperVisible = mWindowManagerService.registerWallpaperVisibilityListener( - mWallpaperVisibilityListener, Display.DEFAULT_DISPLAY); + mWallpaperVisibilityListener, mDisplayTracker.getDefaultDisplayId()); } catch (RemoteException e) { } mView.addOnLayoutChangeListener( @@ -126,7 +129,7 @@ public final class NavigationBarTransitions extends BarTransitions implements public void destroy() { try { mWindowManagerService.unregisterWallpaperVisibilityListener(mWallpaperVisibilityListener, - Display.DEFAULT_DISPLAY); + mDisplayTracker.getDefaultDisplayId()); } catch (RemoteException e) { } mLightTransitionsController.destroy(); @@ -135,7 +138,10 @@ public final class NavigationBarTransitions extends BarTransitions implements @Override public void setAutoDim(boolean autoDim) { // Ensure we aren't in gestural nav if we are triggering auto dim - if (autoDim && isGesturalModeOnDefaultDisplay(mView.getContext(), mNavBarMode)) return; + if (autoDim && isGesturalModeOnDefaultDisplay(mView.getContext(), mDisplayTracker, + mNavBarMode)) { + return; + } if (mAutoDim == autoDim) return; mAutoDim = autoDim; applyLightsOut(true, false); @@ -219,7 +225,7 @@ public final class NavigationBarTransitions extends BarTransitions implements @Override public int getTintAnimationDuration() { - if (isGesturalModeOnDefaultDisplay(mView.getContext(), mNavBarMode)) { + if (isGesturalModeOnDefaultDisplay(mView.getContext(), mDisplayTracker, mNavBarMode)) { return Math.max(DEFAULT_COLOR_ADAPT_TRANSITION_TIME, MIN_COLOR_ADAPT_TRANSITION_TIME); } return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 88c4fd524b79..63fb4996fbbf 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -16,13 +16,11 @@ package com.android.systemui.navigationbar; -import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode; @@ -75,13 +73,12 @@ import com.android.systemui.navigationbar.buttons.NearestTouchFrame; import com.android.systemui.navigationbar.buttons.RotationContextButton; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.recents.Recents; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shared.rotation.FloatingRotationButton; import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; import com.android.systemui.shared.rotation.RotationButtonController; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarTransitionsController; @@ -127,6 +124,7 @@ public class NavigationBarView extends FrameLayout { private int mDarkIconColor; private EdgeBackGestureHandler mEdgeBackGestureHandler; + private DisplayTracker mDisplayTracker; private final DeadZone mDeadZone; private NavigationBarTransitions mBarTransitions; @Nullable @@ -303,7 +301,8 @@ public class NavigationBarView extends FrameLayout { R.dimen.floating_rotation_button_taskbar_left_margin, R.dimen.floating_rotation_button_taskbar_bottom_margin, R.dimen.floating_rotation_button_diameter, - R.dimen.key_button_ripple_max_width); + R.dimen.key_button_ripple_max_width, + R.bool.floating_rotation_button_position_left); mRotationButtonController = new RotationButtonController(mLightContext, mLightIconColor, mDarkIconColor, R.drawable.ic_sysbar_rotate_button_ccw_start_0, R.drawable.ic_sysbar_rotate_button_ccw_start_90, @@ -361,6 +360,10 @@ public class NavigationBarView extends FrameLayout { mBgExecutor = bgExecutor; } + public void setDisplayTracker(DisplayTracker displayTracker) { + mDisplayTracker = displayTracker; + } + public void setTouchHandler(Gefingerpoken touchHandler) { mTouchHandler = touchHandler; } @@ -558,7 +561,8 @@ public class NavigationBarView extends FrameLayout { } public void setBehavior(@Behavior int behavior) { - mRotationButtonController.onBehaviorChanged(Display.DEFAULT_DISPLAY, behavior); + mRotationButtonController.onBehaviorChanged(mDisplayTracker.getDefaultDisplayId(), + behavior); } @Override @@ -678,7 +682,7 @@ public class NavigationBarView extends FrameLayout { @VisibleForTesting boolean isRecentsButtonDisabled() { return mUseCarModeUi || !isOverviewEnabled() - || getContext().getDisplayId() != Display.DEFAULT_DISPLAY; + || getContext().getDisplayId() != mDisplayTracker.getDefaultDisplayId(); } private Display getContextDisplay() { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java index 1230708d780a..590efbb66454 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java @@ -16,8 +16,6 @@ package com.android.systemui.navigationbar.gestural; -import static android.view.Display.DEFAULT_DISPLAY; - import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE; import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG; @@ -56,6 +54,7 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.plugins.NavigationEdgeBackPlugin; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.statusbar.VibratorHelper; @@ -289,7 +288,8 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl Context context, LatencyTracker latencyTracker, VibratorHelper vibratorHelper, - @Background Executor backgroundExecutor) { + @Background Executor backgroundExecutor, + DisplayTracker displayTracker) { super(context); mWindowManager = context.getSystemService(WindowManager.class); @@ -365,7 +365,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl setVisibility(GONE); - boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY; + boolean isPrimaryDisplay = mContext.getDisplayId() == displayTracker.getDefaultDisplayId(); mRegionSamplingHelper = new RegionSamplingHelper(this, new RegionSamplingHelper.SamplingCallback() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 08d18575da79..6bfe1a099c51 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -17,10 +17,12 @@ package com.android.systemui.notetask import android.app.KeyguardManager +import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager import android.os.UserManager +import android.util.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity import com.android.systemui.util.kotlin.getOrNull @@ -57,7 +59,7 @@ constructor( * If the keyguard is locked, notes will open as a full screen experience. A locked device has * no contextual information which let us use the whole screen space available. * - * If no in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be + * If not in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be * collapsed if the notes bubble is already opened. * * That will let users open other apps in full screen, and take contextual notes. @@ -68,16 +70,23 @@ constructor( val bubbles = optionalBubbles.getOrNull() ?: return val keyguardManager = optionalKeyguardManager.getOrNull() ?: return val userManager = optionalUserManager.getOrNull() ?: return - val intent = intentResolver.resolveIntent() ?: return // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing. if (!userManager.isUserUnlocked) return - if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) { - context.startActivity(intent) - } else { - // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter. - bubbles.showOrHideAppBubble(intent) + val intent = intentResolver.resolveIntent() ?: return + + // TODO(b/266686199): We should handle when app not available. For now, we log. + try { + if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) { + context.startActivity(intent) + } else { + bubbles.showOrHideAppBubble(intent) + } + } catch (e: ActivityNotFoundException) { + val message = + "Activity not found for action: ${NoteTaskIntentResolver.ACTION_CREATE_NOTE}." + Log.e(TAG, message, e) } } @@ -106,6 +115,8 @@ constructor( } companion object { + private val TAG = NoteTaskController::class.simpleName.orEmpty() + // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead. const val NOTE_TASK_KEY_EVENT = 311 } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt index 4b10d69a2b80..11dc1d7eb804 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt @@ -16,70 +16,39 @@ package com.android.systemui.notetask -import android.content.ComponentName +import android.app.role.RoleManager +import android.content.Context import android.content.Intent -import android.content.pm.ActivityInfo -import android.content.pm.PackageManager -import android.content.pm.PackageManager.ResolveInfoFlags -import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE import javax.inject.Inject -/** - * Class responsible to query all apps and find one that can handle the [ACTION_CREATE_NOTE]. If - * found, an [Intent] ready for be launched will be returned. Otherwise, returns null. - * - * TODO(b/248274123): should be revisited once the notes role is implemented. - */ internal class NoteTaskIntentResolver @Inject constructor( - private val packageManager: PackageManager, + private val context: Context, + private val roleManager: RoleManager, ) { fun resolveIntent(): Intent? { - val intent = Intent(ACTION_CREATE_NOTE) - val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()) - val infoList = packageManager.queryIntentActivities(intent, flags) - - for (info in infoList) { - val packageName = info.activityInfo.applicationInfo.packageName ?: continue - val activityName = resolveActivityNameForNotesAction(packageName) ?: continue - - return Intent(ACTION_CREATE_NOTE) - .setPackage(packageName) - .setComponent(ComponentName(packageName, activityName)) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - - return null - } - - private fun resolveActivityNameForNotesAction(packageName: String): String? { - val intent = Intent(ACTION_CREATE_NOTE).setPackage(packageName) - val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()) - val resolveInfo = packageManager.resolveActivity(intent, flags) + val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, context.user).firstOrNull() - val activityInfo = resolveInfo?.activityInfo ?: return null - if (activityInfo.name.isNullOrBlank()) return null - if (!activityInfo.exported) return null - if (!activityInfo.enabled) return null - if (!activityInfo.showWhenLocked) return null - if (!activityInfo.turnScreenOn) return null + if (packageName.isNullOrEmpty()) return null - return activityInfo.name + return Intent(ACTION_CREATE_NOTE) + .setPackage(packageName) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint was + // used to start it. + .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true) } companion object { - // TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead. + // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead. const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE" // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead. - const val NOTE_ROLE = "android.app.role.NOTES" + const val ROLE_NOTES = "android.app.role.NOTES" + + // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead. + const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE" } } - -private val ActivityInfo.showWhenLocked: Boolean - get() = flags and ActivityInfo.FLAG_SHOW_WHEN_LOCKED != 0 - -private val ActivityInfo.turnScreenOn: Boolean - get() = flags and ActivityInfo.FLAG_TURN_SCREEN_ON != 0 diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt index 22ce12132124..ec6a16accc4d 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt @@ -51,7 +51,7 @@ internal interface NoteTaskModule { featureFlags: FeatureFlags, roleManager: RoleManager, ): Boolean { - val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.NOTE_ROLE) + val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.ROLE_NOTES) val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS) return isRoleAvailable && isFeatureEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt index 2a6ca1acb38e..8ad2f867a073 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt @@ -17,11 +17,11 @@ package com.android.systemui.privacy import android.content.Context import android.util.AttributeSet import android.view.ViewGroup -import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import com.android.settingslib.Utils import com.android.systemui.R +import com.android.systemui.animation.LaunchableFrameLayout import com.android.systemui.statusbar.events.BackgroundAnimatableView class OngoingPrivacyChip @JvmOverloads constructor( @@ -29,7 +29,7 @@ class OngoingPrivacyChip @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttrs: Int = 0, defStyleRes: Int = 0 -) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView { +) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView { private var iconMargin = 0 private var iconSize = 0 diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt index b48ea23dbf27..be93550158c6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt @@ -43,6 +43,7 @@ import java.util.concurrent.Executor import javax.inject.Inject private const val TAG = "AutoAddTracker" +private const val DELIMITER = "," /** * Class to track tiles that have been auto-added @@ -67,7 +68,7 @@ class AutoAddTracker @VisibleForTesting constructor( @GuardedBy("autoAdded") private val autoAdded = ArraySet<String>() - private var restoredTiles: Set<String>? = null + private var restoredTiles: Map<String, AutoTile>? = null override val currentUserId: Int get() = userId @@ -98,25 +99,26 @@ class AutoAddTracker @VisibleForTesting constructor( when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) { Settings.Secure.QS_TILES -> { restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) - ?.split(",") - ?.toSet() + ?.split(DELIMITER) + ?.mapIndexed(::AutoTile) + ?.associateBy(AutoTile::tileType) ?: run { Log.w(TAG, "Null restored tiles for user $userId") - emptySet() + emptyMap() } } Settings.Secure.QS_AUTO_ADDED_TILES -> { - restoredTiles?.let { tiles -> + restoredTiles?.let { restoredTiles -> val restoredAutoAdded = intent .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) - ?.split(",") + ?.split(DELIMITER) ?: emptyList() val autoAddedBeforeRestore = intent .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE) - ?.split(",") + ?.split(DELIMITER) ?: emptyList() - val tilesToRemove = restoredAutoAdded.filter { it !in tiles } + val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles } if (tilesToRemove.isNotEmpty()) { qsHost.removeTiles(tilesToRemove) } @@ -180,6 +182,9 @@ class AutoAddTracker @VisibleForTesting constructor( registerBroadcastReceiver() } + fun getRestoredTilePosition(tile: String): Int = + restoredTiles?.get(tile)?.index ?: QSTileHost.POSITION_AT_END + /** * Returns `true` if the tile has been auto-added before */ @@ -196,12 +201,12 @@ class AutoAddTracker @VisibleForTesting constructor( */ fun setTileAdded(tile: String) { val tiles = synchronized(autoAdded) { - if (autoAdded.add(tile)) { - getTilesFromListLocked() - } else { - null - } + if (autoAdded.add(tile)) { + getTilesFromListLocked() + } else { + null } + } tiles?.let { saveTiles(it) } } @@ -222,7 +227,7 @@ class AutoAddTracker @VisibleForTesting constructor( } private fun getTilesFromListLocked(): String { - return TextUtils.join(",", autoAdded) + return TextUtils.join(DELIMITER, autoAdded) } private fun saveTiles(tiles: String) { @@ -245,7 +250,7 @@ class AutoAddTracker @VisibleForTesting constructor( private fun getAdded(): Collection<String> { val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId) - return current?.split(",") ?: emptySet() + return current?.split(DELIMITER) ?: emptySet() } override fun dump(pw: PrintWriter, args: Array<out String>) { @@ -281,4 +286,6 @@ class AutoAddTracker @VisibleForTesting constructor( ) } } + + private data class AutoTile(val index: Int, val tileType: String) }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 100853caa2d7..98af9dfe7f37 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -314,7 +314,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P if (!TILES_SETTING.equals(key)) { return; } - Log.d(TAG, "Recreating tiles"); if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) { newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); } @@ -327,6 +326,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P } } if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; + Log.d(TAG, "Recreating tiles: " + tileSpecs); mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach( tile -> { Log.d(TAG, "Destroying tile: " + tile.getKey()); @@ -372,6 +372,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P Log.d(TAG, "Destroying not available tile: " + tileSpec); mQSLogger.logTileDestroyed(tileSpec, "Tile not available"); } + } else { + Log.d(TAG, "No factory for a spec: " + tileSpec); } } catch (Throwable t) { Log.w(TAG, "Error creating tile for spec: " + tileSpec, t); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index cfda9fd6cb96..7c2536dac56e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -15,7 +15,6 @@ */ package com.android.systemui.qs.external; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; import android.app.PendingIntent; @@ -63,6 +62,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.settings.DisplayTracker; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -90,6 +90,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private final TileServiceManager mServiceManager; private final int mUser; private final CustomTileStatePersister mCustomTileStatePersister; + private final DisplayTracker mDisplayTracker; @Nullable private android.graphics.drawable.Icon mDefaultIcon; @Nullable @@ -120,7 +121,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener String action, Context userContext, CustomTileStatePersister customTileStatePersister, - TileServices tileServices + TileServices tileServices, + DisplayTracker displayTracker ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); @@ -135,6 +137,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mServiceManager = tileServices.getTileWrapper(this); mService = mServiceManager.getTileService(); mCustomTileStatePersister = customTileStatePersister; + mDisplayTracker = displayTracker; } @Override @@ -310,7 +313,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mIsShowingDialog = false; try { if (DEBUG) Log.d(TAG, "Removing token"); - mWindowManager.removeWindowToken(mToken, DEFAULT_DISPLAY); + mWindowManager.removeWindowToken(mToken, mDisplayTracker.getDefaultDisplayId()); } catch (RemoteException e) { } } @@ -335,7 +338,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener if (mIsTokenGranted && !mIsShowingDialog) { try { if (DEBUG) Log.d(TAG, "Removing token"); - mWindowManager.removeWindowToken(mToken, DEFAULT_DISPLAY); + mWindowManager.removeWindowToken(mToken, + mDisplayTracker.getDefaultDisplayId()); } catch (RemoteException e) { } mIsTokenGranted = false; @@ -354,7 +358,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener if (mIsTokenGranted) { try { if (DEBUG) Log.d(TAG, "Removing token"); - mWindowManager.removeWindowToken(mToken, DEFAULT_DISPLAY); + mWindowManager.removeWindowToken(mToken, mDisplayTracker.getDefaultDisplayId()); } catch (RemoteException e) { } } @@ -398,8 +402,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mViewClicked = view; try { if (DEBUG) Log.d(TAG, "Adding token"); - mWindowManager.addWindowToken(mToken, TYPE_QS_DIALOG, DEFAULT_DISPLAY, - null /* options */); + mWindowManager.addWindowToken(mToken, TYPE_QS_DIALOG, + mDisplayTracker.getDefaultDisplayId(), null /* options */); mIsTokenGranted = true; } catch (RemoteException e) { } @@ -566,6 +570,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener final QSLogger mQSLogger; final CustomTileStatePersister mCustomTileStatePersister; private TileServices mTileServices; + final DisplayTracker mDisplayTracker; Context mUserContext; String mSpec = ""; @@ -581,7 +586,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener ActivityStarter activityStarter, QSLogger qsLogger, CustomTileStatePersister customTileStatePersister, - TileServices tileServices + TileServices tileServices, + DisplayTracker displayTracker ) { mQSHostLazy = hostLazy; mBackgroundLooper = backgroundLooper; @@ -593,6 +599,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mQSLogger = qsLogger; mCustomTileStatePersister = customTileStatePersister; mTileServices = tileServices; + mDisplayTracker = displayTracker; } Builder setSpec(@NonNull String spec) { @@ -623,7 +630,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener action, mUserContext, mCustomTileStatePersister, - mTileServices + mTileServices, + mDisplayTracker ); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 1151475f0fc3..a979e5a99fa8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -17,7 +17,6 @@ package com.android.systemui.recents; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; @@ -30,6 +29,7 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNL import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED; @@ -88,6 +88,7 @@ import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.buttons.KeyButtonView; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shared.recents.IOverviewProxy; @@ -144,6 +145,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final UserTracker mUserTracker; private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController; private final UiEventLogger mUiEventLogger; + private final DisplayTracker mDisplayTracker; private Region mActiveNavBarRegion; private SurfaceControl mNavigationBarSurface; @@ -225,11 +227,11 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void onImeSwitcherPressed() { - // TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since + // TODO(b/204901476) We're intentionally using the default display for now since // Launcher/Taskbar isn't display aware. mContext.getSystemService(InputMethodManager.class) .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */, - DEFAULT_DISPLAY); + mDisplayTracker.getDefaultDisplayId()); mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP); } @@ -507,6 +509,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis UserTracker userTracker, ScreenLifecycle screenLifecycle, UiEventLogger uiEventLogger, + DisplayTracker displayTracker, KeyguardUnlockAnimationController sysuiUnlockAnimationController, AssistUtils assistUtils, DumpManager dumpManager) { @@ -534,6 +537,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mSysUiState = sysUiState; mSysUiState.addCallback(this::notifySystemUiStateFlags); mUiEventLogger = uiEventLogger; + mDisplayTracker = displayTracker; dumpManager.registerDumpable(getClass().getSimpleName(), this); @@ -652,13 +656,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded, - boolean bouncerShowing, boolean isDozing, boolean panelExpanded) { + boolean bouncerShowing, boolean isDozing, boolean panelExpanded, boolean isDreaming) { mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING, keyguardShowing && !keyguardOccluded) .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED, keyguardShowing && keyguardOccluded) .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing) .setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing) + .setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming) .commitUpdate(mContext.getDisplayId()); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt index 017e57fcaf62..310baafbae1a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt @@ -25,8 +25,22 @@ import android.net.Uri import com.android.systemui.R object ActionIntentCreator { + /** @return a chooser intent to share the given URI. */ + fun createShareIntent(uri: Uri) = createShareIntent(uri, null, null) + /** @return a chooser intent to share the given URI with the optional provided subject. */ - fun createShareIntent(uri: Uri, subject: String?): Intent { + fun createShareIntentWithSubject(uri: Uri, subject: String?) = + createShareIntent(uri, subject = subject) + + /** @return a chooser intent to share the given URI with the optional provided extra text. */ + fun createShareIntentWithExtraText(uri: Uri, extraText: String?) = + createShareIntent(uri, extraText = extraText) + + private fun createShareIntent( + uri: Uri, + subject: String? = null, + extraText: String? = null + ): Intent { // Create a share intent, this will always go through the chooser activity first // which should not trigger auto-enter PiP val sharingIntent = @@ -43,6 +57,7 @@ object ActionIntentCreator { ) putExtra(Intent.EXTRA_SUBJECT, subject) + putExtra(Intent.EXTRA_TEXT, extraText) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt index 01e32b7ada5f..aa8e2c039684 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -22,7 +22,6 @@ import android.os.Bundle import android.os.RemoteException import android.os.UserHandle import android.util.Log -import android.view.Display import android.view.IRemoteAnimationFinishedCallback import android.view.IRemoteAnimationRunner import android.view.RemoteAnimationAdapter @@ -33,6 +32,7 @@ import com.android.internal.infra.ServiceConnector import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.settings.DisplayTracker import javax.inject.Inject import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher @@ -47,6 +47,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, private val context: Context, + private val displayTracker: DisplayTracker ) { /** * Execute the given intent with startActivity while performing operations for screenshot action @@ -82,7 +83,7 @@ constructor( val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0) try { WindowManagerGlobal.getWindowManagerService() - .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY) + .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId) } catch (e: Exception) { Log.e(TAG, "Error overriding screenshot app transition", e) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java index 814b8e90e0dd..4f5cb72438bc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java @@ -16,8 +16,6 @@ package com.android.systemui.screenshot; -import static android.view.Display.DEFAULT_DISPLAY; - import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_EDIT; import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_SHARE; import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT; @@ -35,6 +33,7 @@ import android.util.Log; import android.view.RemoteAnimationAdapter; import android.view.WindowManagerGlobal; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -52,14 +51,17 @@ public class ActionProxyReceiver extends BroadcastReceiver { private final CentralSurfaces mCentralSurfaces; private final ActivityManagerWrapper mActivityManagerWrapper; private final ScreenshotSmartActions mScreenshotSmartActions; + private final DisplayTracker mDisplayTracker; @Inject public ActionProxyReceiver(Optional<CentralSurfaces> centralSurfacesOptional, ActivityManagerWrapper activityManagerWrapper, - ScreenshotSmartActions screenshotSmartActions) { + ScreenshotSmartActions screenshotSmartActions, + DisplayTracker displayTracker) { mCentralSurfaces = centralSurfacesOptional.orElse(null); mActivityManagerWrapper = activityManagerWrapper; mScreenshotSmartActions = screenshotSmartActions; + mDisplayTracker = displayTracker; } @Override @@ -78,7 +80,8 @@ public class ActionProxyReceiver extends BroadcastReceiver { ScreenshotController.SCREENSHOT_REMOTE_RUNNER, 0, 0); try { WindowManagerGlobal.getWindowManagerService() - .overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY); + .overridePendingAppTransitionRemote(runner, + mDisplayTracker.getDefaultDisplayId()); } catch (Exception e) { Log.e(TAG, "Error overriding screenshot app transition", e); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java new file mode 100644 index 000000000000..146e5762790c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java @@ -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.screenshot; +import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; +import android.app.IAssistDataReceiver; +import android.app.assist.AssistContent; +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; + +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * Can be used to request the AssistContent from a provided task id, useful for getting the web uri + * if provided from the task. + * + * Forked from + * packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/AssistContentRequester.java + */ +@SysUISingleton +public class AssistContentRequester { + private static final String TAG = "AssistContentRequester"; + private static final String ASSIST_KEY_CONTENT = "content"; + + /** For receiving content, called on the main thread. */ + public interface Callback { + /** + * Called when the {@link android.app.assist.AssistContent} of the requested task is + * available. + **/ + void onAssistContentAvailable(AssistContent assistContent); + } + + private final IActivityTaskManager mActivityTaskManager; + private final String mPackageName; + private final Executor mCallbackExecutor; + private final Executor mSystemInteractionExecutor; + + // If system loses the callback, our internal cache of original callback will also get cleared. + private final Map<Object, Callback> mPendingCallbacks = + Collections.synchronizedMap(new WeakHashMap<>()); + + @Inject + public AssistContentRequester(Context context, @Main Executor mainExecutor, + @Background Executor bgExecutor) { + mActivityTaskManager = ActivityTaskManager.getService(); + mPackageName = context.getApplicationContext().getPackageName(); + mCallbackExecutor = mainExecutor; + mSystemInteractionExecutor = bgExecutor; + } + + /** + * Request the {@link AssistContent} from the task with the provided id. + * + * @param taskId to query for the content. + * @param callback to call when the content is available, called on the main thread. + */ + public void requestAssistContent(final int taskId, final Callback callback) { + // ActivityTaskManager interaction here is synchronous, so call off the main thread. + mSystemInteractionExecutor.execute(() -> { + try { + mActivityTaskManager.requestAssistDataForTask( + new AssistDataReceiver(callback, this), taskId, mPackageName); + } catch (RemoteException e) { + Log.e(TAG, "Requesting assist content failed for task: " + taskId, e); + } + }); + } + + private void executeOnMainExecutor(Runnable callback) { + mCallbackExecutor.execute(callback); + } + + private static final class AssistDataReceiver extends IAssistDataReceiver.Stub { + + // The AssistDataReceiver binder callback object is passed to a system server, that may + // keep hold of it for longer than the lifetime of the AssistContentRequester object, + // potentially causing a memory leak. In the callback passed to the system server, only + // keep a weak reference to the parent object and lookup its callback if it still exists. + private final WeakReference<AssistContentRequester> mParentRef; + private final Object mCallbackKey = new Object(); + + AssistDataReceiver(Callback callback, AssistContentRequester parent) { + parent.mPendingCallbacks.put(mCallbackKey, callback); + mParentRef = new WeakReference<>(parent); + } + + @Override + public void onHandleAssistData(Bundle data) { + if (data == null) { + return; + } + + final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT); + if (content == null) { + Log.e(TAG, "Received AssistData, but no AssistContent found"); + return; + } + + AssistContentRequester requester = mParentRef.get(); + if (requester != null) { + Callback callback = requester.mPendingCallbacks.get(mCallbackKey); + if (callback != null) { + requester.executeOnMainExecutor( + () -> callback.onAssistContentAvailable(content)); + } else { + Log.d(TAG, "Callback received after calling UI was disposed of"); + } + } else { + Log.d(TAG, "Callback received after Requester was collected"); + } + } + + @Override + public void onHandleAssistScreenshot(Bitmap screenshot) {} + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index d64b33bb3eb1..ca8e10176e7f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -366,7 +366,7 @@ public class LongScreenshotActivity extends Activity { private void doShare(Uri uri) { if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { - Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null); + Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri); mActionExecutor.launchIntentAsync(shareIntent, null, mScreenshotUserHandle.getIdentifier(), false); } else { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt new file mode 100644 index 000000000000..ad66514c689c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt @@ -0,0 +1,145 @@ +package com.android.systemui.screenshot + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.os.UserHandle +import android.view.View +import android.view.ViewGroup +import android.view.ViewGroup.MarginLayoutParams +import android.view.ViewTreeObserver +import android.view.animation.AccelerateDecelerateInterpolator +import androidx.constraintlayout.widget.Guideline +import com.android.systemui.R +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import javax.inject.Inject + +/** + * MessageContainerController controls the display of content in the screenshot message container. + */ +class MessageContainerController +@Inject +constructor( + private val workProfileMessageController: WorkProfileMessageController, + private val screenshotDetectionController: ScreenshotDetectionController, + private val featureFlags: FeatureFlags, +) { + private lateinit var container: ViewGroup + private lateinit var guideline: Guideline + private lateinit var workProfileFirstRunView: ViewGroup + private lateinit var detectionNoticeView: ViewGroup + private var animateOut: Animator? = null + + fun setView(screenshotView: ViewGroup) { + container = screenshotView.requireViewById(R.id.screenshot_message_container) + guideline = screenshotView.requireViewById(R.id.guideline) + + workProfileFirstRunView = container.requireViewById(R.id.work_profile_first_run) + detectionNoticeView = container.requireViewById(R.id.screenshot_detection_notice) + + // Restore to starting state. + container.visibility = View.GONE + guideline.setGuidelineEnd(0) + workProfileFirstRunView.visibility = View.GONE + detectionNoticeView.visibility = View.GONE + } + + // Minimal implementation for use when Flags.SCREENSHOT_METADATA isn't turned on. + fun onScreenshotTaken(userHandle: UserHandle) { + if (featureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { + val workProfileData = workProfileMessageController.onScreenshotTaken(userHandle) + if (workProfileData != null) { + workProfileFirstRunView.visibility = View.VISIBLE + detectionNoticeView.visibility = View.GONE + + workProfileMessageController.populateView( + workProfileFirstRunView, + workProfileData, + this::animateOutMessageContainer + ) + animateInMessageContainer() + } + } + } + + fun onScreenshotTaken(screenshot: ScreenshotData) { + if (featureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { + val workProfileData = + workProfileMessageController.onScreenshotTaken(screenshot.userHandle) + var notifiedApps: List<CharSequence> = listOf() + if (featureFlags.isEnabled(Flags.SCREENSHOT_DETECTION)) { + notifiedApps = screenshotDetectionController.maybeNotifyOfScreenshot(screenshot) + } + + // If work profile first run needs to show, bias towards that, otherwise show screenshot + // detection notification if needed. + if (workProfileData != null) { + workProfileFirstRunView.visibility = View.VISIBLE + detectionNoticeView.visibility = View.GONE + workProfileMessageController.populateView( + workProfileFirstRunView, + workProfileData, + this::animateOutMessageContainer + ) + animateInMessageContainer() + } else if (notifiedApps.isNotEmpty()) { + detectionNoticeView.visibility = View.VISIBLE + workProfileFirstRunView.visibility = View.GONE + screenshotDetectionController.populateView(detectionNoticeView, notifiedApps) + animateInMessageContainer() + } + } + } + + private fun animateInMessageContainer() { + if (container.visibility == View.VISIBLE) return + + // Need the container to be fully measured before animating in (to know animation offset + // destination) + container.visibility = View.VISIBLE + container.viewTreeObserver.addOnPreDrawListener( + object : ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + container.viewTreeObserver.removeOnPreDrawListener(this) + getAnimator(true).start() + return false + } + } + ) + } + + private fun animateOutMessageContainer() { + if (animateOut != null) return + + animateOut = + getAnimator(false).apply { + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + super.onAnimationEnd(animation) + container.visibility = View.GONE + animateOut = null + } + } + ) + start() + } + } + + private fun getAnimator(animateIn: Boolean): Animator { + val params = container.layoutParams as MarginLayoutParams + val offset = container.height + params.topMargin + params.bottomMargin + val anim = if (animateIn) ValueAnimator.ofFloat(0f, 1f) else ValueAnimator.ofFloat(1f, 0f) + with(anim) { + duration = ScreenshotView.SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS + interpolator = AccelerateDecelerateInterpolator() + addUpdateListener { valueAnimator: ValueAnimator -> + val interpolation = valueAnimator.animatedValue as Float + guideline.setGuidelineEnd((interpolation * offset).toInt()) + container.alpha = interpolation + } + } + return anim + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt index f011aab9da0f..4db48ac89e2d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt @@ -45,6 +45,7 @@ class RequestProcessor @Inject constructor( * * @param request the request to process */ + // TODO: Delete once SCREENSHOT_METADATA flag is launched suspend fun process(request: ScreenshotRequest): ScreenshotRequest { var result = request @@ -93,12 +94,67 @@ class RequestProcessor @Inject constructor( * @param request the request to process * @param callback the callback to provide the processed request, invoked from the main thread */ + // TODO: Delete once SCREENSHOT_METADATA flag is launched fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) { mainScope.launch { val result = process(request) callback.accept(result) } } + + /** + * Inspects the incoming ScreenshotData, potentially modifying it based upon policy. + * + * @param screenshot the screenshot to process + */ + suspend fun process(screenshot: ScreenshotData): ScreenshotData { + var result = screenshot + + // 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. + + // Whenever displayContentInfo is fetched, the topComponent is also populated + // regardless of the managed profile status. + + if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE && + flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY) + ) { + val info = policy.findPrimaryContent(policy.getDefaultDisplayId()) + Log.d(TAG, "findPrimaryContent: $info") + result.taskId = info.taskId + result.topComponent = info.component + result.userHandle = info.user + + if (policy.isManagedProfile(info.user.identifier)) { + val image = capture.captureTask(info.taskId) + ?: error("Task snapshot returned a null Bitmap!") + + // Provide the task snapshot as the screenshot + result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE + result.bitmap = image + result.screenBounds = info.bounds + } + } + + return result + } + + /** + * Note: This is for compatibility with existing Java. Prefer the suspending function when + * calling from a Coroutine context. + * + * @param screenshot the screenshot to process + * @param callback the callback to provide the processed screenshot, invoked from the main + * thread + */ + fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) { + mainScope.launch { + val result = process(screenshot) + callback.accept(result) + } + } } private const val TAG = "RequestProcessor" diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 6d87922f49eb..72a8e23fbff5 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -17,7 +17,6 @@ package com.android.systemui.screenshot; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY; @@ -44,6 +43,7 @@ import android.app.ExitTransitionCoordinator; import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.app.ICompatCameraControlCallback; import android.app.Notification; +import android.app.assist.AssistContent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -101,6 +101,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.util.Assert; import com.google.common.util.concurrent.ListenableFuture; @@ -272,6 +273,7 @@ public class ScreenshotController { private final ScrollCaptureClient mScrollCaptureClient; private final PhoneWindow mWindow; private final DisplayManager mDisplayManager; + private final DisplayTracker mDisplayTracker; private final ScrollCaptureController mScrollCaptureController; private final LongScreenshotData mLongScreenshotHolder; private final boolean mIsLowRamDevice; @@ -280,7 +282,7 @@ public class ScreenshotController { private final TimeoutHandler mScreenshotHandler; private final ActionIntentExecutor mActionExecutor; private final UserManager mUserManager; - private final WorkProfileMessageController mWorkProfileMessageController; + private final AssistContentRequester mAssistContentRequester; private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { if (DEBUG_INPUT) { @@ -290,6 +292,7 @@ public class ScreenshotController { }; private ScreenshotView mScreenshotView; + private final MessageContainerController mMessageContainerController; private Bitmap mScreenBitmap; private SaveImageInBackgroundTask mSaveInBgTask; private boolean mScreenshotTakenInPortrait; @@ -328,7 +331,9 @@ public class ScreenshotController { ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, ActionIntentExecutor actionExecutor, UserManager userManager, - WorkProfileMessageController workProfileMessageController + AssistContentRequester assistContentRequester, + MessageContainerController messageContainerController, + DisplayTracker displayTracker ) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; @@ -354,13 +359,15 @@ public class ScreenshotController { }); mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); + mDisplayTracker = displayTracker; final Context displayContext = context.createDisplayContext(getDefaultDisplay()); mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); mWindowManager = mContext.getSystemService(WindowManager.class); mFlags = flags; mActionExecutor = actionExecutor; mUserManager = userManager; - mWorkProfileMessageController = workProfileMessageController; + mMessageContainerController = messageContainerController; + mAssistContentRequester = assistContentRequester; mAccessibilityManager = AccessibilityManager.getInstance(mContext); @@ -390,16 +397,147 @@ public class ScreenshotController { ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED); } + void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, + RequestCallback requestCallback) { + Assert.isMainThread(); + mCurrentRequestCallback = requestCallback; + if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) { + Rect bounds = getFullScreenRect(); + screenshot.setBitmap( + mImageCapture.captureDisplay(mDisplayTracker.getDefaultDisplayId(), bounds)); + screenshot.setScreenBounds(bounds); + } + + if (screenshot.getBitmap() == null) { + Log.e(TAG, "handleScreenshot: Screenshot bitmap was null"); + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + if (mCurrentRequestCallback != null) { + mCurrentRequestCallback.reportError(); + } + return; + } + + if (!isUserSetupComplete(Process.myUserHandle())) { + Log.w(TAG, "User setup not complete, displaying toast only"); + // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing + // and sharing shouldn't be exposed to the user. + saveScreenshotAndToast(screenshot.getUserHandle(), finisher); + return; + } + + mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), + ClipboardOverlayController.SELF_PERMISSION); + + mScreenshotTakenInPortrait = + mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; + + String oldPackageName = mPackageName; + mPackageName = screenshot.getPackageNameString(); + + mScreenBitmap = screenshot.getBitmap(); + // Optimizations + mScreenBitmap.setHasAlpha(false); + mScreenBitmap.prepareToDraw(); + + prepareViewForNewScreenshot(screenshot, oldPackageName); + + saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, + this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); + + // The window is focusable by default + setWindowFocusable(true); + mScreenshotView.requestFocus(); + + enqueueScrollCaptureRequest(screenshot.getUserHandle()); + + attachWindow(); + + boolean showFlash = true; + if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { + if (screenshot.getScreenBounds() != null + && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(), + screenshot.getScreenBounds())) { + showFlash = false; + } else { + showFlash = true; + screenshot.setInsets(Insets.NONE); + screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(), + screenshot.getBitmap().getHeight())); + } + } + + prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> { + if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { + mMessageContainerController.onScreenshotTaken(screenshot); + } + }); + + if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { + mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( + mContext.getDrawable(R.drawable.overlay_badge_background), + screenshot.getUserHandle())); + } + mScreenshotView.setScreenshot(screenshot); + + if (screenshot.getTaskId() >= 0) { + mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), + new AssistContentRequester.Callback() { + @Override + public void onAssistContentAvailable(AssistContent assistContent) { + screenshot.setContextUrl(assistContent.getWebUri()); + } + }); + } + + if (DEBUG_WINDOW) { + Log.d(TAG, "setContentView: " + mScreenshotView); + } + setContentView(mScreenshotView); + // ignore system bar insets for the purpose of window layout + mWindow.getDecorView().setOnApplyWindowInsetsListener( + (v, insets) -> WindowInsets.CONSUMED); + mScreenshotHandler.cancelTimeout(); // restarted after animation + } + + void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) { + withWindowAttached(() -> { + if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY) + && mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) { + mScreenshotView.announceForAccessibility(mContext.getResources().getString( + R.string.screenshot_saving_work_profile_title)); + } else { + mScreenshotView.announceForAccessibility( + mContext.getResources().getString(R.string.screenshot_saving_title)); + } + }); + + mScreenshotView.reset(); + + if (mScreenshotView.isAttachedToWindow()) { + // if we didn't already dismiss for another reason + if (!mScreenshotView.isDismissing()) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, + oldPackageName); + } + if (DEBUG_WINDOW) { + Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " + + "(dismissing=" + mScreenshotView.isDismissing() + ")"); + } + } + + mScreenshotView.setPackageName(mPackageName); + + mScreenshotView.updateOrientation( + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); + } + @MainThread void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher, RequestCallback requestCallback) { Assert.isMainThread(); mCurrentRequestCallback = requestCallback; - DisplayMetrics displayMetrics = new DisplayMetrics(); - getDefaultDisplay().getRealMetrics(displayMetrics); - takeScreenshotInternal( - topComponent, finisher, - new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); + takeScreenshotInternal(topComponent, finisher, getFullScreenRect()); } @MainThread @@ -497,6 +635,9 @@ public class ScreenshotController { // Inflate the screenshot layout mScreenshotView = (ScreenshotView) LayoutInflater.from(mContext).inflate(R.layout.screenshot, null); + if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { + mMessageContainerController.setView(mScreenshotView); + } mScreenshotView.addOnAttachStateChangeListener( new View.OnAttachStateChangeListener() { @Override @@ -537,6 +678,7 @@ public class ScreenshotController { setWindowFocusable(false); } }, mActionExecutor, mFlags); + mScreenshotView.setDefaultDisplay(mDisplayTracker.getDefaultDisplayId()); mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); mScreenshotView.setOnKeyListener((v, keyCode, event) -> { @@ -570,7 +712,8 @@ public class ScreenshotController { // copy the input Rect, since SurfaceControl.screenshot can mutate it Rect screenRect = new Rect(crop); - Bitmap screenshot = mImageCapture.captureDisplay(DEFAULT_DISPLAY, crop); + Bitmap screenshot = mImageCapture.captureDisplay(mDisplayTracker.getDefaultDisplayId(), + crop); if (screenshot == null) { Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null"); @@ -641,6 +784,47 @@ public class ScreenshotController { setWindowFocusable(true); mScreenshotView.requestFocus(); + enqueueScrollCaptureRequest(owner); + + attachWindow(); + prepareAnimation(screenRect, showFlash, () -> { + if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { + mMessageContainerController.onScreenshotTaken(owner); + } + }); + + if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { + mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( + mContext.getDrawable(R.drawable.overlay_badge_background), owner)); + } + mScreenshotView.setScreenshot(mScreenBitmap, screenInsets); + if (DEBUG_WINDOW) { + Log.d(TAG, "setContentView: " + mScreenshotView); + } + setContentView(mScreenshotView); + // ignore system bar insets for the purpose of window layout + mWindow.getDecorView().setOnApplyWindowInsetsListener( + (v, insets) -> WindowInsets.CONSUMED); + mScreenshotHandler.cancelTimeout(); // restarted after animation + } + + private void prepareAnimation(Rect screenRect, boolean showFlash, + Runnable onAnimationComplete) { + mScreenshotView.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (DEBUG_WINDOW) { + Log.d(TAG, "onPreDraw: startAnimation"); + } + mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this); + startAnimation(screenRect, showFlash, onAnimationComplete); + return true; + } + }); + } + + private void enqueueScrollCaptureRequest(UserHandle owner) { // Wait until this window is attached to request because it is // the reference used to locate the target window (below). withWindowAttached(() -> { @@ -678,30 +862,6 @@ public class ScreenshotController { } }); }); - - attachWindow(); - mScreenshotView.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - if (DEBUG_WINDOW) { - Log.d(TAG, "onPreDraw: startAnimation"); - } - mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this); - startAnimation(screenRect, showFlash); - return true; - } - }); - if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { - mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( - mContext.getDrawable(R.drawable.overlay_badge_background), owner)); - } - mScreenshotView.setScreenshot(mScreenBitmap, screenInsets); - - // ignore system bar insets for the purpose of window layout - mWindow.getDecorView().setOnApplyWindowInsetsListener( - (v, insets) -> WindowInsets.CONSUMED); - mScreenshotHandler.cancelTimeout(); // restarted after animation } private void requestScrollCapture(UserHandle owner) { @@ -714,7 +874,7 @@ public class ScreenshotController { mLastScrollCaptureRequest.cancel(true); } final ListenableFuture<ScrollCaptureResponse> future = - mScrollCaptureClient.request(DEFAULT_DISPLAY); + mScrollCaptureClient.request(mDisplayTracker.getDefaultDisplayId()); mLastScrollCaptureRequest = future; mLastScrollCaptureRequest.addListener(() -> onScrollCaptureResponseReady(future, owner), mMainExecutor); @@ -745,7 +905,8 @@ public class ScreenshotController { mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> { DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); - Bitmap newScreenshot = mImageCapture.captureDisplay(DEFAULT_DISPLAY, + Bitmap newScreenshot = mImageCapture.captureDisplay( + mDisplayTracker.getDefaultDisplayId(), new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, @@ -809,7 +970,8 @@ public class ScreenshotController { SCREENSHOT_REMOTE_RUNNER, 0, 0); try { WindowManagerGlobal.getWindowManagerService() - .overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY); + .overridePendingAppTransitionRemote(runner, + mDisplayTracker.getDefaultDisplayId()); } catch (Exception e) { Log.e(TAG, "Error overriding screenshot app transition", e); } @@ -937,13 +1099,22 @@ public class ScreenshotController { /** * Starts the animation after taking the screenshot */ - private void startAnimation(Rect screenRect, boolean showFlash) { + private void startAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete) { if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { mScreenshotAnimation.cancel(); } mScreenshotAnimation = mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash); + if (onAnimationComplete != null) { + mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + onAnimationComplete.run(); + } + }); + } // Play the shutter sound to notify that we've taken a screenshot playCameraSound(); @@ -1042,9 +1213,6 @@ public class ScreenshotController { private void doPostAnimation(ScreenshotController.SavedImageData imageData) { mScreenshotView.setChipIntents(imageData); - if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { - mWorkProfileMessageController.onScreenshotTaken(imageData.owner, mScreenshotView); - } } /** @@ -1147,13 +1315,19 @@ public class ScreenshotController { } private Display getDefaultDisplay() { - return mDisplayManager.getDisplay(DEFAULT_DISPLAY); + return mDisplayManager.getDisplay(mDisplayTracker.getDefaultDisplayId()); } private boolean allowLongScreenshots() { return !mIsLowRamDevice; } + private Rect getFullScreenRect() { + DisplayMetrics displayMetrics = new DisplayMetrics(); + getDefaultDisplay().getRealMetrics(displayMetrics); + return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels); + } + /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt new file mode 100644 index 000000000000..e9be88a59990 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt @@ -0,0 +1,52 @@ +package com.android.systemui.screenshot + +import android.content.ComponentName +import android.graphics.Bitmap +import android.graphics.Insets +import android.graphics.Rect +import android.net.Uri +import android.os.UserHandle +import android.view.WindowManager.ScreenshotSource +import android.view.WindowManager.ScreenshotType +import androidx.annotation.VisibleForTesting +import com.android.internal.util.ScreenshotRequest + +/** ScreenshotData represents the current state of a single screenshot being acquired. */ +data class ScreenshotData( + @ScreenshotType var type: Int, + @ScreenshotSource var source: Int, + /** UserHandle for the owner of the app being screenshotted, if known. */ + var userHandle: UserHandle?, + /** ComponentName of the top-most app in the screenshot. */ + var topComponent: ComponentName?, + var screenBounds: Rect?, + var taskId: Int, + var insets: Insets, + var bitmap: Bitmap?, + /** App-provided URL representing the content the user was looking at in the screenshot. */ + var contextUrl: Uri? = null, +) { + val packageNameString: String + get() = if (topComponent == null) "" else topComponent!!.packageName + + companion object { + @JvmStatic + fun fromRequest(request: ScreenshotRequest): ScreenshotData { + return ScreenshotData( + request.type, + request.source, + if (request.userId >= 0) UserHandle.of(request.userId) else null, + request.topComponent, + request.boundsInScreen, + request.taskId, + request.insets, + request.bitmap, + ) + } + + @VisibleForTesting + fun forTesting(): ScreenshotData { + return ScreenshotData(0, 0, null, null, null, 0, Insets.NONE, null) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt new file mode 100644 index 000000000000..70ea2b5b9507 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.content.pm.PackageManager +import android.view.IWindowManager +import android.view.ViewGroup +import android.widget.TextView +import com.android.systemui.R +import javax.inject.Inject + +class ScreenshotDetectionController +@Inject +constructor( + private val windowManager: IWindowManager, + private val packageManager: PackageManager, +) { + /** + * Notify potentially listening apps of the screenshot. Return a list of the names of the apps + * notified. + */ + fun maybeNotifyOfScreenshot(data: ScreenshotData): List<CharSequence> { + // TODO: actually ask the window manager once API is available. + return listOf() + } + + fun populateView(view: ViewGroup, appNames: List<CharSequence>) { + assert(appNames.isNotEmpty()) + + val textView: TextView = view.requireViewById(R.id.screenshot_detection_notice_text) + if (appNames.size == 1) { + textView.text = + view.resources.getString(R.string.screenshot_detected_template, appNames[0]) + } else { + textView.text = + view.resources.getString( + R.string.screenshot_detected_multiple_template, + appNames[0] + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt index 3a3528606302..21a73100da06 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt @@ -32,12 +32,12 @@ import android.os.RemoteException import android.os.UserHandle import android.os.UserManager import android.util.Log -import android.view.Display.DEFAULT_DISPLAY import com.android.internal.annotations.VisibleForTesting 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.settings.DisplayTracker import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo import java.util.Arrays import javax.inject.Inject @@ -52,6 +52,7 @@ internal open class ScreenshotPolicyImpl @Inject constructor( private val userMgr: UserManager, private val atmService: IActivityTaskManager, @Background val bgDispatcher: CoroutineDispatcher, + private val displayTracker: DisplayTracker ) : ScreenshotPolicy { private val proxyConnector: ServiceConnector<IScreenshotProxy> = @@ -64,7 +65,7 @@ internal open class ScreenshotPolicyImpl @Inject constructor( ) override fun getDefaultDisplayId(): Int { - return DEFAULT_DISPLAY + return displayTracker.defaultDisplayId } override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 200a7dc08bb3..afba7ad24692 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -33,11 +33,11 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ValueAnimator; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Bitmap; @@ -81,7 +81,6 @@ import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.TextView; import androidx.constraintlayout.widget.ConstraintLayout; @@ -101,8 +100,7 @@ import java.util.ArrayList; * Handles the visual elements and animations for the screenshot flow. */ public class ScreenshotView extends FrameLayout implements - ViewTreeObserver.OnComputeInternalInsetsListener, - WorkProfileMessageController.WorkProfileMessageDisplay { + ViewTreeObserver.OnComputeInternalInsetsListener { interface ScreenshotViewCallback { void onUserInteraction(); @@ -122,7 +120,7 @@ public class ScreenshotView extends FrameLayout implements private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234; private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500; private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234; - private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400; + public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400; private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100; private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f; private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe @@ -134,14 +132,13 @@ public class ScreenshotView extends FrameLayout implements private final AccessibilityManager mAccessibilityManager; private final GestureDetector mSwipeDetector; + private int mDefaultDisplay = Display.DEFAULT_DISPLAY; private int mNavMode; private boolean mOrientationPortrait; private boolean mDirectionLTR; private ImageView mScrollingScrim; private DraggableConstraintLayout mScreenshotStatic; - private ViewGroup mMessageContainer; - private TextView mMessageContent; private ImageView mScreenshotPreview; private ImageView mScreenshotBadge; private View mScreenshotPreviewBorder; @@ -166,6 +163,8 @@ public class ScreenshotView extends FrameLayout implements private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>(); private PendingInteraction mPendingInteraction; + // Should only be set/used if the SCREENSHOT_METADATA flag is set. + private ScreenshotData mScreenshotData; private final InteractionJankMonitor mInteractionJankMonitor; private long mDefaultTimeoutOfTimeoutHandler; @@ -291,8 +290,11 @@ public class ScreenshotView extends FrameLayout implements mDismissButton.getBoundsOnScreen(tmpRect); swipeRegion.op(tmpRect, Region.Op.UNION); - mMessageContainer.findViewById(R.id.message_dismiss_button).getBoundsOnScreen(tmpRect); - swipeRegion.op(tmpRect, Region.Op.UNION); + View messageDismiss = findViewById(R.id.message_dismiss_button); + if (messageDismiss != null) { + messageDismiss.getBoundsOnScreen(tmpRect); + swipeRegion.op(tmpRect, Region.Op.UNION); + } return swipeRegion; } @@ -323,7 +325,7 @@ public class ScreenshotView extends FrameLayout implements private void startInputListening() { stopInputListening(); - mInputMonitor = new InputMonitorCompat("Screenshot", Display.DEFAULT_DISPLAY); + mInputMonitor = new InputMonitorCompat("Screenshot", mDefaultDisplay); mInputEventReceiver = mInputMonitor.getInputReceiver( Looper.getMainLooper(), Choreographer.getInstance(), ev -> { if (ev instanceof MotionEvent) { @@ -348,39 +350,11 @@ public class ScreenshotView extends FrameLayout implements } } - /** - * Show a notification under the screenshot view indicating that a work profile screenshot has - * been taken and which app can be used to view it. - * - * @param appName The name of the app to use to view screenshots - * @param appIcon Optional icon for the relevant files app - * @param onDismiss Runnable to be run when the user dismisses this message - */ - @Override - public void showWorkProfileMessage(CharSequence appName, @Nullable Drawable appIcon, - Runnable onDismiss) { - if (appIcon != null) { - // Replace the default icon if one is provided. - ImageView imageView = mMessageContainer.findViewById(R.id.screenshot_message_icon); - imageView.setImageDrawable(appIcon); - } - mMessageContent.setText( - mContext.getString(R.string.screenshot_work_profile_notification, appName)); - mMessageContainer.setVisibility(VISIBLE); - mMessageContainer.findViewById(R.id.message_dismiss_button).setOnClickListener((v) -> { - mMessageContainer.setVisibility(View.GONE); - onDismiss.run(); - }); - } - @Override // View protected void onFinishInflate() { + super.onFinishInflate(); mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim)); mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static)); - mMessageContainer = - requireNonNull(mScreenshotStatic.findViewById(R.id.screenshot_message_container)); - mMessageContent = - requireNonNull(mMessageContainer.findViewById(R.id.screenshot_message_content)); mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview)); mScreenshotPreviewBorder = requireNonNull( @@ -470,10 +444,21 @@ public class ScreenshotView extends FrameLayout implements mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets)); } + void setScreenshot(ScreenshotData screenshot) { + mScreenshotData = screenshot; + setScreenshot(screenshot.getBitmap(), screenshot.getInsets()); + mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, screenshot.getBitmap(), + screenshot.getInsets())); + } + void setPackageName(String packageName) { mPackageName = packageName; } + void setDefaultDisplay(int displayId) { + mDefaultDisplay = displayId; + } + void updateInsets(WindowInsets insets) { int orientation = mContext.getResources().getConfiguration().orientation; mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT); @@ -808,9 +793,17 @@ public class ScreenshotView extends FrameLayout implements mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName); if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { prepareSharedTransition(); - mActionExecutor.launchIntentAsync( - ActionIntentCreator.INSTANCE.createShareIntent( - imageData.uri, imageData.subject), + + Intent shareIntent; + if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && mScreenshotData != null + && mScreenshotData.getContextUrl() != null) { + shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithExtraText( + imageData.uri, mScreenshotData.getContextUrl().toString()); + } else { + shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithSubject( + imageData.uri, imageData.subject); + } + mActionExecutor.launchIntentAsync(shareIntent, imageData.shareTransition.get().bundle, imageData.owner.getIdentifier(), false); } else { @@ -1112,6 +1105,7 @@ public class ScreenshotView extends FrameLayout implements mQuickShareChip = null; setAlpha(1); mScreenshotStatic.setAlpha(1); + mScreenshotData = null; } private void startSharedTransition(ActionTransition transition) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 7b271a886d68..4214c8f6ecf2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -59,6 +59,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagListenable.FlagEvent; +import com.android.systemui.flags.Flags; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -221,8 +222,33 @@ public class TakeScreenshotService extends Service { return; } - mProcessor.processAsync(request, - (r) -> dispatchToController(r, onSaved, callback)); + if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA)) { + Log.d(TAG, "Processing screenshot data"); + ScreenshotData screenshotData = ScreenshotData.fromRequest(request); + mProcessor.processAsync(screenshotData, + (data) -> dispatchToController(data, onSaved, callback)); + } else { + mProcessor.processAsync(request, + (r) -> dispatchToController(r, onSaved, callback)); + } + } + + private void dispatchToController(ScreenshotData screenshot, + Consumer<Uri> uriConsumer, RequestCallback callback) { + + mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0, + screenshot.getPackageNameString()); + + if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE + && screenshot.getBitmap() == null) { + Log.e(TAG, "Got null bitmap from screenshot message"); + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + callback.reportError(); + return; + } + + mScreenshot.handleScreenshot(screenshot, uriConsumer, callback); } private void dispatchToController(ScreenshotRequest request, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt index 5d7e56f6c98a..66b78420146b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt @@ -23,13 +23,16 @@ import android.graphics.drawable.Drawable import android.os.UserHandle import android.os.UserManager import android.util.Log +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView import com.android.systemui.R import javax.inject.Inject /** - * Handles all the non-UI portions of the work profile first run: - * - Track whether the user has already dismissed it. - * - Load the proper icon and app name. + * Handles work profile first run, determining whether a first run UI should be shown and populating + * that UI if needed. */ class WorkProfileMessageController @Inject @@ -40,10 +43,12 @@ constructor( ) { /** - * Determine if a message should be shown to the user, send message details to messageDisplay if - * appropriate. + * @return a populated WorkProfileFirstRunData object if a work profile first run message should + * be shown */ - fun onScreenshotTaken(userHandle: UserHandle, messageDisplay: WorkProfileMessageDisplay) { + fun onScreenshotTaken(userHandle: UserHandle?): WorkProfileFirstRunData? { + if (userHandle == null) return null + if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) { var badgedIcon: Drawable? = null var label: CharSequence? = null @@ -65,7 +70,27 @@ constructor( val badgedLabel = packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle) - messageDisplay.showWorkProfileMessage(badgedLabel, badgedIcon) { onMessageDismissed() } + return WorkProfileFirstRunData(badgedLabel, badgedIcon) + } + return null + } + + /** + * Use the provided WorkProfileFirstRunData to populate the work profile first run UI in the + * given view. + */ + fun populateView(view: ViewGroup, data: WorkProfileFirstRunData, animateOut: () -> Unit) { + if (data.icon != null) { + // Replace the default icon if one is provided. + val imageView: ImageView = view.requireViewById<ImageView>(R.id.screenshot_message_icon) + imageView.setImageDrawable(data.icon) + } + val messageContent = view.requireViewById<TextView>(R.id.screenshot_message_content) + messageContent.text = + view.context.getString(R.string.screenshot_work_profile_notification, data.appName) + view.requireViewById<View>(R.id.message_dismiss_button).setOnClickListener { + animateOut() + onMessageDismissed() } } @@ -89,14 +114,7 @@ constructor( private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name) - /** UI that can show work profile messages (ScreenshotView in practice) */ - interface WorkProfileMessageDisplay { - /** - * Show the given message and icon, calling onDismiss if the user explicitly dismisses the - * message. - */ - fun showWorkProfileMessage(text: CharSequence, icon: Drawable?, onDismiss: Runnable) - } + data class WorkProfileFirstRunData constructor(val appName: CharSequence, val icon: Drawable?) companion object { const val TAG = "WorkProfileMessageCtrl" diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt new file mode 100644 index 000000000000..bb7f721ad61f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt @@ -0,0 +1,63 @@ +/* + * 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.settings + +import android.view.Display +import java.util.concurrent.Executor + +/** + * Display tracker for SystemUI. + * + * This tracker provides async access to display information, as well as callbacks for display + * changes. + */ +interface DisplayTracker { + + /** The id for the default display for the current SystemUI instance. */ + val defaultDisplayId: Int + + /** All displays that should be associated with the current SystemUI instance. */ + val allDisplays: Array<Display> + + /** + * Add a [Callback] to be notified of display changes, including additions, removals, and + * configuration changes, on a particular [Executor]. + */ + fun addDisplayChangeCallback(callback: Callback, executor: Executor) + + /** + * Add a [Callback] to be notified of display brightness changes, on a particular [Executor]. + * This callback will trigger Callback#onDisplayChanged for a display brightness change. + */ + fun addBrightnessChangeCallback(callback: Callback, executor: Executor) + + /** Remove a [Callback] previously added. */ + fun removeCallback(callback: Callback) + + /** Ćallback for notifying of changes. */ + interface Callback { + + /** Notifies that a display has been added. */ + @JvmDefault fun onDisplayAdded(displayId: Int) {} + + /** Notifies that a display has been removed. */ + @JvmDefault fun onDisplayRemoved(displayId: Int) {} + + /** Notifies a display has been changed */ + @JvmDefault fun onDisplayChanged(displayId: Int) {} + } +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt new file mode 100644 index 000000000000..5169f88c373c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt @@ -0,0 +1,158 @@ +/* + * 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.settings + +import android.hardware.display.DisplayManager +import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS +import android.os.Handler +import android.view.Display +import androidx.annotation.GuardedBy +import androidx.annotation.VisibleForTesting +import androidx.annotation.WorkerThread +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.Assert +import java.lang.ref.WeakReference +import java.util.concurrent.Executor + +class DisplayTrackerImpl +internal constructor( + val displayManager: DisplayManager, + @Background val backgroundHandler: Handler +) : DisplayTracker { + override val defaultDisplayId: Int = Display.DEFAULT_DISPLAY + override val allDisplays: Array<Display> + get() = displayManager.displays + + @GuardedBy("displayCallbacks") + private val displayCallbacks: MutableList<DisplayTrackerDataItem> = ArrayList() + @GuardedBy("brightnessCallbacks") + private val brightnessCallbacks: MutableList<DisplayTrackerDataItem> = ArrayList() + + @VisibleForTesting + val displayChangedListener: DisplayManager.DisplayListener = + object : DisplayManager.DisplayListener { + override fun onDisplayAdded(displayId: Int) { + val list = synchronized(displayCallbacks) { displayCallbacks.toList() } + onDisplayAdded(displayId, list) + } + + override fun onDisplayRemoved(displayId: Int) { + val list = synchronized(displayCallbacks) { displayCallbacks.toList() } + onDisplayRemoved(displayId, list) + } + + override fun onDisplayChanged(displayId: Int) { + val list = synchronized(displayCallbacks) { displayCallbacks.toList() } + onDisplayChanged(displayId, list) + } + } + + @VisibleForTesting + val displayBrightnessChangedListener: DisplayManager.DisplayListener = + object : DisplayManager.DisplayListener { + override fun onDisplayAdded(displayId: Int) {} + + override fun onDisplayRemoved(displayId: Int) {} + + override fun onDisplayChanged(displayId: Int) { + val list = synchronized(brightnessCallbacks) { brightnessCallbacks.toList() } + onDisplayChanged(displayId, list) + } + } + + override fun addDisplayChangeCallback(callback: DisplayTracker.Callback, executor: Executor) { + synchronized(displayCallbacks) { + if (displayCallbacks.isEmpty()) { + displayManager.registerDisplayListener(displayChangedListener, backgroundHandler) + } + displayCallbacks.add(DisplayTrackerDataItem(WeakReference(callback), executor)) + } + } + + override fun addBrightnessChangeCallback( + callback: DisplayTracker.Callback, + executor: Executor + ) { + synchronized(brightnessCallbacks) { + if (brightnessCallbacks.isEmpty()) { + displayManager.registerDisplayListener( + displayBrightnessChangedListener, + backgroundHandler, + EVENT_FLAG_DISPLAY_BRIGHTNESS + ) + } + brightnessCallbacks.add(DisplayTrackerDataItem(WeakReference(callback), executor)) + } + } + + override fun removeCallback(callback: DisplayTracker.Callback) { + synchronized(displayCallbacks) { + val changed = displayCallbacks.removeIf { it.sameOrEmpty(callback) } + if (changed && displayCallbacks.isEmpty()) { + displayManager.unregisterDisplayListener(displayChangedListener) + } + } + + synchronized(brightnessCallbacks) { + val changed = brightnessCallbacks.removeIf { it.sameOrEmpty(callback) } + if (changed && brightnessCallbacks.isEmpty()) { + displayManager.unregisterDisplayListener(displayBrightnessChangedListener) + } + } + } + + @WorkerThread + private fun onDisplayAdded(displayId: Int, list: List<DisplayTrackerDataItem>) { + Assert.isNotMainThread() + + notifySubscribers({ onDisplayAdded(displayId) }, list) + } + + @WorkerThread + private fun onDisplayRemoved(displayId: Int, list: List<DisplayTrackerDataItem>) { + Assert.isNotMainThread() + + notifySubscribers({ onDisplayRemoved(displayId) }, list) + } + + @WorkerThread + private fun onDisplayChanged(displayId: Int, list: List<DisplayTrackerDataItem>) { + Assert.isNotMainThread() + + notifySubscribers({ onDisplayChanged(displayId) }, list) + } + + private inline fun notifySubscribers( + crossinline action: DisplayTracker.Callback.() -> Unit, + list: List<DisplayTrackerDataItem> + ) { + list.forEach { + if (it.callback.get() != null) { + it.executor.execute { it.callback.get()?.action() } + } + } + } + + private data class DisplayTrackerDataItem( + val callback: WeakReference<DisplayTracker.Callback>, + val executor: Executor + ) { + fun sameOrEmpty(other: DisplayTracker.Callback): Boolean { + return callback.get()?.equals(other) ?: true + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index 1558ac533137..287e8101f86d 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -62,12 +62,24 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { fun removeCallback(callback: Callback) /** - * Ćallback for notifying of changes. + * Callback for notifying of changes. */ interface Callback { /** + * Notifies that the current user is being changed. + * Override this method to run things while the screen is frozen for the user switch. + * Please use {@link #onUserChanged} if the task doesn't need to push the unfreezing of the + * screen further. Please be aware that code executed in this callback will lengthen the + * user switch duration. + */ + @JvmDefault + fun onUserChanging(newUser: Int, userContext: Context) {} + + /** * Notifies that the current user has changed. + * Override this method to run things after the screen is unfrozen for the user switch. + * Please see {@link #onUserChanging} if you need to hide jank. */ @JvmDefault fun onUserChanged(newUser: Int, userContext: Context) {} @@ -78,4 +90,4 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { @JvmDefault fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {} } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 61390c582fd6..9f551c6ebd34 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -16,6 +16,8 @@ package com.android.systemui.settings +import android.app.IActivityManager +import android.app.UserSwitchObserver import android.content.BroadcastReceiver import android.content.ContentResolver import android.content.Context @@ -23,6 +25,7 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.UserInfo import android.os.Handler +import android.os.IRemoteCallback import android.os.UserHandle import android.os.UserManager import android.util.Log @@ -34,6 +37,7 @@ import com.android.systemui.util.Assert import java.io.PrintWriter import java.lang.IllegalStateException import java.lang.ref.WeakReference +import java.util.concurrent.CountDownLatch import java.util.concurrent.Executor import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -56,6 +60,7 @@ import kotlin.reflect.KProperty class UserTrackerImpl internal constructor( private val context: Context, private val userManager: UserManager, + private val iActivityManager: IActivityManager, private val dumpManager: DumpManager, private val backgroundHandler: Handler ) : UserTracker, Dumpable, BroadcastReceiver() { @@ -107,7 +112,6 @@ class UserTrackerImpl internal constructor( setUserIdInternal(startingUser) val filter = IntentFilter().apply { - addAction(Intent.ACTION_USER_SWITCHED) addAction(Intent.ACTION_USER_INFO_CHANGED) // These get called when a managed profile goes in or out of quiet mode. addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) @@ -118,14 +122,13 @@ class UserTrackerImpl internal constructor( } context.registerReceiverForAllUsers(this, filter, null /* permission */, backgroundHandler) + registerUserSwitchObserver() + dumpManager.registerDumpable(TAG, this) } override fun onReceive(context: Context, intent: Intent) { when (intent.action) { - Intent.ACTION_USER_SWITCHED -> { - handleSwitchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL)) - } Intent.ACTION_USER_INFO_CHANGED, Intent.ACTION_MANAGED_PROFILE_AVAILABLE, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE, @@ -157,22 +160,43 @@ class UserTrackerImpl internal constructor( return ctx to profiles } + private fun registerUserSwitchObserver() { + iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() { + override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { + backgroundHandler.run { + handleUserSwitching(newUserId) + reply?.sendResult(null) + } + } + + override fun onUserSwitchComplete(newUserId: Int) { + backgroundHandler.run { + handleUserSwitchComplete(newUserId) + } + } + }, TAG) + } + @WorkerThread - private fun handleSwitchUser(newUser: Int) { + private fun handleUserSwitching(newUserId: Int) { Assert.isNotMainThread() - if (newUser == UserHandle.USER_NULL) { - Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent") - return - } + Log.i(TAG, "Switching to user $newUserId") - if (newUser == userId) return - Log.i(TAG, "Switching to user $newUser") + setUserIdInternal(newUserId) + notifySubscribers { + onUserChanging(newUserId, userContext) + }.await() + } - val (ctx, profiles) = setUserIdInternal(newUser) + @WorkerThread + private fun handleUserSwitchComplete(newUserId: Int) { + Assert.isNotMainThread() + Log.i(TAG, "Switched to user $newUserId") + setUserIdInternal(newUserId) notifySubscribers { - onUserChanged(newUser, ctx) - onProfilesChanged(profiles) + onUserChanged(newUserId, userContext) + onProfilesChanged(userProfiles) } } @@ -201,17 +225,25 @@ class UserTrackerImpl internal constructor( } } - private inline fun notifySubscribers(crossinline action: UserTracker.Callback.() -> Unit) { + private inline fun notifySubscribers( + crossinline action: UserTracker.Callback.() -> Unit + ): CountDownLatch { val list = synchronized(callbacks) { callbacks.toList() } + val latch = CountDownLatch(list.size) + list.forEach { if (it.callback.get() != null) { it.executor.execute { it.callback.get()?.action() + latch.countDown() } + } else { + latch.countDown() } } + return latch } override fun dump(pw: PrintWriter, args: Array<out String>) { @@ -258,4 +290,4 @@ private data class DataItem( fun sameOrEmpty(other: UserTracker.Callback): Boolean { return callback.get()?.equals(other) ?: true } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 2f6081bc9f8d..8089d01e7343 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -27,10 +27,10 @@ import android.content.Context; import android.database.ContentObserver; import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManager.DisplayListener; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; @@ -49,6 +49,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.BrightnessMirrorController; @@ -78,19 +79,14 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig private final ToggleSlider mControl; private final DisplayManager mDisplayManager; private final UserTracker mUserTracker; + private final DisplayTracker mDisplayTracker; private final IVrManager mVrManager; private final Executor mMainExecutor; private final Handler mBackgroundHandler; private final BrightnessObserver mBrightnessObserver; - private final DisplayListener mDisplayListener = new DisplayListener() { - @Override - public void onDisplayAdded(int displayId) {} - - @Override - public void onDisplayRemoved(int displayId) {} - + private final DisplayTracker.Callback mBrightnessListener = new DisplayTracker.Callback() { @Override public void onDisplayChanged(int displayId) { mBackgroundHandler.post(mUpdateSliderRunnable); @@ -143,14 +139,14 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig cr.registerContentObserver( BRIGHTNESS_FOR_VR_FLOAT_URI, false, this, UserHandle.USER_ALL); - mDisplayManager.registerDisplayListener(mDisplayListener, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); + mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener, + new HandlerExecutor(mHandler)); } public void stopObserving() { final ContentResolver cr = mContext.getContentResolver(); cr.unregisterContentObserver(this); - mDisplayManager.unregisterDisplayListener(mDisplayListener); + mDisplayTracker.removeCallback(mBrightnessListener); } } @@ -292,6 +288,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig Context context, ToggleSlider control, UserTracker userTracker, + DisplayTracker displayTracker, @Main Executor mainExecutor, @Background Handler bgHandler) { mContext = context; @@ -300,6 +297,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mMainExecutor = mainExecutor; mBackgroundHandler = bgHandler; mUserTracker = userTracker; + mDisplayTracker = displayTracker; mBrightnessObserver = new BrightnessObserver(mHandler); mDisplayId = mContext.getDisplayId(); @@ -450,6 +448,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig public static class Factory { private final Context mContext; private final UserTracker mUserTracker; + private final DisplayTracker mDisplayTracker; private final Executor mMainExecutor; private final Handler mBackgroundHandler; @@ -457,10 +456,12 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig public Factory( Context context, UserTracker userTracker, + DisplayTracker displayTracker, @Main Executor mainExecutor, @Background Handler bgHandler) { mContext = context; mUserTracker = userTracker; + mDisplayTracker = displayTracker; mMainExecutor = mainExecutor; mBackgroundHandler = bgHandler; } @@ -471,6 +472,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mContext, toggleSlider, mUserTracker, + mDisplayTracker, mMainExecutor, mBackgroundHandler); } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index e208be957510..8879501fa03d 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -36,6 +36,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import java.util.List; @@ -49,16 +50,19 @@ public class BrightnessDialog extends Activity { private BrightnessController mBrightnessController; private final BrightnessSliderController.Factory mToggleSliderFactory; private final UserTracker mUserTracker; + private final DisplayTracker mDisplayTracker; private final Executor mMainExecutor; private final Handler mBackgroundHandler; @Inject public BrightnessDialog( UserTracker userTracker, + DisplayTracker displayTracker, BrightnessSliderController.Factory factory, @Main Executor mainExecutor, @Background Handler bgHandler) { mUserTracker = userTracker; + mDisplayTracker = displayTracker; mToggleSliderFactory = factory; mMainExecutor = mainExecutor; mBackgroundHandler = bgHandler; @@ -106,7 +110,7 @@ public class BrightnessDialog extends Activity { frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT); mBrightnessController = new BrightnessController( - this, controller, mUserTracker, mMainExecutor, mBackgroundHandler); + this, controller, mUserTracker, mDisplayTracker, mMainExecutor, mBackgroundHandler); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java index 2f62e44ba4c4..e9a1dd7e6ecb 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java +++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java @@ -17,7 +17,9 @@ package com.android.systemui.settings.dagger; import android.app.ActivityManager; +import android.app.IActivityManager; import android.content.Context; +import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.UserManager; @@ -25,6 +27,8 @@ import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; +import com.android.systemui.settings.DisplayTracker; +import com.android.systemui.settings.DisplayTrackerImpl; import com.android.systemui.settings.UserContentResolverProvider; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserFileManager; @@ -57,15 +61,26 @@ public abstract class MultiUserUtilsModule { static UserTracker provideUserTracker( Context context, UserManager userManager, + IActivityManager iActivityManager, DumpManager dumpManager, @Background Handler handler ) { int startingUser = ActivityManager.getCurrentUser(); - UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, dumpManager, handler); + UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, iActivityManager, + dumpManager, handler); tracker.initialize(startingUser); return tracker; } + @SysUISingleton + @Provides + static DisplayTracker provideDisplayTracker( + DisplayManager displayManager, + @Background Handler handler + ) { + return new DisplayTrackerImpl(displayManager, handler); + } + @Binds @IntoMap @ClassKey(UserFileManagerImpl.class) diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index 88676371bd6b..197232ecb547 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade +import android.animation.Animator import android.annotation.IdRes import android.app.StatusBarManager import android.content.res.Configuration @@ -45,7 +46,6 @@ import com.android.systemui.qs.HeaderPrivacyIconsController import com.android.systemui.qs.carrier.QSCarrierGroup import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID -import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider @@ -176,9 +176,13 @@ class LargeScreenShadeHeaderController @Inject constructor( var shadeExpandedFraction = -1f set(value) { if (field != value) { + val oldAlpha = header.alpha header.alpha = ShadeInterpolation.getContentAlpha(value) field = value - updateVisibility() + if ((oldAlpha == 0f && header.alpha > 0f) || + (oldAlpha > 0f && header.alpha == 0f)) { + updateVisibility() + } } } @@ -305,6 +309,8 @@ class LargeScreenShadeHeaderController @Inject constructor( val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f v.pivotX = newPivot v.pivotY = v.height.toFloat() / 2 + + qsCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0) } } @@ -335,9 +341,28 @@ class LargeScreenShadeHeaderController @Inject constructor( .setUpdateListener { updateVisibility() } + .setListener(endAnimationListener) .start() } + private val endAnimationListener = object : Animator.AnimatorListener { + override fun onAnimationCancel(animation: Animator?) { + clearListeners() + } + + override fun onAnimationEnd(animation: Animator?) { + clearListeners() + } + + override fun onAnimationRepeat(animation: Animator?) {} + + override fun onAnimationStart(animation: Animator?) {} + + private fun clearListeners() { + header.animate().setListener(null).setUpdateListener(null) + } + } + private fun loadConstraints() { if (header is MotionLayout) { // Use resources.getXml instead of passing the resource id due to bug b/205018300 diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 392a851290de..469f3833ba92 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -140,12 +140,16 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; +import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; +import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; @@ -204,7 +208,6 @@ import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; -import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; @@ -243,6 +246,7 @@ import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Provider; +import kotlin.Unit; import kotlinx.coroutines.CoroutineDispatcher; @CentralSurfacesComponent.CentralSurfacesScope @@ -698,6 +702,7 @@ public final class NotificationPanelViewController implements Dumpable { private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; private KeyguardTransitionInteractor mKeyguardTransitionInteractor; + private final KeyguardInteractor mKeyguardInteractor; private CoroutineDispatcher mMainDispatcher; private boolean mIsOcclusionTransitionRunning = false; private int mDreamingToLockscreenTransitionTranslationY; @@ -827,7 +832,9 @@ public final class NotificationPanelViewController implements Dumpable { LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel, @Main CoroutineDispatcher mainDispatcher, KeyguardTransitionInteractor keyguardTransitionInteractor, - DumpManager dumpManager) { + DumpManager dumpManager, + KeyguardLongPressViewModel keyguardLongPressViewModel, + KeyguardInteractor keyguardInteractor) { keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onKeyguardFadingAwayChanged() { @@ -848,6 +855,7 @@ public final class NotificationPanelViewController implements Dumpable { mGoneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel; mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; + mKeyguardInteractor = keyguardInteractor; mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { @@ -1000,6 +1008,14 @@ public final class NotificationPanelViewController implements Dumpable { updateUserSwitcherFlags(); mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel; mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor; + KeyguardLongPressViewBinder.bind( + mView.requireViewById(R.id.keyguard_long_press), + keyguardLongPressViewModel, + () -> { + onEmptySpaceClick(); + return Unit.INSTANCE; + }, + mFalsingManager); onFinishInflate(); keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener( new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() { @@ -2157,6 +2173,7 @@ public final class NotificationPanelViewController implements Dumpable { } ValueAnimator animator = createHeightAnimator(target, overshootAmount); if (expand) { + maybeVibrateOnOpening(true /* openingWithTouch */); if (expandBecauseOfFalsing && vel < 0) { vel = 0; } @@ -2167,6 +2184,7 @@ public final class NotificationPanelViewController implements Dumpable { animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION); } } else { + mHasVibratedOnOpen = false; if (shouldUseDismissingAnimation()) { if (vel == 0) { animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); @@ -2516,7 +2534,7 @@ public final class NotificationPanelViewController implements Dumpable { if (!mSplitShadeEnabled && computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) { mShadeLog.logMotionEvent(event, - "handleQsTouch: QQS touched while shade collapsing, QS tracking disabled"); + "handleQsTouch: shade touched while collapsing, QS tracking disabled"); mQsTracking = false; } if (!mQsExpandImmediate && mQsTracking) { @@ -3129,6 +3147,7 @@ public final class NotificationPanelViewController implements Dumpable { mQsClipBottom, radius, qsVisible && !mSplitShadeEnabled); + mKeyguardInteractor.setQuickSettingsVisible(mQsVisible); } // The padding on this area is large enough that we can use a cheaper clipping strategy mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null); @@ -3767,7 +3786,8 @@ public final class NotificationPanelViewController implements Dumpable { // change due to "unlock hint animation." In this case, fading out the bottom area // would also hide the message that says "swipe to unlock," we don't want to do that. float expansionAlpha = MathUtils.map( - isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f, + isUnlockHintRunning() ? 0 : KeyguardBouncerConstants.ALPHA_EXPANSION_THRESHOLD, 1f, + 0f, 1f, getExpandedFraction()); float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction()); alpha *= mBottomAreaShadeAlpha; @@ -4938,8 +4958,12 @@ public final class NotificationPanelViewController implements Dumpable { beginJankMonitoring(); } mInitialOffsetOnTouch = expandedHeight; - mInitialExpandY = newY; - mInitialExpandX = newX; + if (!mTracking || isFullyCollapsed()) { + mInitialExpandY = newY; + mInitialExpandX = newX; + } else { + mShadeLog.d("not setting mInitialExpandY in startExpandMotion"); + } mInitialTouchFromKeyguard = mKeyguardStateController.isShowing(); if (startTracking) { mTouchSlopExceeded = true; @@ -6123,8 +6147,12 @@ public final class NotificationPanelViewController implements Dumpable { + " false"); return true; } - mInitialExpandY = y; - mInitialExpandX = x; + if (!mTracking || isFullyCollapsed()) { + mInitialExpandY = y; + mInitialExpandX = x; + } else { + mShadeLog.d("not setting mInitialExpandY in onInterceptTouch"); + } mTouchStartedInEmptyArea = !isInContentBounds(x, y); mTouchSlopExceeded = mTouchSlopExceededBeforeDown; mMotionAborted = false; @@ -6313,7 +6341,8 @@ public final class NotificationPanelViewController implements Dumpable { final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN + || event.getActionMasked() == MotionEvent.ACTION_MOVE) { mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop(); mIgnoreXTouchSlop = true; } @@ -6375,7 +6404,7 @@ public final class NotificationPanelViewController implements Dumpable { mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction); } addMovement(event); - if (!isFullyCollapsed()) { + if (!isFullyCollapsed() && !isOnKeyguard()) { maybeVibrateOnOpening(true /* openingWithTouch */); } float h = y - mInitialExpandY; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index ab2e692915ad..156e4fd1889f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -563,7 +563,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mCurrentState.keyguardOccluded, mCurrentState.bouncerShowing, mCurrentState.dozing, - mCurrentState.panelExpanded); + mCurrentState.panelExpanded, + mCurrentState.dreaming); } } @@ -778,6 +779,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } @Override + public void setDreaming(boolean dreaming) { + mCurrentState.dreaming = dreaming; + apply(mCurrentState); + } + + @Override public void setForcePluginOpen(boolean forceOpen, Object token) { if (forceOpen) { mCurrentState.forceOpenTokens.add(token); @@ -904,5 +911,10 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW public void onDozingChanged(boolean isDozing) { setDozing(isDozing); } + + @Override + public void onDreamingChanged(boolean isDreaming) { + setDreaming(isDreaming); + } }; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt index 736404aa548a..fed9b8469c4b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt @@ -23,8 +23,8 @@ import com.android.systemui.shade.NotificationShadeWindowState.Buffer import com.android.systemui.statusbar.StatusBarState /** - * Represents state of shade window, used by [NotificationShadeWindowControllerImpl]. - * Contains nested class [Buffer] for pretty table logging in bug reports. + * Represents state of shade window, used by [NotificationShadeWindowControllerImpl]. Contains + * nested class [Buffer] for pretty table logging in bug reports. */ class NotificationShadeWindowState( @JvmField var keyguardShowing: Boolean = false, @@ -55,6 +55,7 @@ class NotificationShadeWindowState( @JvmField var remoteInputActive: Boolean = false, @JvmField var forcePluginOpen: Boolean = false, @JvmField var dozing: Boolean = false, + @JvmField var dreaming: Boolean = false, @JvmField var scrimsVisibility: Int = 0, @JvmField var backgroundBlurRadius: Int = 0, ) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 7ed6e3e55623..60fa865b83bc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -160,12 +160,10 @@ public class NotificationShadeWindowViewController { // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); - if (featureFlags.isEnabled(Flags.MODERN_BOUNCER)) { - KeyguardBouncerViewBinder.bind( - mView.findViewById(R.id.keyguard_bouncer_container), - keyguardBouncerViewModel, - keyguardBouncerComponentFactory); - } + KeyguardBouncerViewBinder.bind( + mView.findViewById(R.id.keyguard_bouncer_container), + keyguardBouncerViewModel, + keyguardBouncerComponentFactory); if (featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)) { collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 85b259e54f37..de02115184b6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -31,6 +31,7 @@ import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.fragments.FragmentService import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.plugins.qs.QS import com.android.systemui.plugins.qs.QSContainerController @@ -54,6 +55,7 @@ class NotificationsQSContainerController @Inject constructor( private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController, private val shadeExpansionStateManager: ShadeExpansionStateManager, private val featureFlags: FeatureFlags, + private val fragmentService: FragmentService, @Main private val delayableExecutor: DelayableExecutor ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController { @@ -128,6 +130,7 @@ class NotificationsQSContainerController @Inject constructor( mView.setInsetsChangedListener(delayedInsetSetter) mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) } mView.setConfigurationChangedListener { updateResources() } + fragmentService.getFragmentHostManager(mView).addTagListener(QS.TAG, mView) } override fun onViewDetached() { @@ -136,6 +139,7 @@ class NotificationsQSContainerController @Inject constructor( mView.removeOnInsetsChangedListener() mView.removeQSFragmentAttachedListener() mView.setConfigurationChangedListener(null) + fragmentService.getFragmentHostManager(mView).removeTagListener(QS.TAG, mView) } fun updateResources() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java index 02316b7965ca..f73dde632051 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java @@ -29,7 +29,6 @@ import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; import com.android.systemui.R; -import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.statusbar.notification.AboveShelfObserver; @@ -133,18 +132,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - FragmentHostManager.get(this).addTagListener(QS.TAG, this); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - FragmentHostManager.get(this).removeTagListener(QS.TAG, this); - } - - @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { mInsetsChangedListener.accept(insets); return insets; diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt index b02a45a913db..393279b0b054 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt @@ -18,7 +18,6 @@ package com.android.systemui.smartspace.dagger import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.smartspace.SmartspacePrecondition import com.android.systemui.smartspace.SmartspaceTargetFilter -import com.android.systemui.smartspace.filters.LockscreenAndDreamTargetFilter import com.android.systemui.smartspace.preconditions.LockscreenPrecondition import dagger.Binds import dagger.BindsOptionalOf @@ -35,11 +34,6 @@ abstract class SmartspaceModule { const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin" /** - * The lockscreen smartspace target filter. - */ - const val LOCKSCREEN_SMARTSPACE_TARGET_FILTER = "lockscreen_smartspace_target_filter" - - /** * The dream smartspace target filter. */ const val DREAM_SMARTSPACE_TARGET_FILTER = "dream_smartspace_target_filter" @@ -48,6 +42,11 @@ abstract class SmartspaceModule { * The precondition for dream smartspace */ const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition" + + /** + * The BcSmartspaceDataPlugin for the standalone weather. + */ + const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin" } @BindsOptionalOf @@ -59,12 +58,6 @@ abstract class SmartspaceModule { abstract fun optionalDreamsBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin? @Binds - @Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER) - abstract fun provideLockscreenSmartspaceTargetFilter( - filter: LockscreenAndDreamTargetFilter? - ): SmartspaceTargetFilter? - - @Binds @Named(DREAM_SMARTSPACE_PRECONDITION) abstract fun bindSmartspacePrecondition( lockscreenPrecondition: LockscreenPrecondition? diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt b/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt index 1302ec9dbc55..88e8ad9d1bae 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt @@ -15,8 +15,6 @@ */ package com.android.systemui.smartspace.preconditions -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.smartspace.SmartspacePrecondition import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.util.concurrency.Execution @@ -24,11 +22,9 @@ import javax.inject.Inject /** * {@link LockscreenPrecondition} covers the conditions that must be met before Smartspace can be - * used over lockscreen. These conditions include the device being provisioned with a setup user - * and the Smartspace feature flag enabled. + * used over lockscreen. These conditions include the device being provisioned with a setup user. */ class LockscreenPrecondition @Inject constructor( - private val featureFlags: FeatureFlags, private val deviceProvisionedController: DeviceProvisionedController, private val execution: Execution ) : SmartspacePrecondition { @@ -90,6 +86,6 @@ class LockscreenPrecondition @Inject constructor( override fun conditionsMet(): Boolean { execution.assertIsMainThread() - return featureFlags.isEnabled(Flags.SMARTSPACE) && deviceReady + return deviceReady } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java index 87c12c25a0a5..6577cf696b61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java @@ -20,10 +20,21 @@ import android.content.Context; import android.util.AttributeSet; import android.widget.Button; +import com.android.systemui.animation.LaunchableView; +import com.android.systemui.animation.LaunchableViewDelegate; + +import kotlin.Unit; + /** * A Button which doesn't have overlapping drawing commands */ -public class AlphaOptimizedButton extends Button { +public class AlphaOptimizedButton extends Button implements LaunchableView { + private LaunchableViewDelegate mDelegate = new LaunchableViewDelegate(this, + (visibility) -> { + super.setVisibility(visibility); + return Unit.INSTANCE; + }); + public AlphaOptimizedButton(Context context) { super(context); } @@ -45,4 +56,14 @@ public class AlphaOptimizedButton extends Button { public boolean hasOverlappingRendering() { return false; } + + @Override + public void setShouldBlockVisibilityChanges(boolean block) { + mDelegate.setShouldBlockVisibilityChanges(block); + } + + @Override + public void setVisibility(int visibility) { + mDelegate.setVisibility(visibility); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 17583793701c..f2e729d7967c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -20,7 +20,6 @@ import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.DISABLE_NONE; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import android.annotation.Nullable; @@ -38,7 +37,6 @@ import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; -import android.hardware.display.DisplayManager; import android.hardware.fingerprint.IUdfpsHbmListener; import android.inputmethodservice.InputMethodService.BackDispositionMode; import android.media.INearbyMediaDevicesProvider; @@ -46,6 +44,7 @@ import android.media.MediaRoute2Info; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -60,6 +59,7 @@ import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.IAddTileResultCallback; @@ -70,6 +70,7 @@ import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.GcUtils; import com.android.internal.view.AppearanceRegion; import com.android.systemui.dump.DumpHandler; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.policy.CallbackController; @@ -89,8 +90,7 @@ import java.util.ArrayList; * are coalesced, note that they are all idempotent. */ public class CommandQueue extends IStatusBar.Stub implements - CallbackController<Callbacks>, - DisplayManager.DisplayListener { + CallbackController<Callbacks> { private static final String TAG = CommandQueue.class.getSimpleName(); private static final int INDEX_MASK = 0xffff; @@ -192,6 +192,7 @@ public class CommandQueue extends IStatusBar.Stub implements private ProtoTracer mProtoTracer; private final @Nullable CommandRegistry mRegistry; private final @Nullable DumpHandler mDumpHandler; + private final @Nullable DisplayTracker mDisplayTracker; /** * These methods are called back on the main thread. @@ -351,7 +352,7 @@ public class CommandQueue extends IStatusBar.Stub implements } /** - * @see DisplayManager.DisplayListener#onDisplayRemoved(int) + * @see DisplayTracker.Callback#onDisplayRemoved(int) */ default void onDisplayRemoved(int displayId) { } @@ -500,12 +501,14 @@ public class CommandQueue extends IStatusBar.Stub implements default void showMediaOutputSwitcher(String packageName) {} } - public CommandQueue(Context context) { - this(context, null, null, null); + @VisibleForTesting + public CommandQueue(Context context, DisplayTracker displayTracker) { + this(context, displayTracker, null, null, null); } public CommandQueue( Context context, + DisplayTracker displayTracker, ProtoTracer protoTracer, CommandRegistry registry, DumpHandler dumpHandler @@ -513,33 +516,28 @@ public class CommandQueue extends IStatusBar.Stub implements mProtoTracer = protoTracer; mRegistry = registry; mDumpHandler = dumpHandler; - context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler); + mDisplayTracker = displayTracker; + mDisplayTracker.addDisplayChangeCallback(new DisplayTracker.Callback() { + @Override + public void onDisplayRemoved(int displayId) { + synchronized (mLock) { + mDisplayDisabled.remove(displayId); + } + // This callback is registered with {@link #mHandler} that already posts to run + // on main thread, so it is safe to dispatch directly. + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + mCallbacks.get(i).onDisplayRemoved(displayId); + } + } + }, new HandlerExecutor(mHandler)); // We always have default display. - setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE); + setDisabled(mDisplayTracker.getDefaultDisplayId(), DISABLE_NONE, DISABLE2_NONE); } - @Override - public void onDisplayAdded(int displayId) { } - - @Override - public void onDisplayRemoved(int displayId) { - synchronized (mLock) { - mDisplayDisabled.remove(displayId); - } - // This callback is registered with {@link #mHandler} that already posts to run on main - // thread, so it is safe to dispatch directly. - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).onDisplayRemoved(displayId); - } - } - - @Override - public void onDisplayChanged(int displayId) { } - // TODO(b/118592525): add multi-display support if needed. public boolean panelsEnabled() { - final int disabled1 = getDisabled1(DEFAULT_DISPLAY); - final int disabled2 = getDisabled2(DEFAULT_DISPLAY); + final int disabled1 = getDisabled1(mDisplayTracker.getDefaultDisplayId()); + final int disabled2 = getDisabled2(mDisplayTracker.getDefaultDisplayId()); return (disabled1 & StatusBarManager.DISABLE_EXPAND) == 0 && (disabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java index 63179dac7b8c..5adb58bca886 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java @@ -77,6 +77,12 @@ public class CrossFadeHelper { */ public static void fadeOut(View view, float fadeOutAmount, boolean remap) { view.animate().cancel(); + + // Don't fade out if already not visible. + if (view.getAlpha() == 0.0f) { + return; + } + if (fadeOutAmount == 1.0f && view.getVisibility() != View.GONE) { view.setVisibility(View.INVISIBLE); } else if (view.getVisibility() == View.INVISIBLE) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 006b5528e7e9..648185503ef6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -43,6 +43,7 @@ import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON; import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; import static com.android.systemui.plugins.log.LogLevel.ERROR; +import android.app.AlarmManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -98,6 +99,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.wakelock.SettableWakeLock; import com.android.systemui.util.wakelock.WakeLock; @@ -127,10 +129,8 @@ public class KeyguardIndicationController { private static final String TAG = "KeyguardIndication"; private static final boolean DEBUG_CHARGING_SPEED = false; - private static final int MSG_HIDE_TRANSIENT = 1; - private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2; - private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3; - private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 4; + private static final int MSG_SHOW_ACTION_TO_UNLOCK = 1; + private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 2; private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300; public static final long DEFAULT_HIDE_DELAY_MS = 3500 + KeyguardIndicationTextView.Y_IN_DURATION; @@ -212,6 +212,11 @@ public class KeyguardIndicationController { }; private boolean mFaceLockedOutThisAuthSession; + // Use AlarmTimeouts to guarantee that the events are handled even if scheduled and + // triggered while the device is asleep + private final AlarmTimeout mHideTransientMessageHandler; + private final AlarmTimeout mHideBiometricMessageHandler; + /** * Creates a new KeyguardIndicationController and registers callbacks. */ @@ -238,7 +243,9 @@ public class KeyguardIndicationController { AccessibilityManager accessibilityManager, FaceHelpMessageDeferral faceHelpMessageDeferral, KeyguardLogger keyguardLogger, - AlternateBouncerInteractor alternateBouncerInteractor) { + AlternateBouncerInteractor alternateBouncerInteractor, + AlarmManager alarmManager + ) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mDevicePolicyManager = devicePolicyManager; @@ -273,17 +280,26 @@ public class KeyguardIndicationController { mHandler = new Handler(mainLooper) { @Override public void handleMessage(Message msg) { - if (msg.what == MSG_HIDE_TRANSIENT) { - hideTransientIndication(); - } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) { + if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) { showActionToUnlock(); - } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) { - hideBiometricMessage(); } else if (msg.what == MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON) { mBiometricErrorMessageToShowOnScreenOn = null; } } }; + + mHideTransientMessageHandler = new AlarmTimeout( + alarmManager, + this::hideTransientIndication, + TAG, + mHandler + ); + mHideBiometricMessageHandler = new AlarmTimeout( + alarmManager, + this::hideBiometricMessage, + TAG, + mHandler + ); } /** Call this after construction to finish setting up the instance. */ @@ -335,6 +351,8 @@ public class KeyguardIndicationController { */ public void destroy() { mHandler.removeCallbacksAndMessages(null); + mHideBiometricMessageHandler.cancel(); + mHideTransientMessageHandler.cancel(); mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); } @@ -679,7 +697,7 @@ public class KeyguardIndicationController { if (visible) { // If this is called after an error message was already shown, we should not clear it. // Otherwise the error message won't be shown - if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) { + if (!mHideTransientMessageHandler.isScheduled()) { hideTransientIndication(); } updateDeviceEntryIndication(false); @@ -727,16 +745,14 @@ public class KeyguardIndicationController { * Hides transient indication in {@param delayMs}. */ public void hideTransientIndicationDelayed(long delayMs) { - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_HIDE_TRANSIENT), delayMs); + mHideTransientMessageHandler.schedule(delayMs, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED); } /** * Hides biometric indication in {@param delayMs}. */ public void hideBiometricMessageDelayed(long delayMs) { - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_HIDE_BIOMETRIC_MESSAGE), delayMs); + mHideBiometricMessageHandler.schedule(delayMs, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED); } /** @@ -751,7 +767,6 @@ public class KeyguardIndicationController { */ private void showTransientIndication(CharSequence transientIndication) { mTransientIndication = transientIndication; - mHandler.removeMessages(MSG_HIDE_TRANSIENT); hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS); updateTransient(); @@ -777,7 +792,6 @@ public class KeyguardIndicationController { mBiometricMessageFollowUp = biometricMessageFollowUp; mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK); - mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE); hideBiometricMessageDelayed( mBiometricMessageFollowUp != null ? IMPORTANT_MSG_MIN_DURATION * 2 @@ -791,7 +805,7 @@ public class KeyguardIndicationController { if (mBiometricMessage != null || mBiometricMessageFollowUp != null) { mBiometricMessage = null; mBiometricMessageFollowUp = null; - mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE); + mHideBiometricMessageHandler.cancel(); updateBiometricMessage(); } } @@ -802,7 +816,7 @@ public class KeyguardIndicationController { public void hideTransientIndication() { if (mTransientIndication != null) { mTransientIndication = null; - mHandler.removeMessages(MSG_HIDE_TRANSIENT); + mHideTransientMessageHandler.cancel(); updateTransient(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index 0b1807dd2d70..2ca0b0054bf7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -143,6 +143,9 @@ public interface NotificationShadeWindowController extends RemoteInputController /** Sets the state of whether sysui is dozing or not. */ default void setDozing(boolean dozing) {} + /** Sets the state of whether sysui is dreaming or not. */ + default void setDreaming(boolean dreaming) {} + /** Sets the state of whether plugin open is forced or not. */ default void setForcePluginOpen(boolean forcePluginOpen, Object token) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 186e6dc92f93..784e2d153be1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -129,6 +129,11 @@ public class StatusBarStateControllerImpl implements private boolean mIsDozing; /** + * If the device is currently dreaming or not. + */ + private boolean mIsDreaming; + + /** * If the status bar is currently expanded or not. */ private boolean mIsExpanded; @@ -294,6 +299,29 @@ public class StatusBarStateControllerImpl implements } @Override + public boolean setIsDreaming(boolean isDreaming) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "setIsDreaming:" + isDreaming); + } + if (mIsDreaming == isDreaming) { + return false; + } + + mIsDreaming = isDreaming; + + synchronized (mListeners) { + String tag = getClass().getSimpleName() + "#setIsDreaming"; + DejankUtils.startDetectingBlockingIpcs(tag); + for (RankedListener rl : new ArrayList<>(mListeners)) { + rl.mListener.onDreamingChanged(isDreaming); + } + DejankUtils.stopDetectingBlockingIpcs(tag); + } + + return true; + } + + @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/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java index e0cf812a11e3..088c56850d25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java @@ -99,6 +99,13 @@ public interface SysuiStatusBarStateController extends StatusBarStateController boolean setIsDozing(boolean isDozing); /** + * Update the dreaming state from {@link CentralSurfaces}'s perspective + * @param isDreaming whether we are dreaming + * @return {@code true} if the state changed, else {@code false} + */ + boolean setIsDreaming(boolean isDreaming); + + /** * 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/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java index f960eb7b6e9b..ed32008526d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java @@ -47,8 +47,6 @@ public interface NetworkController extends CallbackController<SignalCallback>, D void addEmergencyListener(EmergencyListener listener); /** */ void removeEmergencyListener(EmergencyListener listener); - /** */ - boolean hasEmergencyCryptKeeperText(); /** */ boolean isRadioOn(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index 5f5418f5eeb9..6e74542691a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -84,7 +84,6 @@ import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DataSaverControllerImpl; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; -import com.android.systemui.statusbar.policy.EncryptionHelper; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.CarrierConfigTracker; @@ -1486,11 +1485,6 @@ public class NetworkControllerImpl extends BroadcastReceiver } /** */ - public boolean hasEmergencyCryptKeeperText() { - return EncryptionHelper.IS_DATA_ENCRYPTED; - } - - /** */ public boolean isRadioOn() { return !mAirplaneMode; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 9a65e342478e..d7568a9bd89c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -25,17 +25,21 @@ import android.util.Log; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.AnimationFeatureFlags; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.carrier.QSCarrierGroupController; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.MediaArtworkProcessor; @@ -181,11 +185,12 @@ public interface CentralSurfacesDependenciesModule { @SysUISingleton static CommandQueue provideCommandQueue( Context context, + DisplayTracker displayTracker, ProtoTracer protoTracer, CommandRegistry registry, DumpHandler dumpHandler ) { - return new CommandQueue(context, protoTracer, registry, dumpHandler); + return new CommandQueue(context, displayTracker, protoTracer, registry, dumpHandler); } /** @@ -281,7 +286,8 @@ public interface CentralSurfacesDependenciesModule { static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager, KeyguardStateController keyguardStateController, Lazy<AlternateBouncerInteractor> alternateBouncerInteractor, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + AnimationFeatureFlags animationFeatureFlags) { DialogLaunchAnimator.Callback callback = new DialogLaunchAnimator.Callback() { @Override public boolean isDreaming() { @@ -303,6 +309,19 @@ public interface CentralSurfacesDependenciesModule { return alternateBouncerInteractor.get().canShowAlternateBouncerForFingerprint(); } }; - return new DialogLaunchAnimator(callback, interactionJankMonitor); + return new DialogLaunchAnimator(callback, interactionJankMonitor, animationFeatureFlags); + } + + /** + */ + @Provides + @SysUISingleton + static AnimationFeatureFlags provideAnimationFeatureFlags(FeatureFlags featureFlags) { + return new AnimationFeatureFlags() { + @Override + public boolean isPredictiveBackQsDialogAnim() { + return featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM); + } + }; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt index 3a4731a5a6aa..92a8356b7f07 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt @@ -20,9 +20,9 @@ package com.android.systemui.statusbar.gesture import android.annotation.CallSuper import android.os.Looper import android.view.Choreographer -import android.view.Display import android.view.InputEvent import android.view.MotionEvent +import com.android.systemui.settings.DisplayTracker import com.android.systemui.shared.system.InputChannelCompat import com.android.systemui.shared.system.InputMonitorCompat @@ -38,7 +38,8 @@ import com.android.systemui.shared.system.InputMonitorCompat * gesture is detected, they should call [onGestureDetected] (which will notify the callbacks). */ abstract class GenericGestureDetector( - private val tag: String + private val tag: String, + private val displayTracker: DisplayTracker ) { /** * Active callbacks, each associated with a tag. Gestures will only be monitored if @@ -86,7 +87,7 @@ abstract class GenericGestureDetector( internal open fun startGestureListening() { stopGestureListening() - inputMonitor = InputMonitorCompat(tag, Display.DEFAULT_DISPLAY).also { + inputMonitor = InputMonitorCompat(tag, displayTracker.defaultDisplayId).also { inputReceiver = it.getInputReceiver( Looper.getMainLooper(), Choreographer.getInstance(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt index 5ab3d7ce9bec..693ae6617feb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.gesture import android.content.Context import android.view.MotionEvent import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.settings.DisplayTracker import com.android.systemui.statusbar.window.StatusBarWindowController import javax.inject.Inject @@ -28,9 +29,10 @@ class SwipeStatusBarAwayGestureHandler @Inject constructor( context: Context, + displayTracker: DisplayTracker, logger: SwipeUpGestureLogger, private val statusBarWindowController: StatusBarWindowController, -) : SwipeUpGestureHandler(context, logger, loggerTag = LOGGER_TAG) { +) : SwipeUpGestureHandler(context, displayTracker, logger, loggerTag = LOGGER_TAG) { override fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean { // Gesture starts just below the status bar return ev.y >= statusBarWindowController.statusBarHeight && diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt index 5ecc35ca4576..452762d185a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt @@ -24,6 +24,7 @@ import android.view.MotionEvent.ACTION_DOWN import android.view.MotionEvent.ACTION_MOVE import android.view.MotionEvent.ACTION_UP import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.settings.DisplayTracker /** * A class to detect a generic "swipe up" gesture. To be notified when the swipe up gesture is @@ -32,9 +33,10 @@ import com.android.systemui.dagger.SysUISingleton @SysUISingleton abstract class SwipeUpGestureHandler( context: Context, + displayTracker: DisplayTracker, private val logger: SwipeUpGestureLogger, - private val loggerTag: String, -) : GenericGestureDetector(SwipeUpGestureHandler::class.simpleName!!) { + private val loggerTag: String +) : GenericGestureDetector(SwipeUpGestureHandler::class.simpleName!!, displayTracker) { private var startY: Float = 0f private var startTime: Long = 0L diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt index 7ffb07aa77d0..a901d5979576 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt @@ -21,6 +21,7 @@ import android.view.GestureDetector import android.view.InputEvent import android.view.MotionEvent import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.settings.DisplayTracker import javax.inject.Inject /** @@ -29,8 +30,9 @@ import javax.inject.Inject */ @SysUISingleton class TapGestureDetector @Inject constructor( - private val context: Context -) : GenericGestureDetector(TapGestureDetector::class.simpleName!!) { + private val context: Context, + displayTracker: DisplayTracker +) : GenericGestureDetector(TapGestureDetector::class.simpleName!!, displayTracker) { private val gestureListener = object : GestureDetector.SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { 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 284973976003..f4ca9ccee218 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -52,6 +52,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.shared.regionsampling.UpdateColorCallback +import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -60,6 +61,7 @@ import com.android.systemui.util.settings.SecureSettings import java.util.Optional import java.util.concurrent.Executor import javax.inject.Inject +import javax.inject.Named /** Controller for managing the smartspace view on the lockscreen */ @SysUISingleton @@ -82,6 +84,8 @@ constructor( @Main private val uiExecutor: Executor, @Background private val bgExecutor: Executor, @Main private val handler: Handler, + @Named(WEATHER_SMARTSPACE_DATA_PLUGIN) + optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>, optionalPlugin: Optional<BcSmartspaceDataPlugin>, optionalConfigPlugin: Optional<BcSmartspaceConfigPlugin>, ) { @@ -90,6 +94,7 @@ constructor( } private var session: SmartspaceSession? = null + private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null) private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null) @@ -100,7 +105,7 @@ constructor( private val regionSamplingEnabled = featureFlags.isEnabled(Flags.REGION_SAMPLING) - + private var isContentUpdatedOnce = false private var showNotifications = false private var showSensitiveContentForCurrentUser = false private var showSensitiveContentForManagedUser = false @@ -114,19 +119,6 @@ constructor( override fun onViewAttachedToWindow(v: View) { smartspaceViews.add(v as SmartspaceView) - if (regionSamplingEnabled) { - var regionSampler = RegionSampler( - v, - uiExecutor, - bgExecutor, - regionSamplingEnabled, - updateFun - ) - initializeTextColors(regionSampler) - regionSampler.startRegionSampler() - regionSamplers.put(v, regionSampler) - } - connectSession() updateTextColorFromWallpaper() @@ -136,12 +128,6 @@ constructor( override fun onViewDetachedFromWindow(v: View) { smartspaceViews.remove(v as SmartspaceView) - if (regionSamplingEnabled) { - var regionSampler = regionSamplers.getValue(v) - regionSampler.stopRegionSampler() - regionSamplers.remove(v) - } - if (smartspaceViews.isEmpty()) { disconnect() } @@ -150,8 +136,30 @@ constructor( private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets -> execution.assertIsMainThread() + + // The weather data plugin takes unfiltered targets and performs the filtering internally. + weatherPlugin?.onTargetsAvailable(targets) + val filteredTargets = targets.filter(::filterSmartspaceTarget) plugin?.onTargetsAvailable(filteredTargets) + if (!isContentUpdatedOnce) { + for (v in smartspaceViews) { + if (regionSamplingEnabled) { + var regionSampler = RegionSampler( + v as View, + uiExecutor, + bgExecutor, + regionSamplingEnabled, + updateFun + ) + initializeTextColors(regionSampler) + regionSamplers[v] = regionSampler + regionSampler.startRegionSampler() + } + updateTextColorFromWallpaper() + } + isContentUpdatedOnce = true + } } private val userTrackerCallback = object : UserTracker.Callback { @@ -207,7 +215,14 @@ constructor( fun isEnabled(): Boolean { execution.assertIsMainThread() - return featureFlags.isEnabled(Flags.SMARTSPACE) && plugin != null + return plugin != null + } + + fun isDateWeatherDecoupled(): Boolean { + execution.assertIsMainThread() + + return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) && + weatherPlugin != null } private fun updateBypassEnabled() { @@ -216,26 +231,45 @@ constructor( } /** - * Constructs the smartspace view and connects it to the smartspace service. + * Constructs the weather view and connects it to the smartspace service. */ - fun buildAndConnectView(parent: ViewGroup): View? { + fun buildAndConnectWeatherView(parent: ViewGroup): View? { execution.assertIsMainThread() if (!isEnabled()) { throw RuntimeException("Cannot build view when not enabled") } + if (!isDateWeatherDecoupled()) { + throw RuntimeException("Cannot build weather view when not decoupled") + } - val view = buildView(parent) + val view = buildView(parent, weatherPlugin) connectSession() return view } - fun requestSmartspaceUpdate() { - session?.requestSmartspaceUpdate() + /** + * Constructs the smartspace view and connects it to the smartspace service. + */ + fun buildAndConnectView(parent: ViewGroup): View? { + execution.assertIsMainThread() + + if (!isEnabled()) { + throw RuntimeException("Cannot build view when not enabled") + } + + val view = buildView(parent, plugin, configPlugin) + connectSession() + + return view } - private fun buildView(parent: ViewGroup): View? { + private fun buildView( + parent: ViewGroup, + plugin: BcSmartspaceDataPlugin?, + configPlugin: BcSmartspaceConfigPlugin? = null + ): View? { if (plugin == null) { return null } @@ -243,7 +277,7 @@ constructor( val ssView = plugin.getView(parent) ssView.setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) ssView.registerDataProvider(plugin) - ssView.registerConfigProvider(configPlugin) + configPlugin?.let { ssView.registerConfigProvider(it) } ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter { override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) { @@ -274,7 +308,8 @@ constructor( } private fun connectSession() { - if (plugin == null || session != null || smartspaceViews.isEmpty()) { + if (weatherPlugin == null && plugin == null) return + if (session != null || smartspaceViews.isEmpty()) { return } @@ -311,15 +346,21 @@ constructor( statusBarStateController.addCallback(statusBarStateListener) bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener) - plugin.registerSmartspaceEventNotifier { - e -> session?.notifySmartspaceEvent(e) - } + weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } + plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } updateBypassEnabled() reloadSmartspace() } /** + * Requests the smartspace session for an update. + */ + fun requestSmartspaceUpdate() { + session?.requestSmartspaceUpdate() + } + + /** * Disconnects the smartspace view from the smartspace service and cleans up any resources. */ fun disconnect() { @@ -342,9 +383,13 @@ constructor( bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener) session = null + weatherPlugin?.registerSmartspaceEventNotifier(null) + weatherPlugin?.onTargetsAvailable(emptyList()) + plugin?.registerSmartspaceEventNotifier(null) plugin?.onTargetsAvailable(emptyList()) - Log.d(TAG, "Ending smartspace session for lockscreen") + + Log.d(TAG, "Ended smartspace session for lockscreen") } fun addListener(listener: SmartspaceTargetListener) { @@ -358,8 +403,11 @@ constructor( } private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { + if (isDateWeatherDecoupled()) { + return t.featureType != SmartspaceTarget.FEATURE_WEATHER + } if (!showNotifications) { - return t.getFeatureType() == SmartspaceTarget.FEATURE_WEATHER + return t.featureType == SmartspaceTarget.FEATURE_WEATHER } return when (t.userHandle) { userTracker.userHandle -> { @@ -398,7 +446,8 @@ constructor( private fun updateTextColorFromWallpaper() { val wallpaperManager = WallpaperManager.getInstance(context) - if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists()) { + if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists() || + regionSamplers.isEmpty()) { val wallpaperTextColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor) smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt index 635ed7c16140..48567592c43e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt @@ -32,8 +32,6 @@ class NotifPipelineFlags @Inject constructor( fun isDevLoggingEnabled(): Boolean = featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING) - fun isSmartspaceDedupingEnabled(): Boolean = featureFlags.isEnabled(Flags.SMARTSPACE) - fun fullScreenIntentRequiresKeyguard(): Boolean = featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index aeae89cb3223..7e53d5431353 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.phone.KeyguardBypassController.OnBypassStateChangedListener import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener @@ -38,7 +39,6 @@ import java.io.PrintWriter import javax.inject.Inject import kotlin.math.min - @SysUISingleton class NotificationWakeUpCoordinator @Inject constructor( dumpManager: DumpManager, @@ -68,6 +68,7 @@ class NotificationWakeUpCoordinator @Inject constructor( private var mLinearDozeAmount: Float = 0.0f private var mDozeAmount: Float = 0.0f private var mDozeAmountSource: String = "init" + private var mNotifsHiddenByDozeAmountOverride: Boolean = false private var mNotificationVisibleAmount = 0.0f private var mNotificationsVisible = false private var mNotificationsVisibleForExpansion = false @@ -130,6 +131,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } } } + /** * True if we can show pulsing heads up notifications */ @@ -149,10 +151,19 @@ class NotificationWakeUpCoordinator @Inject constructor( return canShow } + private val bypassStateChangedListener = object : OnBypassStateChangedListener { + override fun onBypassStateChanged(isEnabled: Boolean) { + // When the bypass state changes, we have to check whether we should re-show the + // notifications by clearing the doze amount override which hides them. + maybeClearDozeAmountOverrideHidingNotifs() + } + } + init { dumpManager.registerDumpable(this) mHeadsUpManager.addListener(this) statusBarStateController.addCallback(this) + bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener) addListener(object : WakeUpListener { override fun onFullyHiddenChanged(isFullyHidden: Boolean) { if (isFullyHidden && mNotificationsVisibleForExpansion) { @@ -261,12 +272,18 @@ class NotificationWakeUpCoordinator @Inject constructor( setDozeAmount(linear, eased, source = "StatusBar") } - fun setDozeAmount(linear: Float, eased: Float, source: String) { + fun setDozeAmount( + linear: Float, + eased: Float, + source: String, + hidesNotifsByOverride: Boolean = false + ) { val changed = linear != mLinearDozeAmount logger.logSetDozeAmount(linear, eased, source, statusBarStateController.state, changed) mLinearDozeAmount = linear mDozeAmount = eased mDozeAmountSource = source + mNotifsHiddenByDozeAmountOverride = hidesNotifsByOverride mStackScrollerController.setDozeAmount(mDozeAmount) updateHideAmount() if (changed && linear == 0.0f) { @@ -295,6 +312,8 @@ class NotificationWakeUpCoordinator @Inject constructor( return } + maybeClearDozeAmountOverrideHidingNotifs() + if (bypassController.bypassEnabled && newState == StatusBarState.KEYGUARD && state == StatusBarState.SHADE_LOCKED && (!statusBarStateController.isDozing || shouldAnimateVisibility())) { @@ -325,7 +344,8 @@ class NotificationWakeUpCoordinator @Inject constructor( private fun overrideDozeAmountIfBypass(): Boolean { if (bypassController.bypassEnabled) { if (statusBarStateController.state == StatusBarState.KEYGUARD) { - setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)") + setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)", + hidesNotifsByOverride = true) } else { setDozeAmount(0f, 0f, source = "Override: bypass (shade)") } @@ -335,6 +355,37 @@ class NotificationWakeUpCoordinator @Inject constructor( } /** + * If the last [setDozeAmount] call was an override to hide notifications, then this call will + * check for the set of states that may have caused that override, and if none of them still + * apply, and the device is awake or not on the keyguard, then dozeAmount will be reset to 0. + * This fixes bugs where the bypass state changing could result in stale overrides, hiding + * notifications either on the inside screen or even after unlock. + */ + private fun maybeClearDozeAmountOverrideHidingNotifs() { + if (mNotifsHiddenByDozeAmountOverride) { + val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD + val dozing = statusBarStateController.isDozing + val bypass = bypassController.bypassEnabled + val animating = + screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard() + // Overrides are set by [overrideDozeAmountIfAnimatingScreenOff] and + // [overrideDozeAmountIfBypass] based on 'animating' and 'bypass' respectively, so only + // clear the override if both those conditions are cleared. But also require either + // !dozing or !onKeyguard because those conditions should indicate that we intend + // notifications to be visible, and thus it is safe to unhide them. + val willRemove = (!onKeyguard || !dozing) && !bypass && !animating + logger.logMaybeClearDozeAmountOverrideHidingNotifs( + willRemove = willRemove, + onKeyguard = onKeyguard, dozing = dozing, + bypass = bypass, animating = animating, + ) + if (willRemove) { + setDozeAmount(0f, 0f, source = "Removed: $mDozeAmountSource") + } + } + } + + /** * If we're playing the screen off animation, force the notification doze amount to be 1f (fully * dozing). This is needed so that the notifications aren't briefly visible as the screen turns * off and dozeAmount goes from 1f to 0f. @@ -344,7 +395,8 @@ class NotificationWakeUpCoordinator @Inject constructor( */ private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean { if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) { - setDozeAmount(1f, 1f, source = "Override: animating screen off") + setDozeAmount(1f, 1f, source = "Override: animating screen off", + hidesNotifsByOverride = true) return true } @@ -430,6 +482,7 @@ class NotificationWakeUpCoordinator @Inject constructor( pw.println("mLinearDozeAmount: $mLinearDozeAmount") pw.println("mDozeAmount: $mDozeAmount") pw.println("mDozeAmountSource: $mDozeAmountSource") + pw.println("mNotifsHiddenByDozeAmountOverride: $mNotifsHiddenByDozeAmountOverride") pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount") pw.println("mNotificationsVisible: $mNotificationsVisible") pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt index de18b0c4307d..44645315ca80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt @@ -46,6 +46,25 @@ constructor(@NotificationLog private val buffer: LogBuffer) { ) } + fun logMaybeClearDozeAmountOverrideHidingNotifs( + willRemove: Boolean, + onKeyguard: Boolean, + dozing: Boolean, + bypass: Boolean, + animating: Boolean, + ) { + buffer.log( + TAG, + DEBUG, + { + str1 = + "willRemove=$willRemove onKeyguard=$onKeyguard dozing=$dozing" + + " bypass=$bypass animating=$animating" + }, + { "maybeClearDozeAmountOverrideHidingNotifs() $str1" } + ) + } + fun logOnDozeAmountChanged(linear: Float, eased: Float) { buffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 1399385e7654..03a3ca55083f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -88,9 +88,7 @@ class NotifCoordinatorsImpl @Inject constructor( mCoordinators.add(viewConfigCoordinator) mCoordinators.add(visualStabilityCoordinator) mCoordinators.add(sensitiveContentCoordinator) - if (notifPipelineFlags.isSmartspaceDedupingEnabled()) { - mCoordinators.add(smartspaceDedupingCoordinator) - } + mCoordinators.add(smartspaceDedupingCoordinator) mCoordinators.add(headsUpCoordinator) mCoordinators.add(gutsCoordinator) mCoordinators.add(preparationCoordinator) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 808638a99dfa..8436ff73c628 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -16,27 +16,15 @@ package com.android.systemui.statusbar.notification.dagger; -import android.app.INotificationManager; import android.content.Context; -import android.content.pm.LauncherApps; -import android.content.pm.ShortcutManager; -import android.os.Handler; -import android.view.accessibility.AccessibilityManager; -import com.android.internal.logging.UiEventLogger; 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.dagger.qualifiers.UiBackground; -import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.settings.UserContextProvider; -import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeEventsModule; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.NotificationListener; -import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; @@ -50,7 +38,6 @@ import com.android.systemui.statusbar.notification.collection.inflation.BindEven import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; -import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl; import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderModule; import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator; @@ -72,22 +59,17 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger; import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl; -import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; -import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.wmshell.BubblesManager; -import java.util.Optional; import java.util.concurrent.Executor; import javax.inject.Provider; import dagger.Binds; -import dagger.Lazy; import dagger.Module; import dagger.Provides; @@ -109,48 +91,6 @@ public interface NotificationsModule { @Binds StackScrollAlgorithm.BypassController bindBypassController(KeyguardBypassController impl); - /** Provides an instance of {@link NotificationGutsManager} */ - @SysUISingleton - @Provides - static NotificationGutsManager provideNotificationGutsManager( - Context context, - Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, - @Main Handler mainHandler, - @Background Handler bgHandler, - AccessibilityManager accessibilityManager, - HighPriorityProvider highPriorityProvider, - INotificationManager notificationManager, - PeopleSpaceWidgetManager peopleSpaceWidgetManager, - LauncherApps launcherApps, - ShortcutManager shortcutManager, - ChannelEditorDialogController channelEditorDialogController, - UserContextProvider contextTracker, - AssistantFeedbackController assistantFeedbackController, - Optional<BubblesManager> bubblesManagerOptional, - UiEventLogger uiEventLogger, - OnUserInteractionCallback onUserInteractionCallback, - ShadeController shadeController) { - return new NotificationGutsManager( - context, - centralSurfacesOptionalLazy, - mainHandler, - bgHandler, - accessibilityManager, - highPriorityProvider, - notificationManager, - peopleSpaceWidgetManager, - launcherApps, - shortcutManager, - channelEditorDialogController, - contextTracker, - assistantFeedbackController, - bubblesManagerOptional, - uiEventLogger, - onUserInteractionCallback, - shadeController - ); - } - /** Provides an instance of {@link NotifGutsViewManager} */ @Binds NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager); 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 9275e2b603c3..a6b71dc3e54d 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 @@ -591,6 +591,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } mShowingPublicInitialized = false; updateNotificationColor(); + updateLongClickable(); if (mMenuRow != null) { mMenuRow.onNotificationUpdated(mEntry.getSbn()); mMenuRow.setAppName(mAppName); @@ -1196,8 +1197,26 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return getShowingLayout().getVisibleWrapper(); } + private boolean isNotificationRowLongClickable() { + if (mLongPressListener == null) { + return false; + } + + if (!areGutsExposed()) { // guts is not opened + return true; + } + + // if it is leave behind, it shouldn't be long clickable. + return !isGutsLeaveBehind(); + } + + private void updateLongClickable() { + setLongClickable(isNotificationRowLongClickable()); + } + public void setLongPressListener(LongPressListener longPressListener) { mLongPressListener = longPressListener; + updateLongClickable(); } public void setDragController(ExpandableNotificationRowDragController dragController) { @@ -2044,11 +2063,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView void onGutsOpened() { resetTranslation(); updateContentAccessibilityImportanceForGuts(false /* isEnabled */); + updateLongClickable(); } void onGutsClosed() { updateContentAccessibilityImportanceForGuts(true /* isEnabled */); mIsSnoozed = false; + updateLongClickable(); } /** @@ -2947,6 +2968,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return (mGuts != null && mGuts.isExposed()); } + private boolean isGutsLeaveBehind() { + return (mGuts != null && mGuts.isLeavebehind()); + } + @Override public boolean isContentExpandable() { if (mIsSummaryWithChildren && !shouldShowPublic()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index ea12b8263fed..efcbb3cd9655 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -44,12 +44,10 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.settingslib.notification.ConversationIconFactory; -import com.android.systemui.Dependency; -import com.android.systemui.Dumpable; 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.dump.DumpManager; import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -65,28 +63,29 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener; import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; -import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.wmshell.BubblesManager; -import java.io.PrintWriter; import java.util.Optional; +import javax.inject.Inject; + import dagger.Lazy; /** * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and * closing guts, and keeping track of the currently exposed notification guts. */ +@SysUISingleton public class NotificationGutsManager implements NotifGutsViewManager { private static final String TAG = "NotificationGutsManager"; // Must match constant in Settings. Used to highlight preferences when linking to Settings. private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; - private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); + private final MetricsLogger mMetricsLogger; private final Context mContext; private final AccessibilityManager mAccessibilityManager; private final HighPriorityProvider mHighPriorityProvider; @@ -94,12 +93,9 @@ public class NotificationGutsManager implements NotifGutsViewManager { private final OnUserInteractionCallback mOnUserInteractionCallback; // Dependencies: - private final NotificationLockscreenUserManager mLockscreenUserManager = - Dependency.get(NotificationLockscreenUserManager.class); - private final StatusBarStateController mStatusBarStateController = - Dependency.get(StatusBarStateController.class); - private final DeviceProvisionedController mDeviceProvisionedController = - Dependency.get(DeviceProvisionedController.class); + private final NotificationLockscreenUserManager mLockscreenUserManager; + private final StatusBarStateController mStatusBarStateController; + private final DeviceProvisionedController mDeviceProvisionedController; private final AssistantFeedbackController mAssistantFeedbackController; // which notification is currently being longpress-examined by the user @@ -124,9 +120,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { private final ShadeController mShadeController; private NotifGutsViewListener mGutsListener; - /** - * Injected constructor. See {@link NotificationsModule}. - */ + @Inject public NotificationGutsManager(Context context, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, @Main Handler mainHandler, @@ -143,7 +137,11 @@ public class NotificationGutsManager implements NotifGutsViewManager { Optional<BubblesManager> bubblesManagerOptional, UiEventLogger uiEventLogger, OnUserInteractionCallback onUserInteractionCallback, - ShadeController shadeController) { + ShadeController shadeController, + NotificationLockscreenUserManager notificationLockscreenUserManager, + StatusBarStateController statusBarStateController, + DeviceProvisionedController deviceProvisionedController, + MetricsLogger metricsLogger) { mContext = context; mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; mMainHandler = mainHandler; @@ -161,6 +159,10 @@ public class NotificationGutsManager implements NotifGutsViewManager { mUiEventLogger = uiEventLogger; mOnUserInteractionCallback = onUserInteractionCallback; mShadeController = shadeController; + mLockscreenUserManager = notificationLockscreenUserManager; + mStatusBarStateController = statusBarStateController; + mDeviceProvisionedController = deviceProvisionedController; + mMetricsLogger = metricsLogger; } public void setUpWithPresenter(NotificationPresenter presenter, @@ -372,7 +374,8 @@ public class NotificationGutsManager implements NotifGutsViewManager { mDeviceProvisionedController.isDeviceProvisioned(), row.getIsNonblockable(), mHighPriorityProvider.isHighPriority(row.getEntry()), - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); } /** @@ -583,7 +586,9 @@ public class NotificationGutsManager implements NotifGutsViewManager { } final ExpandableNotificationRow row = (ExpandableNotificationRow) view; - view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + if (view.isLongClickable()) { + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } if (row.areGutsExposed()) { closeAndSaveGuts(false /* removeLeavebehind */, false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index ea0060a693b2..8a50f2f527fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -204,10 +204,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G boolean isDeviceProvisioned, boolean isNonblockable, boolean wasShownHighPriority, - AssistantFeedbackController assistantFeedbackController) + AssistantFeedbackController assistantFeedbackController, + MetricsLogger metricsLogger) throws RemoteException { mINotificationManager = iNotificationManager; - mMetricsLogger = Dependency.get(MetricsLogger.class); + mMetricsLogger = metricsLogger; mOnUserInteractionCallback = onUserInteractionCallback; mChannelEditorDialogController = channelEditorDialogController; mAssistantFeedbackController = assistantFeedbackController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 149ec545dfa7..8b1a02b05e0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -279,7 +279,8 @@ public class AutoTileManager implements UserAwareController { public void onManagedProfileChanged() { if (mManagedProfileController.hasActiveProfile()) { if (mAutoTracker.isAdded(WORK)) return; - mHost.addTile(WORK); + final int position = mAutoTracker.getRestoredTilePosition(WORK); + mHost.addTile(WORK, position); mAutoTracker.setTileAdded(WORK); } else { if (!mAutoTracker.isAdded(WORK)) return; 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 dd75d356a60b..85399ca22d96 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1306,8 +1306,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Set up the quick settings tile panel final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame); if (container != null) { - FragmentHostManager fragmentHostManager = FragmentHostManager.get(container); - ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame, + FragmentHostManager fragmentHostManager = + mFragmentService.getFragmentHostManager(container); + ExtensionFragmentListener.attachExtensonToFragment( + mFragmentService, + container, + QS.TAG, + R.id.qs_frame, mExtensionController .newExtension(QS.class) .withPlugin(QS.class) @@ -1408,12 +1413,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // to dismiss the lock screen until entering the SIM PIN. // - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the // keyguard. + // - Shade is in QQS over keyguard - swiping up should take us back to keyguard if (!isKeyguardShowing() || mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing() || isOccluded() || !mKeyguardStateController.canDismissLockScreen() || mKeyguardViewMediator.isAnySimPinSecure() - || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) { + || (mNotificationPanelViewController.isQsExpanded() && trackingTouch) + || mNotificationPanelViewController.getBarState() == StatusBarState.SHADE_LOCKED) { return; } @@ -1476,7 +1483,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } protected QS createDefaultQSFragment() { - return FragmentHostManager.get(mNotificationShadeWindowView).create(QSFragment.class); + return mFragmentService + .getFragmentHostManager(mNotificationShadeWindowView) + .create(QSFragment.class); } private void setUpPresenter() { @@ -3770,6 +3779,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }); } else if (mDozing && !unlocking) { mScrimController.transitionTo(ScrimState.AOD); + // This will cancel the keyguardFadingAway animation if it is running. We need to do + // this as otherwise it can remain pending and leave keyguard in a weird state. + mUnlockScrimCallback.onCancelled(); } else if (mKeyguardStateController.isShowing() && !isOccluded() && !unlocking) { mScrimController.transitionTo(ScrimState.KEYGUARD); } else if (mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java deleted file mode 100644 index d31875935dd3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ /dev/null @@ -1,708 +0,0 @@ -/* - * Copyright (C) 2014 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.phone; - -import static com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; -import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; -import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.hardware.biometrics.BiometricSourceType; -import android.os.Handler; -import android.os.Trace; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Log; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowInsets; - -import com.android.internal.policy.SystemBarUtils; -import com.android.keyguard.KeyguardHostViewController; -import com.android.keyguard.KeyguardSecurityModel; -import com.android.keyguard.KeyguardSecurityView; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.keyguard.ViewMediatorCallback; -import com.android.keyguard.dagger.KeyguardBouncerComponent; -import com.android.systemui.DejankUtils; -import com.android.systemui.classifier.FalsingCollector; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.keyguard.DismissCallbackRegistry; -import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.KeyguardResetCallback; -import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; -import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.ListenerSet; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -/** - * A class which manages the primary (pin/pattern/password) bouncer on the lockscreen. - * @deprecated Use KeyguardBouncerRepository - */ -@Deprecated -public class KeyguardBouncer { - - private static final String TAG = "PrimaryKeyguardBouncer"; - static final long BOUNCER_FACE_DELAY = 1200; - public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f; - protected final Context mContext; - protected final ViewMediatorCallback mCallback; - protected final ViewGroup mContainer; - private final FalsingCollector mFalsingCollector; - private final DismissCallbackRegistry mDismissCallbackRegistry; - private final Handler mHandler; - private final List<PrimaryBouncerExpansionCallback> mExpansionCallbacks = new ArrayList<>(); - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final KeyguardStateController mKeyguardStateController; - private final KeyguardSecurityModel mKeyguardSecurityModel; - private final KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; - private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = - new KeyguardUpdateMonitorCallback() { - @Override - public void onStrongAuthStateChanged(int userId) { - mBouncerPromptReason = mCallback.getBouncerPromptReason(); - } - - @Override - public void onLockedOutStateChanged(BiometricSourceType type) { - if (type == BiometricSourceType.FINGERPRINT) { - mBouncerPromptReason = mCallback.getBouncerPromptReason(); - } - } - - @Override - public void onNonStrongBiometricAllowedChanged(int userId) { - mBouncerPromptReason = mCallback.getBouncerPromptReason(); - } - }; - private final Runnable mRemoveViewRunnable = this::removeView; - private final KeyguardBypassController mKeyguardBypassController; - private KeyguardHostViewController mKeyguardViewController; - private final ListenerSet<KeyguardResetCallback> mResetCallbacks = new ListenerSet<>(); - private final Runnable mResetRunnable = ()-> { - if (mKeyguardViewController != null) { - mKeyguardViewController.resetSecurityContainer(); - for (KeyguardResetCallback callback : mResetCallbacks) { - callback.onKeyguardReset(); - } - } - }; - - private int mStatusBarHeight; - private float mExpansion = EXPANSION_HIDDEN; - private boolean mShowingSoon; - private int mBouncerPromptReason; - private boolean mIsAnimatingAway; - private boolean mIsScrimmed; - private boolean mInitialized; - - private KeyguardBouncer(Context context, ViewMediatorCallback callback, - ViewGroup container, - DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector, - PrimaryBouncerExpansionCallback expansionCallback, - KeyguardStateController keyguardStateController, - KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardBypassController keyguardBypassController, @Main Handler handler, - KeyguardSecurityModel keyguardSecurityModel, - KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) { - mContext = context; - mCallback = callback; - mContainer = container; - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mFalsingCollector = falsingCollector; - mDismissCallbackRegistry = dismissCallbackRegistry; - mHandler = handler; - mKeyguardStateController = keyguardStateController; - mKeyguardSecurityModel = keyguardSecurityModel; - mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory; - mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); - mKeyguardBypassController = keyguardBypassController; - mExpansionCallbacks.add(expansionCallback); - } - - /** - * Get the KeyguardBouncer expansion - * @return 1=HIDDEN, 0=SHOWING, in between 0 and 1 means the bouncer is in transition. - */ - public float getExpansion() { - return mExpansion; - } - - /** - * Enable/disable only the back button - */ - public void setBackButtonEnabled(boolean enabled) { - int vis = mContainer.getSystemUiVisibility(); - if (enabled) { - vis &= ~View.STATUS_BAR_DISABLE_BACK; - } else { - vis |= View.STATUS_BAR_DISABLE_BACK; - } - mContainer.setSystemUiVisibility(vis); - } - - public void show(boolean resetSecuritySelection) { - show(resetSecuritySelection, true /* scrimmed */); - } - - /** - * Shows the bouncer. - * - * @param resetSecuritySelection Cleans keyguard view - * @param isScrimmed true when the bouncer show show scrimmed, false when the user will be - * dragging it and translation should be deferred. - */ - public void show(boolean resetSecuritySelection, boolean isScrimmed) { - final int keyguardUserId = KeyguardUpdateMonitor.getCurrentUser(); - if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) { - // In split system user mode, we never unlock system user. - return; - } - - try { - Trace.beginSection("KeyguardBouncer#show"); - - ensureView(); - mIsScrimmed = isScrimmed; - - // On the keyguard, we want to show the bouncer when the user drags up, but it's - // not correct to end the falsing session. We still need to verify if those touches - // are valid. - // Later, at the end of the animation, when the bouncer is at the top of the screen, - // onFullyShown() will be called and FalsingManager will stop recording touches. - if (isScrimmed) { - setExpansion(EXPANSION_VISIBLE); - } - - if (resetSecuritySelection) { - // showPrimarySecurityScreen() updates the current security method. This is needed - // in case we are already showing and the current security method changed. - showPrimarySecurityScreen(); - } - - if (mContainer.getVisibility() == View.VISIBLE || mShowingSoon) { - // Calls to reset must resume the ViewControllers when in fullscreen mode - if (needsFullscreenBouncer()) { - mKeyguardViewController.onResume(); - } - return; - } - - final int activeUserId = KeyguardUpdateMonitor.getCurrentUser(); - final boolean isSystemUser = - UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM; - final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId; - - // If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern) - // is set, this will dismiss the whole Keyguard. Otherwise, show the bouncer. - if (allowDismissKeyguard && mKeyguardViewController.dismiss(activeUserId)) { - return; - } - - // This condition may indicate an error on Android, so log it. - if (!allowDismissKeyguard) { - Log.w(TAG, "User can't dismiss keyguard: " + activeUserId + " != " - + keyguardUserId); - } - - mShowingSoon = true; - - // Split up the work over multiple frames. - DejankUtils.removeCallbacks(mResetRunnable); - if (mKeyguardStateController.isFaceAuthEnabled() - && !mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - KeyguardUpdateMonitor.getCurrentUser()) - && !needsFullscreenBouncer() - && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - BiometricSourceType.FACE) - && !mKeyguardBypassController.getBypassEnabled()) { - mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY); - } else { - DejankUtils.postAfterTraversal(mShowRunnable); - } - - mKeyguardStateController.notifyBouncerShowing(true /* showing */); - dispatchStartingToShow(); - } finally { - Trace.endSection(); - } - } - - public boolean isScrimmed() { - return mIsScrimmed; - } - - /** - * This method must be called at the end of the bouncer animation when - * the translation is performed manually by the user, otherwise FalsingManager - * will never be notified and its internal state will be out of sync. - */ - private void onFullyShown() { - mFalsingCollector.onBouncerShown(); - if (mKeyguardViewController == null) { - Log.e(TAG, "onFullyShown when view was null"); - } else { - mKeyguardViewController.onResume(); - mContainer.announceForAccessibility( - mKeyguardViewController.getAccessibilityTitleForCurrentMode()); - } - } - - /** - * @see #onFullyShown() - */ - private void onFullyHidden() { - - } - - private void setVisibility(@View.Visibility int visibility) { - mContainer.setVisibility(visibility); - if (mKeyguardViewController != null) { - mKeyguardViewController.onBouncerVisibilityChanged(visibility); - } - dispatchVisibilityChanged(); - } - - private final Runnable mShowRunnable = new Runnable() { - @Override - public void run() { - setVisibility(View.VISIBLE); - showPromptReason(mBouncerPromptReason); - final CharSequence customMessage = mCallback.consumeCustomMessage(); - if (customMessage != null) { - mKeyguardViewController.showErrorMessage(customMessage); - } - mKeyguardViewController.appear(mStatusBarHeight); - mShowingSoon = false; - if (mExpansion == EXPANSION_VISIBLE) { - mKeyguardViewController.onResume(); - mKeyguardViewController.resetSecurityContainer(); - showPromptReason(mBouncerPromptReason); - } - } - }; - - /** - * Show a string explaining why the security view needs to be solved. - * - * @param reason a flag indicating which string should be shown, see - * {@link KeyguardSecurityView#PROMPT_REASON_NONE} - * and {@link KeyguardSecurityView#PROMPT_REASON_RESTART} - */ - public void showPromptReason(int reason) { - if (mKeyguardViewController != null) { - mKeyguardViewController.showPromptReason(reason); - } else { - Log.w(TAG, "Trying to show prompt reason on empty bouncer"); - } - } - - public void showMessage(String message, ColorStateList colorState) { - if (mKeyguardViewController != null) { - mKeyguardViewController.showMessage(message, colorState); - } else { - Log.w(TAG, "Trying to show message on empty bouncer"); - } - } - - private void cancelShowRunnable() { - DejankUtils.removeCallbacks(mShowRunnable); - mHandler.removeCallbacks(mShowRunnable); - mShowingSoon = false; - } - - public void showWithDismissAction(OnDismissAction r, Runnable cancelAction) { - ensureView(); - setDismissAction(r, cancelAction); - show(false /* resetSecuritySelection */); - } - - /** - * Set the actions to run when the keyguard is dismissed or when the dismiss is cancelled. Those - * actions will still be run even if this bouncer is not shown, for instance when authenticating - * with an alternate authenticator like the UDFPS. - */ - public void setDismissAction(OnDismissAction r, Runnable cancelAction) { - mKeyguardViewController.setOnDismissAction(r, cancelAction); - } - - public void hide(boolean destroyView) { - Trace.beginSection("KeyguardBouncer#hide"); - if (isShowing()) { - SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, - SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN); - mDismissCallbackRegistry.notifyDismissCancelled(); - } - mIsScrimmed = false; - mFalsingCollector.onBouncerHidden(); - mKeyguardStateController.notifyBouncerShowing(false /* showing */); - cancelShowRunnable(); - if (mKeyguardViewController != null) { - mKeyguardViewController.cancelDismissAction(); - mKeyguardViewController.cleanUp(); - } - mIsAnimatingAway = false; - setVisibility(View.INVISIBLE); - if (destroyView) { - - // We have a ViewFlipper that unregisters a broadcast when being detached, which may - // be slow because of AM lock contention during unlocking. We can delay it a bit. - mHandler.postDelayed(mRemoveViewRunnable, 50); - } - Trace.endSection(); - } - - /** - * See {@link StatusBarKeyguardViewManager#startPreHideAnimation}. - */ - public void startPreHideAnimation(Runnable runnable) { - mIsAnimatingAway = true; - if (mKeyguardViewController != null) { - mKeyguardViewController.startDisappearAnimation(runnable); - } else if (runnable != null) { - runnable.run(); - } - } - - /** - * Reset the state of the view. - */ - public void reset() { - cancelShowRunnable(); - inflateView(); - mFalsingCollector.onBouncerHidden(); - } - - public void onScreenTurnedOff() { - if (mKeyguardViewController != null && mContainer.getVisibility() == View.VISIBLE) { - mKeyguardViewController.onPause(); - } - } - - public boolean isShowing() { - return (mShowingSoon || mContainer.getVisibility() == View.VISIBLE) - && mExpansion == EXPANSION_VISIBLE && !isAnimatingAway(); - } - - /** - * {@link #show(boolean)} was called but we're not showing yet, or being dragged. - */ - public boolean inTransit() { - return mShowingSoon || mExpansion != EXPANSION_HIDDEN && mExpansion != EXPANSION_VISIBLE; - } - - /** - * @return {@code true} when bouncer's pre-hide animation already started but isn't completely - * hidden yet, {@code false} otherwise. - */ - public boolean isAnimatingAway() { - return mIsAnimatingAway; - } - - public void prepare() { - boolean wasInitialized = mInitialized; - ensureView(); - if (wasInitialized) { - showPrimarySecurityScreen(); - } - mBouncerPromptReason = mCallback.getBouncerPromptReason(); - } - - private void showPrimarySecurityScreen() { - mKeyguardViewController.showPrimarySecurityScreen(); - } - - /** - * Current notification panel expansion - * @param fraction 0 when notification panel is collapsed and 1 when expanded. - * @see StatusBarKeyguardViewManager#onPanelExpansionChanged - */ - public void setExpansion(float fraction) { - float oldExpansion = mExpansion; - boolean expansionChanged = mExpansion != fraction; - mExpansion = fraction; - if (mKeyguardViewController != null && !mIsAnimatingAway) { - mKeyguardViewController.setExpansion(fraction); - } - - if (fraction == EXPANSION_VISIBLE && oldExpansion != EXPANSION_VISIBLE) { - onFullyShown(); - dispatchFullyShown(); - } else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) { - DejankUtils.postAfterTraversal(mResetRunnable); - /* - * There are cases where #hide() was not invoked, such as when - * NotificationPanelViewController controls the hide animation. Make sure the state gets - * updated by calling #hide() directly. - */ - hide(false /* destroyView */); - dispatchFullyHidden(); - } else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) { - dispatchStartingToHide(); - if (mKeyguardViewController != null) { - mKeyguardViewController.onStartingToHide(); - } - } - - if (expansionChanged) { - dispatchExpansionChanged(); - } - } - - public boolean willDismissWithAction() { - return mKeyguardViewController != null && mKeyguardViewController.hasDismissActions(); - } - - public int getTop() { - if (mKeyguardViewController == null) { - return 0; - } - - return mKeyguardViewController.getTop(); - } - - protected void ensureView() { - // Removal of the view might be deferred to reduce unlock latency, - // in this case we need to force the removal, otherwise we'll - // end up in an unpredictable state. - boolean forceRemoval = mHandler.hasCallbacks(mRemoveViewRunnable); - if (!mInitialized || forceRemoval) { - inflateView(); - } - } - - protected void inflateView() { - removeView(); - mHandler.removeCallbacks(mRemoveViewRunnable); - - KeyguardBouncerComponent component = mKeyguardBouncerComponentFactory.create(mContainer); - mKeyguardViewController = component.getKeyguardHostViewController(); - mKeyguardViewController.init(); - - mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext); - setVisibility(View.INVISIBLE); - - final WindowInsets rootInsets = mContainer.getRootWindowInsets(); - if (rootInsets != null) { - mContainer.dispatchApplyWindowInsets(rootInsets); - } - mInitialized = true; - } - - protected void removeView() { - mContainer.removeAllViews(); - mInitialized = false; - } - - /** - * @return True if and only if the security method should be shown before showing the - * notifications on Keyguard, like SIM PIN/PUK. - */ - public boolean needsFullscreenBouncer() { - SecurityMode mode = mKeyguardSecurityModel.getSecurityMode( - KeyguardUpdateMonitor.getCurrentUser()); - return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk; - } - - /** - * Like {@link #needsFullscreenBouncer}, but uses the currently visible security method, which - * makes this method much faster. - */ - public boolean isFullscreenBouncer() { - if (mKeyguardViewController != null) { - SecurityMode mode = mKeyguardViewController.getCurrentSecurityMode(); - return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk; - } - return false; - } - - /** - * WARNING: This method might cause Binder calls. - */ - public boolean isSecure() { - return mKeyguardSecurityModel.getSecurityMode( - KeyguardUpdateMonitor.getCurrentUser()) != SecurityMode.None; - } - - public boolean shouldDismissOnMenuPressed() { - return mKeyguardViewController.shouldEnableMenuKey(); - } - - public boolean interceptMediaKey(KeyEvent event) { - ensureView(); - return mKeyguardViewController.interceptMediaKey(event); - } - - /** - * @return true if the pre IME back event should be handled - */ - public boolean dispatchBackKeyEventPreIme() { - ensureView(); - return mKeyguardViewController.dispatchBackKeyEventPreIme(); - } - - public void notifyKeyguardAuthenticated(boolean strongAuth) { - ensureView(); - mKeyguardViewController.finish(strongAuth, KeyguardUpdateMonitor.getCurrentUser()); - } - - private void dispatchFullyShown() { - for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) { - callback.onFullyShown(); - } - } - - private void dispatchStartingToHide() { - for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) { - callback.onStartingToHide(); - } - } - - private void dispatchStartingToShow() { - for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) { - callback.onStartingToShow(); - } - } - - private void dispatchFullyHidden() { - for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) { - callback.onFullyHidden(); - } - } - - private void dispatchExpansionChanged() { - for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) { - callback.onExpansionChanged(mExpansion); - } - } - - private void dispatchVisibilityChanged() { - for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) { - callback.onVisibilityChanged(mContainer.getVisibility() == View.VISIBLE); - } - } - - /** - * Apply keyguard configuration from the currently active resources. This can be called when the - * device configuration changes, to re-apply some resources that are qualified on the device - * configuration. - */ - public void updateResources() { - if (mKeyguardViewController != null) { - mKeyguardViewController.updateResources(); - } - } - - public void dump(PrintWriter pw) { - pw.println("KeyguardBouncer"); - pw.println(" isShowing(): " + isShowing()); - pw.println(" mStatusBarHeight: " + mStatusBarHeight); - pw.println(" mExpansion: " + mExpansion); - pw.println(" mKeyguardViewController; " + mKeyguardViewController); - pw.println(" mShowingSoon: " + mShowingSoon); - pw.println(" mBouncerPromptReason: " + mBouncerPromptReason); - pw.println(" mIsAnimatingAway: " + mIsAnimatingAway); - pw.println(" mInitialized: " + mInitialized); - } - - /** Update keyguard position based on a tapped X coordinate. */ - public void updateKeyguardPosition(float x) { - if (mKeyguardViewController != null) { - mKeyguardViewController.updateKeyguardPosition(x); - } - } - - public void addKeyguardResetCallback(KeyguardResetCallback callback) { - mResetCallbacks.addIfAbsent(callback); - } - - public void removeKeyguardResetCallback(KeyguardResetCallback callback) { - mResetCallbacks.remove(callback); - } - - /** - * Adds a callback to listen to bouncer expansion updates. - */ - public void addBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) { - if (!mExpansionCallbacks.contains(callback)) { - mExpansionCallbacks.add(callback); - } - } - - /** - * Removes a previously added callback. If the callback was never added, this methood - * does nothing. - */ - public void removeBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) { - mExpansionCallbacks.remove(callback); - } - - /** Create a {@link KeyguardBouncer} once a container and bouncer callback are available. */ - public static class Factory { - private final Context mContext; - private final ViewMediatorCallback mCallback; - private final DismissCallbackRegistry mDismissCallbackRegistry; - private final FalsingCollector mFalsingCollector; - private final KeyguardStateController mKeyguardStateController; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final KeyguardBypassController mKeyguardBypassController; - private final Handler mHandler; - private final KeyguardSecurityModel mKeyguardSecurityModel; - private final KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; - - @Inject - public Factory(Context context, ViewMediatorCallback callback, - DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector, - KeyguardStateController keyguardStateController, - KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardBypassController keyguardBypassController, @Main Handler handler, - KeyguardSecurityModel keyguardSecurityModel, - KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) { - mContext = context; - mCallback = callback; - mDismissCallbackRegistry = dismissCallbackRegistry; - mFalsingCollector = falsingCollector; - mKeyguardStateController = keyguardStateController; - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mKeyguardBypassController = keyguardBypassController; - mHandler = handler; - mKeyguardSecurityModel = keyguardSecurityModel; - mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory; - } - - /** - * Construct a KeyguardBouncer that will exist in the given container. - */ - public KeyguardBouncer create(ViewGroup container, - PrimaryBouncerExpansionCallback expansionCallback) { - return new KeyguardBouncer(mContext, mCallback, container, - mDismissCallbackRegistry, mFalsingCollector, expansionCallback, - mKeyguardStateController, mKeyguardUpdateMonitor, - mKeyguardBypassController, mHandler, mKeyguardSecurityModel, - mKeyguardBouncerComponentFactory); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index 4d1454232853..fe2a9137c1a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; @@ -38,6 +37,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.DarkIconDispatcher; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.statusbar.policy.BatteryController; import java.io.PrintWriter; @@ -94,7 +94,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, - DumpManager dumpManager) { + DumpManager dumpManager, + DisplayTracker displayTracker) { mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone); mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone); mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher; @@ -104,7 +105,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC mNavigationMode = mode; }); - if (ctx.getDisplayId() == DEFAULT_DISPLAY) { + if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) { dumpManager.registerDumpable(getClass().getSimpleName(), this); } } @@ -317,24 +318,27 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private final BatteryController mBatteryController; private final NavigationModeController mNavModeController; private final DumpManager mDumpManager; + private final DisplayTracker mDisplayTracker; @Inject public Factory( DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, - DumpManager dumpManager) { + DumpManager dumpManager, + DisplayTracker displayTracker) { mDarkIconDispatcher = darkIconDispatcher; mBatteryController = batteryController; mNavModeController = navModeController; mDumpManager = dumpManager; + mDisplayTracker = displayTracker; } /** Create an {@link LightBarController} */ public LightBarController create(Context context) { return new LightBarController(context, mDarkIconDispatcher, mBatteryController, - mNavModeController, mDumpManager); + mNavModeController, mDumpManager, mDisplayTracker); } } } 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 24ad55d67bb0..11863627218e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -501,7 +501,7 @@ public interface StatusBarIconController { @VisibleForTesting protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) { if (mStatusBarPipelineFlags.useNewWifiIcon()) { - throw new IllegalStateException("Attempting to add a mobile icon while the new " + throw new IllegalStateException("Attempting to add a wifi icon while the new " + "icons are enabled is not supported"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 8e22bf4a8df4..fd465710d4ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -129,7 +129,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final ConfigurationController mConfigurationController; private final NavigationModeController mNavigationModeController; private final NotificationShadeWindowController mNotificationShadeWindowController; - private final KeyguardBouncer.Factory mKeyguardBouncerFactory; private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory; private final DreamOverlayStateController mDreamOverlayStateController; @Nullable @@ -206,28 +205,28 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()"); } onBackPressed(); - if (shouldPlayBackAnimation()) { + if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) { mPrimaryBouncerView.getDelegate().getBackCallback().onBackInvoked(); } } @Override public void onBackProgressed(BackEvent event) { - if (shouldPlayBackAnimation()) { + if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) { mPrimaryBouncerView.getDelegate().getBackCallback().onBackProgressed(event); } } @Override public void onBackCancelled() { - if (shouldPlayBackAnimation()) { + if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) { mPrimaryBouncerView.getDelegate().getBackCallback().onBackCancelled(); } } @Override public void onBackStarted(BackEvent event) { - if (shouldPlayBackAnimation()) { + if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) { mPrimaryBouncerView.getDelegate().getBackCallback().onBackStarted(event); } } @@ -256,7 +255,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private View mNotificationContainer; - @Nullable protected KeyguardBouncer mPrimaryBouncer; protected boolean mRemoteInputActive; private boolean mGlobalActionsVisible = false; private boolean mLastGlobalActionsVisible = false; @@ -281,7 +279,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastScreenOffAnimationPlaying; private float mQsExpansion; final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>(); - private boolean mIsModernBouncerEnabled; private boolean mIsUnoccludeTransitionFlagEnabled; private boolean mIsModernAlternateBouncerEnabled; private boolean mIsBackAnimationEnabled; @@ -329,7 +326,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb NotificationShadeWindowController notificationShadeWindowController, KeyguardStateController keyguardStateController, NotificationMediaManager notificationMediaManager, - KeyguardBouncer.Factory keyguardBouncerFactory, KeyguardMessageAreaController.Factory keyguardMessageAreaFactory, Optional<SysUIUnfoldComponent> sysUIUnfoldComponent, Lazy<ShadeController> shadeController, @@ -352,7 +348,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardUpdateManager = keyguardUpdateMonitor; mStatusBarStateController = sysuiStatusBarStateController; mDockManager = dockManager; - mKeyguardBouncerFactory = keyguardBouncerFactory; mKeyguardMessageAreaFactory = keyguardMessageAreaFactory; mShadeController = shadeController; mLatencyTracker = latencyTracker; @@ -362,7 +357,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mPrimaryBouncerView = primaryBouncerView; mFoldAodAnimationController = sysUIUnfoldComponent .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); - mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER); mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION); mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER); mAlternateBouncerInteractor = alternateBouncerInteractor; @@ -381,11 +375,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBiometricUnlockController = biometricUnlockController; ViewGroup container = mCentralSurfaces.getBouncerContainer(); - if (mIsModernBouncerEnabled) { - mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback); - } else { - mPrimaryBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback); - } + mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback); mNotificationPanelViewController = notificationPanelViewController; if (shadeExpansionStateManager != null) { shadeExpansionStateManager.addExpansionListener(this); @@ -552,11 +542,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * show if any subsequent events are to be handled. */ if (beginShowingBouncer(event)) { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */); - } else { - mPrimaryBouncerInteractor.show(/* isScrimmed= */false); - } + mPrimaryBouncerInteractor.show(/* isScrimmed= */false); } if (!primaryBouncerIsOrWillBeShowing()) { @@ -564,17 +550,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } if (mKeyguardStateController.isShowing()) { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.setExpansion(fraction); - } else { - mPrimaryBouncerInteractor.setPanelExpansion(fraction); - } + mPrimaryBouncerInteractor.setPanelExpansion(fraction); } else { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.setExpansion(EXPANSION_HIDDEN); - } else { - mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN); - } + mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN); } } @@ -604,24 +582,17 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb /** * Shows the notification keyguard or the bouncer depending on - * {@link KeyguardBouncer#needsFullscreenBouncer()}. + * {@link #needsFullscreenBouncer()}. */ protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) { if (needsFullscreenBouncer() && !mDozing) { // The keyguard might be showing (already). So we need to hide it. mCentralSurfaces.hideKeyguard(); - if (mPrimaryBouncer != null) { - mPrimaryBouncer.show(true /* resetSecuritySelection */); - } else { - mPrimaryBouncerInteractor.show(true); - } + mPrimaryBouncerInteractor.show(true); } else { mCentralSurfaces.showKeyguard(); if (hideBouncerWhenShowing) { hideBouncer(false /* destroyView */); - if (mPrimaryBouncer != null) { - mPrimaryBouncer.prepare(); - } } } updateStates(); @@ -648,11 +619,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ @VisibleForTesting void hideBouncer(boolean destroyView) { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.hide(destroyView); - } else { - mPrimaryBouncerInteractor.hide(); - } + mPrimaryBouncerInteractor.hide(); if (mKeyguardStateController.isShowing()) { // If we were showing the bouncer and then aborting, we need to also clear out any // potential actions unless we actually unlocked. @@ -671,11 +638,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb hideAlternateBouncer(false); if (mKeyguardStateController.isShowing() && !isBouncerShowing()) { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.show(false /* resetSecuritySelection */, scrimmed); - } else { - mPrimaryBouncerInteractor.show(scrimmed); - } + mPrimaryBouncerInteractor.show(scrimmed); } updateStates(); } @@ -708,13 +671,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // instead of the bouncer. if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) { if (!afterKeyguardGone) { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.setDismissAction(mAfterKeyguardGoneAction, - mKeyguardGoneCancelAction); - } else { - mPrimaryBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction, - mKeyguardGoneCancelAction); - } + mPrimaryBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction, + mKeyguardGoneCancelAction); mAfterKeyguardGoneAction = null; mKeyguardGoneCancelAction = null; } @@ -726,22 +684,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (afterKeyguardGone) { // we'll handle the dismiss action after keyguard is gone, so just show the // bouncer - if (mPrimaryBouncer != null) { - mPrimaryBouncer.show(false /* resetSecuritySelection */); - } else { - mPrimaryBouncerInteractor.show(/* isScrimmed= */true); - } + mPrimaryBouncerInteractor.show(/* isScrimmed= */true); } else { // after authentication success, run dismiss action with the option to defer // hiding the keyguard based on the return value of the OnDismissAction - if (mPrimaryBouncer != null) { - mPrimaryBouncer.showWithDismissAction(mAfterKeyguardGoneAction, - mKeyguardGoneCancelAction); - } else { - mPrimaryBouncerInteractor.setDismissAction( - mAfterKeyguardGoneAction, mKeyguardGoneCancelAction); - mPrimaryBouncerInteractor.show(/* isScrimmed= */true); - } + mPrimaryBouncerInteractor.setDismissAction( + mAfterKeyguardGoneAction, mKeyguardGoneCancelAction); + mPrimaryBouncerInteractor.show(/* isScrimmed= */true); // bouncer will handle the dismiss action, so we no longer need to track it here mAfterKeyguardGoneAction = null; mKeyguardGoneCancelAction = null; @@ -841,11 +790,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void onFinishedGoingToSleep() { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.onScreenTurnedOff(); - } else { - mPrimaryBouncerInteractor.onScreenTurnedOff(); - } + mPrimaryBouncerInteractor.onScreenTurnedOff(); } @Override @@ -939,11 +884,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void startPreHideAnimation(Runnable finishRunnable) { if (primaryBouncerIsShowing()) { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.startPreHideAnimation(finishRunnable); - } else { - mPrimaryBouncerInteractor.startDisappearAnimation(finishRunnable); - } + mPrimaryBouncerInteractor.startDisappearAnimation(finishRunnable); mNotificationPanelViewController.startBouncerPreHideAnimation(); // We update the state (which will show the keyguard) only if an animation will run on @@ -1051,17 +992,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public void onThemeChanged() { - if (mIsModernBouncerEnabled) { - updateResources(); - return; - } - boolean wasShowing = primaryBouncerIsShowing(); - boolean wasScrimmed = primaryBouncerIsScrimmed(); - - hideBouncer(true /* destroyView */); - mPrimaryBouncer.prepare(); - - if (wasShowing) showPrimaryBouncer(wasScrimmed); + updateResources(); } public void onKeyguardFadedAway() { @@ -1106,10 +1037,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * WARNING: This method might cause Binder calls. */ public boolean isSecure() { - if (mPrimaryBouncer != null) { - return mPrimaryBouncer.isSecure(); - } - return mKeyguardSecurityModel.getSecurityMode( KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None; } @@ -1164,10 +1091,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public boolean isFullscreenBouncer() { - if (mPrimaryBouncerView.getDelegate() != null) { - return mPrimaryBouncerView.getDelegate().isFullScreenBouncer(); - } - return mPrimaryBouncer != null && mPrimaryBouncer.isFullscreenBouncer(); + return mPrimaryBouncerView.getDelegate() != null + && mPrimaryBouncerView.getDelegate().isFullScreenBouncer(); } /** @@ -1223,17 +1148,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb != (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive) || mFirstUpdate) { if (primaryBouncerDismissible || !showing || remoteInputActive) { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.setBackButtonEnabled(true); - } else { - mPrimaryBouncerInteractor.setBackButtonEnabled(true); - } + mPrimaryBouncerInteractor.setBackButtonEnabled(true); } else { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.setBackButtonEnabled(false); - } else { - mPrimaryBouncerInteractor.setBackButtonEnabled(false); - } + mPrimaryBouncerInteractor.setBackButtonEnabled(false); } } @@ -1327,27 +1244,21 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public boolean shouldDismissOnMenuPressed() { - if (mPrimaryBouncerView.getDelegate() != null) { - return mPrimaryBouncerView.getDelegate().shouldDismissOnMenuPressed(); - } - return mPrimaryBouncer != null && mPrimaryBouncer.shouldDismissOnMenuPressed(); + return mPrimaryBouncerView.getDelegate() != null + && mPrimaryBouncerView.getDelegate().shouldDismissOnMenuPressed(); } public boolean interceptMediaKey(KeyEvent event) { - if (mPrimaryBouncerView.getDelegate() != null) { - return mPrimaryBouncerView.getDelegate().interceptMediaKey(event); - } - return mPrimaryBouncer != null && mPrimaryBouncer.interceptMediaKey(event); + return mPrimaryBouncerView.getDelegate() != null + && mPrimaryBouncerView.getDelegate().interceptMediaKey(event); } /** * @return true if the pre IME back event should be handled */ public boolean dispatchBackKeyEventPreIme() { - if (mPrimaryBouncerView.getDelegate() != null) { - return mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme(); - } - return mPrimaryBouncer != null && mPrimaryBouncer.dispatchBackKeyEventPreIme(); + return mPrimaryBouncerView.getDelegate() != null + && mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme(); } public void readyForKeyguardDone() { @@ -1393,11 +1304,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * fingerprint. */ public void notifyKeyguardAuthenticated(boolean strongAuth) { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.notifyKeyguardAuthenticated(strongAuth); - } else { - mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth); - } + mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth); if (mAlternateBouncerInteractor.isVisibleState()) { hideAlternateBouncer(false); @@ -1412,11 +1319,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardMessageAreaController.setMessage(message); } } else { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.showMessage(message, colorState); - } else { - mPrimaryBouncerInteractor.showMessage(message, colorState); - } + mPrimaryBouncerInteractor.showMessage(message, colorState); } } @@ -1472,11 +1375,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * configuration. */ public void updateResources() { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.updateResources(); - } else { - mPrimaryBouncerInteractor.updateResources(); - } + mPrimaryBouncerInteractor.updateResources(); } public void dump(PrintWriter pw) { @@ -1494,11 +1393,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb pw.println(" " + callback); } - if (mPrimaryBouncer != null) { - pw.println("PrimaryBouncer:"); - mPrimaryBouncer.dump(pw); - } - if (mOccludingAppBiometricUI != null) { pw.println("mOccludingAppBiometricUI:"); mOccludingAppBiometricUI.dump(pw); @@ -1548,11 +1442,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } - @Nullable - public KeyguardBouncer getPrimaryBouncer() { - return mPrimaryBouncer; - } - /** * For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently * showing. @@ -1571,11 +1460,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb /** Update keyguard position based on a tapped X coordinate. */ public void updateKeyguardPosition(float x) { - if (mPrimaryBouncer != null) { - mPrimaryBouncer.updateKeyguardPosition(x); - } else { - mPrimaryBouncerInteractor.setKeyguardPosition(x); - } + mPrimaryBouncerInteractor.setKeyguardPosition(x); } private static class DismissWithActionRequest { @@ -1615,56 +1500,35 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * Returns if bouncer expansion is between 0 and 1 non-inclusive. */ public boolean isPrimaryBouncerInTransit() { - if (mPrimaryBouncer != null) { - return mPrimaryBouncer.inTransit(); - } else { - return mPrimaryBouncerInteractor.isInTransit(); - } + return mPrimaryBouncerInteractor.isInTransit(); } /** * Returns if bouncer is showing */ public boolean primaryBouncerIsShowing() { - if (mPrimaryBouncer != null) { - return mPrimaryBouncer.isShowing(); - } else { - return mPrimaryBouncerInteractor.isFullyShowing(); - } + return mPrimaryBouncerInteractor.isFullyShowing(); } /** * Returns if bouncer is scrimmed */ public boolean primaryBouncerIsScrimmed() { - if (mPrimaryBouncer != null) { - return mPrimaryBouncer.isScrimmed(); - } else { - return mPrimaryBouncerInteractor.isScrimmed(); - } + return mPrimaryBouncerInteractor.isScrimmed(); } /** * Returns if bouncer is animating away */ public boolean bouncerIsAnimatingAway() { - if (mPrimaryBouncer != null) { - return mPrimaryBouncer.isAnimatingAway(); - } else { - return mPrimaryBouncerInteractor.isAnimatingAway(); - } - + return mPrimaryBouncerInteractor.isAnimatingAway(); } /** * Returns if bouncer will dismiss with action */ public boolean primaryBouncerWillDismissWithAction() { - if (mPrimaryBouncer != null) { - return mPrimaryBouncer.willDismissWithAction(); - } else { - return mPrimaryBouncerInteractor.willDismissWithAction(); - } + return mPrimaryBouncerInteractor.willDismissWithAction(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java index ae48c2d3b6f3..50cce45cd87a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java @@ -17,5 +17,5 @@ package com.android.systemui.statusbar.phone; public interface StatusBarWindowCallback { void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing, - boolean isDozing, boolean panelExpanded); + boolean isDozing, boolean panelExpanded, boolean isDreaming); } 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 206c0aa0d7a4..344d23341dc1 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 @@ -46,7 +46,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.OperatorNameViewController; -import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent; @@ -293,7 +292,6 @@ public abstract class StatusBarViewModule { StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, NotificationPanelViewController notificationPanelViewController, - NetworkController networkController, StatusBarStateController statusBarStateController, CommandQueue commandQueue, CarrierConfigTracker carrierConfigTracker, @@ -315,7 +313,6 @@ public abstract class StatusBarViewModule { statusBarHideIconsForBouncerManager, keyguardStateController, notificationPanelViewController, - networkController, statusBarStateController, commandQueue, carrierConfigTracker, 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 be6d5baf8eda..9354c5e1948d 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 @@ -24,7 +24,6 @@ import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedul import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT; import android.animation.Animator; -import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.Fragment; @@ -59,9 +58,6 @@ import com.android.systemui.statusbar.DisableFlagsLogger.DisableState; import com.android.systemui.statusbar.OperatorNameView; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.connectivity.IconState; -import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.NotificationIconAreaController; @@ -75,7 +71,6 @@ import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentCom import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; -import com.android.systemui.statusbar.policy.EncryptionHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.CarrierConfigTracker; import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListener; @@ -110,7 +105,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final StatusBarStateController mStatusBarStateController; private final KeyguardStateController mKeyguardStateController; private final NotificationPanelViewController mNotificationPanelViewController; - private final NetworkController mNetworkController; private LinearLayout mEndSideContent; private View mClockView; private View mOngoingCallChip; @@ -139,13 +133,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private List<String> mBlockedIcons = new ArrayList<>(); private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>(); - private SignalCallback mSignalCallback = new SignalCallback() { - @Override - public void setIsAirplaneMode(@NonNull IconState icon) { - mCommandQueue.recomputeDisableFlags(getContext().getDisplayId(), true /* animate */); - } - }; - private final OngoingCallListener mOngoingCallListener = new OngoingCallListener() { @Override public void onOngoingCallStateChanged(boolean animate) { @@ -191,7 +178,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, NotificationPanelViewController notificationPanelViewController, - NetworkController networkController, StatusBarStateController statusBarStateController, CommandQueue commandQueue, CarrierConfigTracker carrierConfigTracker, @@ -213,7 +199,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mDarkIconManagerFactory = darkIconManagerFactory; mKeyguardStateController = keyguardStateController; mNotificationPanelViewController = notificationPanelViewController; - mNetworkController = networkController; mStatusBarStateController = statusBarStateController; mCommandQueue = commandQueue; mCarrierConfigTracker = carrierConfigTracker; @@ -261,7 +246,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip); showEndSideContent(false); showClock(false); - initEmergencyCryptkeeperText(); initOperatorName(); initNotificationIconArea(); mSystemEventAnimator = @@ -340,9 +324,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void onDestroyView() { super.onDestroyView(); mStatusBarIconController.removeIconGroup(mDarkIconManager); - if (mNetworkController.hasEmergencyCryptKeeperText()) { - mNetworkController.removeCallback(mSignalCallback); - } mCarrierConfigTracker.removeCallback(mCarrierConfigCallback); mCarrierConfigTracker.removeDataSubscriptionChangedListener(mDefaultDataListener); @@ -444,15 +425,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue state |= DISABLE_CLOCK; } - if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) { - if (mNetworkController.hasEmergencyCryptKeeperText()) { - state |= DISABLE_NOTIFICATION_ICONS; - } - if (!mNetworkController.isRadioOn()) { - state |= DISABLE_SYSTEM_INFO; - } - } - if (mOngoingCallController.hasOngoingCall()) { state &= ~DISABLE_ONGOING_CALL_CHIP; } else { @@ -620,19 +592,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } } - private void initEmergencyCryptkeeperText() { - View emergencyViewStub = mStatusBar.findViewById(R.id.emergency_cryptkeeper_text); - if (mNetworkController.hasEmergencyCryptKeeperText()) { - if (emergencyViewStub != null) { - ((ViewStub) emergencyViewStub).inflate(); - } - mNetworkController.addCallback(mSignalCallback); - } else if (emergencyViewStub != null) { - ViewGroup parent = (ViewGroup) emergencyViewStub.getParent(); - parent.removeView(emergencyViewStub); - } - } - private void initOperatorName() { int subId = SubscriptionManager.getDefaultDataSubscriptionId(); if (mCarrierConfigTracker.getShowOperatorNameInStatusBarConfig(subId)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt index 2c8677dee4d9..2d80edb8d2f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt @@ -19,14 +19,14 @@ package com.android.systemui.statusbar.phone.userswitcher import android.content.Context import android.util.AttributeSet import android.widget.ImageView -import android.widget.LinearLayout import android.widget.TextView import com.android.systemui.R +import com.android.systemui.common.ui.view.LaunchableLinearLayout class StatusBarUserSwitcherContainer( context: Context?, attrs: AttributeSet? -) : LinearLayout(context, attrs) { +) : LaunchableLinearLayout(context, attrs) { lateinit var text: TextView private set lateinit var avatar: ImageView diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 510482dcb21e..39ad31f7eee8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -53,7 +53,7 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository -import com.android.systemui.util.kotlin.pairwiseBy +import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -66,10 +66,10 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn @@ -287,22 +287,13 @@ constructor( */ @SuppressLint("MissingPermission") override val activeSubChangedInGroupEvent = - flow { - activeMobileDataSubscriptionId.pairwiseBy { prevVal: Int, newVal: Int -> - if (!defaultMobileNetworkConnectivity.value.isValidated) { - return@pairwiseBy - } - val prevSub = subscriptionManager.getActiveSubscriptionInfo(prevVal) - val nextSub = subscriptionManager.getActiveSubscriptionInfo(newVal) - - if (prevSub == null || nextSub == null) { - return@pairwiseBy - } + activeMobileDataSubscriptionId + .pairwise() + .mapNotNull { (prevVal: Int, newVal: Int) -> + val prevSub = subscriptionManager.getActiveSubscriptionInfo(prevVal)?.groupUuid + val nextSub = subscriptionManager.getActiveSubscriptionInfo(newVal)?.groupUuid - if (prevSub.groupUuid != null && prevSub.groupUuid == nextSub.groupUuid) { - emit(Unit) - } - } + if (prevSub != null && prevSub == nextSub) Unit else null } .flowOn(bgDispatcher) 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 index d3ff3573dae2..491f3a5513b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt @@ -97,15 +97,20 @@ constructor( ) } - fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { + fun logOnCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities, + isDefaultNetworkCallback: Boolean, + ) { buffer.log( SB_LOGGING_TAG, LogLevel.INFO, { + bool1 = isDefaultNetworkCallback int1 = network.getNetId() str1 = networkCapabilities.toString() }, - { "onCapabilitiesChanged: net=$int1 capabilities=$str1" } + { "onCapabilitiesChanged[default=$bool1]: net=$int1 capabilities=$str1" } ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt index cc0ec548716d..b1e28129a690 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt @@ -77,6 +77,17 @@ open class ModernStatusBarView(context: Context, attrs: AttributeSet?) : return binding.getShouldIconBeVisible() } + /** See [StatusBarIconView.getDrawingRect]. */ + override fun getDrawingRect(outRect: Rect) { + super.getDrawingRect(outRect) + val translationX = translationX.toInt() + val translationY = translationY.toInt() + outRect.left += translationX + outRect.right += translationX + outRect.top += translationY + outRect.bottom += translationY + } + /** * Initializes this view. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index d26499c18661..86690479f679 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -114,13 +114,17 @@ constructor( network: Network, networkCapabilities: NetworkCapabilities ) { + logger.logOnCapabilitiesChanged( + network, + networkCapabilities, + isDefaultNetworkCallback = true, + ) + // This method will always be called immediately after the network // becomes the default, in addition to any time the capabilities change // while the network is the default. - // If this network contains valid wifi info, then wifi is the default - // network. - val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities) - trySend(wifiInfo != null) + // If this network is a wifi network, then wifi is the default network. + trySend(isWifiNetwork(networkCapabilities)) } override fun onLost(network: Network) { @@ -152,7 +156,11 @@ constructor( network: Network, networkCapabilities: NetworkCapabilities ) { - logger.logOnCapabilitiesChanged(network, networkCapabilities) + logger.logOnCapabilitiesChanged( + network, + networkCapabilities, + isDefaultNetworkCallback = false, + ) wifiNetworkChangeEvents.tryEmit(Unit) @@ -253,16 +261,30 @@ constructor( 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) + networkCapabilities.hasTransport(TRANSPORT_WIFI) -> + if (networkCapabilities.transportInfo is WifiInfo) { + networkCapabilities.transportInfo as WifiInfo + } else { + null + } else -> null } } + /** True if these capabilities represent a wifi network. */ + private fun isWifiNetwork(networkCapabilities: NetworkCapabilities): Boolean { + return when { + networkCapabilities.hasTransport(TRANSPORT_WIFI) -> true + networkCapabilities.hasTransport(TRANSPORT_CELLULAR) -> + Utils.tryGetWifiInfoForVcn(networkCapabilities) != null + else -> false + } + } + private fun createWifiNetworkModel( wifiInfo: WifiInfo, network: Network, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java deleted file mode 100644 index 21a83004ba84..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.policy; - -import android.annotation.Nullable; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.provider.Settings; -import android.telephony.SubscriptionInfo; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.widget.TextView; - -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.systemui.Dependency; - -import java.util.List; - -public class EmergencyCryptkeeperText extends TextView { - - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onPhoneStateChanged(int phoneState) { - update(); - } - - @Override - public void onRefreshCarrierInfo() { - update(); - } - }; - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) { - update(); - } - } - }; - - public EmergencyCryptkeeperText(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - setVisibility(GONE); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); - mKeyguardUpdateMonitor.registerCallback(mCallback); - getContext().registerReceiver(mReceiver, - new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)); - update(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mKeyguardUpdateMonitor != null) { - mKeyguardUpdateMonitor.removeCallback(mCallback); - } - getContext().unregisterReceiver(mReceiver); - } - - private boolean iccCardExist(int simState) { - return ((simState == TelephonyManager.SIM_STATE_PIN_REQUIRED) - || (simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) - || (simState == TelephonyManager.SIM_STATE_NETWORK_LOCKED) - || (simState == TelephonyManager.SIM_STATE_READY) - || (simState == TelephonyManager.SIM_STATE_NOT_READY) - || (simState == TelephonyManager.SIM_STATE_PERM_DISABLED) - || (simState == TelephonyManager.SIM_STATE_CARD_IO_ERROR) - || (simState == TelephonyManager.SIM_STATE_CARD_RESTRICTED) - || (simState == TelephonyManager.SIM_STATE_LOADED)); - } - - public void update() { - boolean hasMobile = mContext.getSystemService(TelephonyManager.class).isDataCapable(); - boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.AIRPLANE_MODE_ON, 0) == 1); - - if (!hasMobile || airplaneMode) { - setText(null); - setVisibility(GONE); - return; - } - - boolean allSimsMissing = true; - CharSequence displayText = null; - - List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(); - final int N = subs.size(); - for (int i = 0; i < N; i++) { - int subId = subs.get(i).getSubscriptionId(); - int simState = mKeyguardUpdateMonitor.getSimState(subId); - CharSequence carrierName = subs.get(i).getCarrierName(); - if (iccCardExist(simState) && !TextUtils.isEmpty(carrierName)) { - allSimsMissing = false; - displayText = carrierName; - } - } - if (allSimsMissing) { - if (N != 0) { - // Shows "Emergency calls only" on devices that are voice-capable. - // This depends on mPlmn containing the text "Emergency calls only" when the radio - // has some connectivity. Otherwise it should show "No service" - // Grab the first subscription, because they all should contain the emergency text, - // described above. - displayText = subs.get(0).getCarrierName(); - } else { - // We don't have a SubscriptionInfo to get the emergency calls only from. - // Grab it from the old sticky broadcast if possible instead. We can use it - // here because no subscriptions are active, so we don't have - // to worry about MSIM clashing. - displayText = getContext().getText( - com.android.internal.R.string.emergency_calls_only); - Intent i = getContext().registerReceiver(null, - new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)); - if (i != null) { - displayText = i.getStringExtra(TelephonyManager.EXTRA_PLMN); - } - } - } - - setText(displayText); - setVisibility(TextUtils.isEmpty(displayText) ? GONE : VISIBLE); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EncryptionHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EncryptionHelper.java deleted file mode 100644 index 9c099f91bc8d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EncryptionHelper.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.policy; - -import android.sysprop.VoldProperties; - -/** - * Helper for determining whether the phone is decrypted yet. - */ -public class EncryptionHelper { - - public static final boolean IS_DATA_ENCRYPTED = isDataEncrypted(); - - private static boolean isDataEncrypted() { - String voldState = VoldProperties.decrypt().orElse(""); - return "1".equals(voldState) || "trigger_restart_min_framework".equals(voldState); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java index 9946b4b9ecaa..5dcafb37f57e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.policy; import android.annotation.WorkerThread; -import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; @@ -255,7 +254,6 @@ public class FlashlightControllerImpl implements FlashlightController { setTorchMode(enabled); mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1); mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0); - mBroadcastSender.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index e0d780a5fcd5..7a4e35f159ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -48,6 +48,7 @@ import com.android.systemui.animation.DelegateLaunchAnimatorController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.fragments.FragmentService; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener; @@ -73,6 +74,7 @@ public class StatusBarWindowController { private boolean mIsAttached; private final ViewGroup mStatusBarWindowView; + private final FragmentService mFragmentService; // The container in which we should run launch animations started from the status bar and // expanding into the opening window. private final ViewGroup mLaunchAnimationContainer; @@ -86,6 +88,7 @@ public class StatusBarWindowController { WindowManager windowManager, IWindowManager iWindowManager, StatusBarContentInsetsProvider contentInsetsProvider, + FragmentService fragmentService, @Main Resources resources, Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) { mContext = context; @@ -93,6 +96,7 @@ public class StatusBarWindowController { mIWindowManager = iWindowManager; mContentInsetsProvider = contentInsetsProvider; mStatusBarWindowView = statusBarWindowView; + mFragmentService = fragmentService; mLaunchAnimationContainer = mStatusBarWindowView.findViewById( R.id.status_bar_launch_animation_container); mLpChanged = new WindowManager.LayoutParams(); @@ -157,7 +161,7 @@ public class StatusBarWindowController { /** Returns a fragment host manager for the status bar window view. */ public FragmentHostManager getFragmentHostManager() { - return FragmentHostManager.get(mStatusBarWindowView); + return mFragmentService.getFragmentHostManager(mStatusBarWindowView); } /** diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt index 5a8850a9f89b..dde2a80b05f7 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt @@ -39,6 +39,17 @@ constructor( private val featureFlags: FeatureFlags, ) : CoreStartable, StylusManager.StylusCallback, StylusManager.StylusBatteryCallback { + override fun onStylusAdded(deviceId: Int) { + // On some devices, the addition of a new internal stylus indicates the use of a + // USI stylus with a different vendor/product ID. We would therefore like to reset + // the battery notification suppression, in case the user has dismissed a low battery + // notification of the previous stylus. + val device = inputManager.getInputDevice(deviceId) ?: return + if (!device.isExternal) { + stylusUsiPowerUi.updateSuppression(false) + } + } + override fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) { stylusUsiPowerUi.refresh() } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index df8d16142b8b..1065d3349930 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -327,7 +327,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora // appropriately. activeViews.remove(displayInfo) listeners.forEach { - it.onInfoPermanentlyRemoved(id) + it.onInfoPermanentlyRemoved(id, removalReason) } // No need to time the view out since it's already gone @@ -393,7 +393,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora activeViews.remove(it) logger.logViewExpiration(it.info) listeners.forEach { listener -> - listener.onInfoPermanentlyRemoved(it.info.id) + listener.onInfoPermanentlyRemoved(it.info.id, REMOVAL_REASON_TIME_EXPIRED) } } } @@ -457,7 +457,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora * Called whenever a [DisplayInfo] with the given [id] has been removed and will never be * displayed again (unless another call to [updateView] is made). */ - fun onInfoPermanentlyRemoved(id: String) + fun onInfoPermanentlyRemoved(id: String, reason: String) } /** A container for all the display-related state objects. */ @@ -494,6 +494,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT" +private const val REMOVAL_REASON_TIME_EXPIRED = "TIMEOUT_EXPIRED_BEFORE_REDISPLAY" private const val MIN_REQUIRED_TIME_FOR_REDISPLAY = 1000 private data class IconInfo( diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt index 6e3cb4823afa..9dbc4b398ab3 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt @@ -19,6 +19,7 @@ package com.android.systemui.temporarydisplay.chipbar import android.content.Context import android.view.MotionEvent import android.view.View +import com.android.systemui.settings.DisplayTracker import com.android.systemui.statusbar.gesture.SwipeUpGestureHandler import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger import com.android.systemui.util.boundsOnScreen @@ -31,8 +32,9 @@ import com.android.systemui.util.boundsOnScreen */ class SwipeChipbarAwayGestureHandler( context: Context, + displayTracker: DisplayTracker, logger: SwipeUpGestureLogger, -) : SwipeUpGestureHandler(context, logger, loggerTag = LOGGER_TAG) { +) : SwipeUpGestureHandler(context, displayTracker, logger, loggerTag = LOGGER_TAG) { private var viewFetcher: () -> View? = { null } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt index 933c0604a3b9..b1be4045eb43 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBufferFactory import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.settings.DisplayTracker import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler import dagger.Module @@ -41,10 +42,11 @@ interface TemporaryDisplayModule { fun provideSwipeChipbarAwayGestureHandler( mediaTttFlags: MediaTttFlags, context: Context, + displayTracker: DisplayTracker, logger: SwipeUpGestureLogger, ): SwipeChipbarAwayGestureHandler? { return if (mediaTttFlags.isMediaTttDismissGestureEnabled()) { - SwipeChipbarAwayGestureHandler(context, logger) + SwipeChipbarAwayGestureHandler(context, displayTracker, logger) } else { null } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java index 79811c5de42d..24758908cff2 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java @@ -28,8 +28,9 @@ import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settingslib.Utils; +import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.fragments.FragmentService; import java.util.Objects; @@ -74,7 +75,7 @@ public class RadioListPreference extends CustomListPreference { RadioFragment f = new RadioFragment(); f.setPreference(this); - FragmentHostManager.get(v).getFragmentManager() + Dependency.get(FragmentService.class).getFragmentHostManager(v).getFragmentManager() .beginTransaction() .add(android.R.id.content, f) .commit(); @@ -86,8 +87,10 @@ public class RadioListPreference extends CustomListPreference { Bundle savedInstanceState) { super.onDialogStateRestored(fragment, dialog, savedInstanceState); View view = dialog.findViewById(R.id.content); - RadioFragment radioFragment = (RadioFragment) FragmentHostManager.get(view) - .getFragmentManager().findFragmentById(R.id.content); + RadioFragment radioFragment = (RadioFragment) Dependency.get(FragmentService.class) + .getFragmentHostManager(view) + .getFragmentManager() + .findFragmentById(R.id.content); if (radioFragment != null) { radioFragment.setPreference(this); } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 0069bb545ef4..19a0866cd0a8 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -38,6 +38,7 @@ import android.view.WindowlessWindowManager import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.settings.DisplayTracker import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.LinearLightRevealEffect @@ -68,6 +69,7 @@ constructor( @Main private val executor: Executor, private val threadFactory: ThreadFactory, private val rotationChangeProvider: RotationChangeProvider, + private val displayTracker: DisplayTracker ) { private val transitionListener = TransitionListener() @@ -104,7 +106,7 @@ constructor( .setName("unfold-overlay-container") displayAreaHelper.get().attachToRootDisplayArea( - Display.DEFAULT_DISPLAY, + displayTracker.defaultDisplayId, containerBuilder ) { builder -> executor.execute { diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index c0f03902202a..8cb4deb4882c 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -174,7 +174,7 @@ constructor( val callback = object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { + override fun onUserChanging(newUser: Int, userContext: Context) { send() } diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index d54de3fa9a3f..c2727fc32465 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -14,8 +14,6 @@ package com.android.systemui.util; -import static android.view.Display.DEFAULT_DISPLAY; - import android.Manifest; import android.content.Context; import android.content.Intent; @@ -26,6 +24,7 @@ import android.view.DisplayCutout; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.R; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shared.system.QuickStepContract; import java.util.List; @@ -71,8 +70,9 @@ public class Utils { * {@link android.view.WindowManagerPolicyConstants#NAV_BAR_MODE_GESTURAL} AND * the context is that of the default display */ - public static boolean isGesturalModeOnDefaultDisplay(Context context, int navMode) { - return context.getDisplayId() == DEFAULT_DISPLAY + public static boolean isGesturalModeOnDefaultDisplay(Context context, + DisplayTracker displayTracker, int navMode) { + return context.getDisplayId() == displayTracker.getDefaultDisplayId() && QuickStepContract.isGesturalMode(navMode); } diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt index 08ee0af17fb0..56c5d3b433ff 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt @@ -27,6 +27,8 @@ import android.view.ViewTreeObserver import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.animation.LaunchableView +import com.android.systemui.animation.LaunchableViewDelegate import com.android.systemui.statusbar.CrossFadeHelper /** @@ -38,7 +40,7 @@ class TransitionLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayout(context, attrs, defStyleAttr) { +) : ConstraintLayout(context, attrs, defStyleAttr), LaunchableView { private val boundsRect = Rect() private val originalGoneChildrenSet: MutableSet<Int> = mutableSetOf() @@ -50,7 +52,11 @@ class TransitionLayout @JvmOverloads constructor( private var desiredMeasureWidth = 0 private var desiredMeasureHeight = 0 - private var transitionVisibility = View.VISIBLE + private val delegate = + LaunchableViewDelegate( + this, + superSetVisibility = { super.setVisibility(it) }, + ) /** * The measured state of this view which is the one we will lay ourselves out with. This @@ -83,11 +89,12 @@ class TransitionLayout @JvmOverloads constructor( } } - override fun setTransitionVisibility(visibility: Int) { - // We store the last transition visibility assigned to this view to restore it later if - // necessary. - super.setTransitionVisibility(visibility) - transitionVisibility = visibility + override fun setShouldBlockVisibilityChanges(block: Boolean) { + delegate.setShouldBlockVisibilityChanges(block) + } + + override fun setVisibility(visibility: Int) { + delegate.setVisibility(visibility) } override fun onFinishInflate() { @@ -173,14 +180,6 @@ class TransitionLayout @JvmOverloads constructor( translationY = currentState.translation.y CrossFadeHelper.fadeIn(this, currentState.alpha) - - // CrossFadeHelper#fadeIn will change this view visibility, which overrides the transition - // visibility. We set the transition visibility again to make sure that this view plays well - // with GhostView, which sets the transition visibility and is used for activity launch - // animations. - if (transitionVisibility != View.VISIBLE) { - setTransitionVisibility(transitionVisibility) - } } private fun applyCurrentStateOnPredraw() { diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 7033ccde8c7d..5d896cbbdab4 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -236,7 +236,8 @@ public class BubblesManager { // Store callback in a field so it won't get GC'd mStatusBarWindowCallback = - (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded) -> + (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded, + isDreaming) -> mBubbles.onNotificationPanelExpandedChanged(panelExpanded); notificationShadeWindowController.registerCallback(mStatusBarWindowCallback); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 8ef98f08c60d..bd60401034b3 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -16,8 +16,6 @@ package com.android.systemui.wmshell; -import static android.view.Display.DEFAULT_DISPLAY; - import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED; @@ -50,6 +48,7 @@ import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.notetask.NoteTaskInitializer; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.systemui.statusbar.CommandQueue; @@ -124,6 +123,7 @@ public final class WMShell implements private final WakefulnessLifecycle mWakefulnessLifecycle; private final ProtoTracer mProtoTracer; private final UserTracker mUserTracker; + private final DisplayTracker mDisplayTracker; private final NoteTaskInitializer mNoteTaskInitializer; private final Executor mSysUiMainExecutor; @@ -186,6 +186,7 @@ public final class WMShell implements ProtoTracer protoTracer, WakefulnessLifecycle wakefulnessLifecycle, UserTracker userTracker, + DisplayTracker displayTracker, NoteTaskInitializer noteTaskInitializer, @Main Executor sysUiMainExecutor) { mContext = context; @@ -203,6 +204,7 @@ public final class WMShell implements mWakefulnessLifecycle = wakefulnessLifecycle; mProtoTracer = protoTracer; mUserTracker = userTracker; + mDisplayTracker = displayTracker; mNoteTaskInitializer = noteTaskInitializer; mSysUiMainExecutor = sysUiMainExecutor; } @@ -268,7 +270,7 @@ public final class WMShell implements public void onStartTransition(boolean isEntering) { mSysUiMainExecutor.execute(() -> { mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE, - true).commitUpdate(DEFAULT_DISPLAY); + true).commitUpdate(mDisplayTracker.getDefaultDisplayId()); }); } @@ -276,7 +278,7 @@ public final class WMShell implements public void onStartFinished(Rect bounds) { mSysUiMainExecutor.execute(() -> { mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE, - true).commitUpdate(DEFAULT_DISPLAY); + true).commitUpdate(mDisplayTracker.getDefaultDisplayId()); }); } @@ -284,7 +286,7 @@ public final class WMShell implements public void onStopFinished(Rect bounds) { mSysUiMainExecutor.execute(() -> { mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE, - false).commitUpdate(DEFAULT_DISPLAY); + false).commitUpdate(mDisplayTracker.getDefaultDisplayId()); }); } }); @@ -333,7 +335,8 @@ public final class WMShell implements @Override public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { - if (displayId == DEFAULT_DISPLAY && (vis & InputMethodService.IME_VISIBLE) != 0) { + if (displayId == mDisplayTracker.getDefaultDisplayId() + && (vis & InputMethodService.IME_VISIBLE) != 0) { oneHanded.stopOneHanded( OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_POP_IME_OUT); } @@ -346,7 +349,7 @@ public final class WMShell implements @Override public void onVisibilityChanged(boolean hasFreeformTasks) { mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, hasFreeformTasks) - .commitUpdate(DEFAULT_DISPLAY); + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); } }, mSysUiMainExecutor); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index c76b127a161c..b9c23d4af8a1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -15,7 +15,6 @@ */ package com.android.keyguard -import com.android.systemui.statusbar.CommandQueue import android.content.BroadcastReceiver import android.testing.AndroidTestingRunner import android.view.View @@ -34,6 +33,7 @@ import com.android.systemui.plugins.ClockEvents import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceEvents import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.mockito.any @@ -41,8 +41,6 @@ import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock -import java.util.TimeZone -import java.util.concurrent.Executor import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.yield @@ -51,15 +49,16 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import java.util.* +import java.util.concurrent.Executor +import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @SmallTest @@ -140,8 +139,9 @@ class ClockEventControllerTest : SysuiTestCase() { @Test fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) { - verify(smallClockEvents).onRegionDarknessChanged(anyBoolean()) - verify(largeClockEvents).onRegionDarknessChanged(anyBoolean()) + // TODO(b/266103601): delete this test and add more coverage for updateColors() + // verify(smallClockEvents).onRegionDarknessChanged(anyBoolean()) + // verify(largeClockEvents).onRegionDarknessChanged(anyBoolean()) val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() verify(configurationController).addCallback(capture(captor)) @@ -152,9 +152,6 @@ class ClockEventControllerTest : SysuiTestCase() { @Test fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) { - verify(smallClockEvents).onRegionDarknessChanged(anyBoolean()) - verify(largeClockEvents).onRegionDarknessChanged(anyBoolean()) - val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() verify(configurationController).addCallback(capture(captor)) captor.value.onDensityOrFontScaleChanged() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index 10595439200a..50645e5daa09 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -150,4 +150,11 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { getContext().getResources().getString(R.string.kg_prompt_reason_restart_password), false); } + + + @Test + public void testReset() { + mKeyguardAbsKeyInputViewController.reset(); + verify(mKeyguardMessageAreaController).setMessage("", false); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java index 01365b43b4b8..1a365ef9db17 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java @@ -25,9 +25,7 @@ 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 android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -39,6 +37,7 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.systemui.SysuiTestCase; import com.android.systemui.navigationbar.NavigationBarController; +import com.android.systemui.settings.FakeDisplayTracker; import org.junit.Before; import org.junit.Test; @@ -58,12 +57,12 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { @Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; @Mock - private DisplayManager mDisplayManager; - @Mock private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation; + private Executor mMainExecutor = Runnable::run; private Executor mBackgroundExecutor = Runnable::run; private KeyguardDisplayManager mManager; + private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); // The default and secondary displays are both in the default group private Display mDefaultDisplay; @@ -75,9 +74,9 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext.addMockSystemService(DisplayManager.class, mDisplayManager); mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController, - mKeyguardStatusViewComponentFactory, mBackgroundExecutor)); + mKeyguardStatusViewComponentFactory, mDisplayTracker, mMainExecutor, + mBackgroundExecutor)); doReturn(mKeyguardPresentation).when(mManager).createPresentation(any()); mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY, @@ -96,23 +95,21 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { @Test public void testShow_defaultDisplayOnly() { - when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay}); + mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay}); mManager.show(); verify(mManager, never()).createPresentation(any()); } @Test public void testShow_includeSecondaryDisplay() { - when(mDisplayManager.getDisplays()).thenReturn( - new Display[]{mDefaultDisplay, mSecondaryDisplay}); + mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay}); mManager.show(); verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay)); } @Test public void testShow_includeAlwaysUnlockedDisplay() { - when(mDisplayManager.getDisplays()).thenReturn( - new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay}); + mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay}); mManager.show(); verify(mManager, never()).createPresentation(any()); @@ -120,9 +117,8 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { @Test public void testShow_includeSecondaryAndAlwaysUnlockedDisplays() { - when(mDisplayManager.getDisplays()).thenReturn( + mDisplayTracker.setAllDisplays( new Display[]{mDefaultDisplay, mSecondaryDisplay, mAlwaysUnlockedDisplay}); - mManager.show(); verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay)); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java index 06082b61ec26..68dc6c04bc79 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java @@ -29,6 +29,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; @@ -52,6 +53,7 @@ public class KeyguardSliceViewControllerTest extends SysuiTestCase { private ConfigurationController mConfigurationController; @Mock private ActivityStarter mActivityStarter; + private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); private DumpManager mDumpManager = new DumpManager(); private KeyguardSliceViewController mController; @@ -63,7 +65,7 @@ public class KeyguardSliceViewControllerTest extends SysuiTestCase { when(mView.getContext()).thenReturn(mContext); mController = new KeyguardSliceViewController( mView, mActivityStarter, mConfigurationController, - mTunerService, mDumpManager); + mTunerService, mDumpManager, mDisplayTracker); mController.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index df6752a2b69d..d1650b776052 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -17,7 +17,6 @@ package com.android.keyguard; import static android.app.StatusBarManager.SESSION_KEYGUARD; -import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; @@ -41,7 +40,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -95,7 +93,6 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; -import android.provider.Settings; import android.service.dreams.IDreamManager; import android.service.trust.TrustAgentService; import android.telephony.ServiceState; @@ -238,8 +235,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private UiEventLogger mUiEventLogger; @Mock - private PowerManager mPowerManager; - @Mock private GlobalSettings mGlobalSettings; private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig; @Mock @@ -834,6 +829,19 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void doesNotTryToAuthenticateWhenKeyguardIsNotShowingButOccluded_whenAssistant() { + mKeyguardUpdateMonitor.setKeyguardShowing(false, true); + mKeyguardUpdateMonitor.setAssistantVisible(true); + + verify(mFaceManager, never()).authenticate(any(), + any(), + any(), + any(), + anyInt(), + anyBoolean()); + } + + @Test public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() { mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); mTestableLooper.processAllMessages(); @@ -846,6 +854,32 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void faceUnlockDoesNotRunWhenDeviceIsGoingToSleepWithAssistantVisible() { + mKeyguardUpdateMonitor.setKeyguardShowing(true, true); + mKeyguardUpdateMonitor.setAssistantVisible(true); + + verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); + mTestableLooper.processAllMessages(); + clearInvocations(mFaceManager); + + // Device going to sleep while assistant is visible + mKeyguardUpdateMonitor.handleStartedGoingToSleep(0); + mKeyguardUpdateMonitor.handleFinishedGoingToSleep(0); + mTestableLooper.moveTimeForward(DEFAULT_CANCEL_SIGNAL_TIMEOUT); + mTestableLooper.processAllMessages(); + + mKeyguardUpdateMonitor.handleKeyguardReset(); + + assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse(); + verify(mFaceManager, never()).authenticate(any(), + any(), + any(), + any(), + anyInt(), + anyBoolean()); + } + + @Test public void testIgnoresAuth_whenTrustAgentOnKeyguard_withoutBypass() { mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); mTestableLooper.processAllMessages(); @@ -1872,28 +1906,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testFingerAcquired_wakesUpPowerManager() { - cleanupKeyguardUpdateMonitor(); - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.bool.kg_wake_on_acquire_start, true); - mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); - fingerprintAcquireStart(); - - verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString()); - } - - @Test - public void testFingerAcquired_doesNotWakeUpPowerManager() { - cleanupKeyguardUpdateMonitor(); - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.bool.kg_wake_on_acquire_start, false); - mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); - fingerprintAcquireStart(); - - verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); - } - - @Test public void testDreamingStopped_faceDoesNotRun() { mKeyguardUpdateMonitor.dispatchDreamingStopped(); mTestableLooper.processAllMessages(); @@ -2374,11 +2386,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out"); } - private void fingerprintAcquireStart() { - mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback - .onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START); - } - private void deviceInPostureStateOpened() { mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_OPENED); } @@ -2525,7 +2532,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mAuthController, mTelephonyListenerManager, mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig, mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker, - mPowerManager, mTrustManager, mSubscriptionManager, mUserManager, + mTrustManager, mSubscriptionManager, mUserManager, mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager, mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager, mFaceWakeUpTriggersConfig, mDevicePostureController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt index 81d0034128b1..32edf8f23aed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt @@ -148,7 +148,7 @@ class ChooserSelectorTest : SysuiTestCase() { // Act whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true) - flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id)) + flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name)) // Assert verify(mockPackageManager, times(2)).setComponentEnabledSetting( @@ -175,7 +175,7 @@ class ChooserSelectorTest : SysuiTestCase() { // Act whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false) - flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id)) + flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name)) // Assert verify(mockPackageManager, times(2)).setComponentEnabledSetting( @@ -198,13 +198,13 @@ class ChooserSelectorTest : SysuiTestCase() { // Act whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false) - flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id + 1)) + flagListener.value.onFlagChanged(TestFlagEvent("other flag")) // Assert verifyZeroInteractions(mockPackageManager) } - private class TestFlagEvent(override val flagId: Int) : FlagListenable.FlagEvent { + private class TestFlagEvent(override val flagName: String) : FlagListenable.FlagEvent { override fun requestNoRestart() {} } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 0627fc6c542f..e918c1cc9b10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -89,6 +89,7 @@ import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl; import com.android.systemui.decor.PrivacyDotDecorProviderFactory; import com.android.systemui.decor.RoundedCornerResDelegate; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.tuner.TunerService; @@ -117,6 +118,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { private DisplayManager mDisplayManager; private SecureSettings mSecureSettings; private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); private FakeThreadFactory mThreadFactory; private ArrayList<DecorProvider> mPrivacyDecorProviders; private ArrayList<DecorProvider> mFaceScanningProviders; @@ -220,7 +222,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { mExecutor)); mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings, - mTunerService, mUserTracker, mDotViewController, mThreadFactory, + mTunerService, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) { @Override public void start() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java index 58b4af43a9b7..da419d177d46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java @@ -39,6 +39,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.CommandQueue; import org.junit.Before; @@ -76,6 +77,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { private IWindowMagnificationConnection mIWindowMagnificationConnection; private WindowMagnification mWindowMagnification; + private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); @Before public void setUp() throws Exception { @@ -88,7 +90,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { any(IWindowMagnificationConnection.class)); mWindowMagnification = new WindowMagnification(getContext(), getContext().getMainThreadHandler(), mCommandQueue, - mModeSwitchesController, mSysUiState, mOverviewProxyService); + mModeSwitchesController, mSysUiState, mOverviewProxyService, mDisplayTracker); mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier( mContext.getSystemService(DisplayManager.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index cdf3f654899e..f4505f5a914e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -78,6 +78,7 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.util.leak.ReferenceTestUtils; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.utils.os.FakeHandler; @@ -120,11 +121,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private Handler mHandler; private TestableWindowManager mWindowManager; - private SysUiState mSysUiState = new SysUiState(); + private SysUiState mSysUiState; private Resources mResources; private WindowMagnificationAnimationController mWindowMagnificationAnimationController; private WindowMagnificationController mWindowMagnificationController; private Instrumentation mInstrumentation; + private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0); @Before @@ -143,6 +145,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { return null; }).when(mSfVsyncFrameProvider).postFrameCallback( any(FrameCallback.class)); + mSysUiState = new SysUiState(mDisplayTracker); mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class)); when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then( returnsSecondArg()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java index ccf2f8b16f8a..f75dc03c7eae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java @@ -45,6 +45,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.CommandQueue; import org.junit.Before; @@ -74,6 +75,8 @@ public class WindowMagnificationTest extends SysuiTestCase { private CommandQueue mCommandQueue; private WindowMagnification mWindowMagnification; private OverviewProxyListener mOverviewProxyListener; + private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -87,10 +90,10 @@ public class WindowMagnificationTest extends SysuiTestCase { when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); - mCommandQueue = new CommandQueue(getContext()); + mCommandQueue = new CommandQueue(getContext(), new FakeDisplayTracker(getContext())); mWindowMagnification = new WindowMagnification(getContext(), getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController, - mSysUiState, mOverviewProxyService); + mSysUiState, mOverviewProxyService, mDisplayTracker); mWindowMagnification.start(); final ArgumentCaptor<OverviewProxyListener> listenerArgumentCaptor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index a61cd23b60fc..578e1d4d02ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -15,6 +15,7 @@ import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationTarget import android.view.SurfaceControl import android.view.ViewGroup +import android.widget.FrameLayout import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -26,6 +27,7 @@ import junit.framework.Assert.assertTrue import junit.framework.AssertionFailedError import kotlin.concurrent.thread import org.junit.After +import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Rule import org.junit.Test @@ -195,6 +197,13 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { verify(controller).onLaunchAnimationStart(anyBoolean()) } + @Test + fun creatingControllerFromNormalViewThrows() { + assertThrows(IllegalArgumentException::class.java) { + ActivityLaunchAnimator.Controller.fromView(FrameLayout(mContext)) + } + } + private fun fakeWindow(): RemoteAnimationTarget { val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */) val taskInfo = ActivityManager.RunningTaskInfo() diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt index cac4a0e5432c..1e62fd2332c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -26,6 +26,7 @@ import junit.framework.Assert.assertNull import junit.framework.Assert.assertTrue import org.junit.After import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Rule import org.junit.Test @@ -260,6 +261,13 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { assertThat(touchSurface.visibility).isEqualTo(View.GONE) } + @Test + fun creatingControllerFromNormalViewThrows() { + assertThrows(IllegalArgumentException::class.java) { + DialogLaunchAnimator.Controller.fromView(FrameLayout(mContext)) + } + } + private fun createAndShowDialog( animator: DialogLaunchAnimator = dialogLaunchAnimator, ): TestDialog { diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt index 3696ec540baf..0798d73cc2f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt @@ -16,58 +16,34 @@ package com.android.systemui.animation -import android.graphics.drawable.Drawable import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import android.view.View -import android.view.ViewGroup -import android.view.ViewParent +import android.widget.FrameLayout import androidx.test.filters.SmallTest -import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase -import org.junit.Before +import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mock -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() { - @Mock lateinit var interactionJankMonitor: InteractionJankMonitor - @Mock lateinit var view: View - @Mock lateinit var rootView: ViewGroup - @Mock lateinit var viewParent: ViewParent - @Mock lateinit var drawable: Drawable - lateinit var controller: GhostedViewLaunchAnimatorController - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - whenever(view.rootView).thenReturn(rootView) - whenever(view.background).thenReturn(drawable) - whenever(view.height).thenReturn(0) - whenever(view.width).thenReturn(0) - whenever(view.parent).thenReturn(viewParent) - whenever(view.visibility).thenReturn(View.VISIBLE) - whenever(view.invalidate()).then { /* NO-OP */ } - whenever(view.getLocationOnScreen(any())).then { /* NO-OP */ } - whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true) - whenever(interactionJankMonitor.end(anyInt())).thenReturn(true) - controller = GhostedViewLaunchAnimatorController(view, 0, interactionJankMonitor) - } - @Test fun animatingOrphanViewDoesNotCrash() { val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0) + val controller = GhostedViewLaunchAnimatorController(LaunchableFrameLayout(mContext)) controller.onIntentStarted(willAnimate = true) controller.onLaunchAnimationStart(isExpandingFullyAbove = true) controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f) controller.onLaunchAnimationEnd(isExpandingFullyAbove = true) } + + @Test + fun creatingControllerFromNormalViewThrows() { + assertThrows(IllegalArgumentException::class.java) { + GhostedViewLaunchAnimatorController(FrameLayout(mContext)) + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt new file mode 100644 index 000000000000..3bdbf9789f5c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt @@ -0,0 +1,87 @@ +package com.android.systemui.animation.back + +import android.util.DisplayMetrics +import android.window.BackEvent +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +private data class BackInput(val progressX: Float, val progressY: Float, val edge: Int) + +@SmallTest +@RunWith(JUnit4::class) +class BackAnimationSpecTest : SysuiTestCase() { + private var displayMetrics = + DisplayMetrics().apply { + widthPixels = 100 + heightPixels = 200 + density = 3f + } + + @Test + fun sysUi_floatingSystemSurfaces_animationValues() { + val maxX = 14.0f + val maxY = 4.0f + val minScale = 0.8f + + val backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics) + + assertBackTransformation( + backAnimationSpec = backAnimationSpec, + backInput = BackInput(progressX = 0f, progressY = 0f, edge = BackEvent.EDGE_LEFT), + expected = BackTransformation(translateX = 0f, translateY = 0f, scale = 1f), + ) + assertBackTransformation( + backAnimationSpec = backAnimationSpec, + backInput = BackInput(progressX = 1f, progressY = 0f, edge = BackEvent.EDGE_LEFT), + expected = BackTransformation(translateX = -maxX, translateY = 0f, scale = minScale), + ) + assertBackTransformation( + backAnimationSpec = backAnimationSpec, + backInput = BackInput(progressX = 1f, progressY = 0f, edge = BackEvent.EDGE_RIGHT), + expected = BackTransformation(translateX = maxX, translateY = 0f, scale = minScale), + ) + assertBackTransformation( + backAnimationSpec = backAnimationSpec, + backInput = BackInput(progressX = 1f, progressY = 1f, edge = BackEvent.EDGE_LEFT), + expected = BackTransformation(translateX = -maxX, translateY = -maxY, scale = minScale), + ) + assertBackTransformation( + backAnimationSpec = backAnimationSpec, + backInput = BackInput(progressX = 0f, progressY = 1f, edge = BackEvent.EDGE_LEFT), + expected = BackTransformation(translateX = 0f, translateY = -maxY, scale = 1f), + ) + assertBackTransformation( + backAnimationSpec = backAnimationSpec, + backInput = BackInput(progressX = 0f, progressY = -1f, edge = BackEvent.EDGE_LEFT), + expected = BackTransformation(translateX = 0f, translateY = maxY, scale = 1f), + ) + } +} + +private fun assertBackTransformation( + backAnimationSpec: BackAnimationSpec, + backInput: BackInput, + expected: BackTransformation, +) { + val actual = BackTransformation() + backAnimationSpec.getBackTransformation( + backEvent = + BackEvent( + /* touchX = */ 0f, + /* touchY = */ 0f, + /* progress = */ backInput.progressX, + /* swipeEdge = */ backInput.edge, + ), + progressY = backInput.progressY, + result = actual + ) + + val tolerance = 0f + assertThat(actual.translateX).isWithin(tolerance).of(expected.translateX) + assertThat(actual.translateY).isWithin(tolerance).of(expected.translateY) + assertThat(actual.scale).isWithin(tolerance).of(expected.scale) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt new file mode 100644 index 000000000000..190b3d25d16b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt @@ -0,0 +1,80 @@ +package com.android.systemui.animation.back + +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions + +@SmallTest +@RunWith(JUnit4::class) +class BackTransformationTest : SysuiTestCase() { + private val targetView: View = mock() + + @Test + fun defaultValue_noTransformation() { + val transformation = BackTransformation() + + assertThat(transformation.translateX).isNaN() + assertThat(transformation.translateY).isNaN() + assertThat(transformation.scale).isNaN() + } + + @Test + fun applyTo_targetView_translateX_Y_Scale() { + val transformation = BackTransformation(translateX = 0f, translateY = 0f, scale = 1f) + + transformation.applyTo(targetView = targetView) + + verify(targetView).translationX = 0f + verify(targetView).translationY = 0f + verify(targetView).scaleX = 1f + verify(targetView).scaleY = 1f + verifyNoMoreInteractions(targetView) + } + + @Test + fun applyTo_targetView_translateX() { + val transformation = BackTransformation(translateX = 1f) + + transformation.applyTo(targetView = targetView) + + verify(targetView).translationX = 1f + verifyNoMoreInteractions(targetView) + } + + @Test + fun applyTo_targetView_translateY() { + val transformation = BackTransformation(translateY = 2f) + + transformation.applyTo(targetView = targetView) + + verify(targetView).translationY = 2f + verifyNoMoreInteractions(targetView) + } + + @Test + fun applyTo_targetView_scale() { + val transformation = BackTransformation(scale = 3f) + + transformation.applyTo(targetView = targetView) + + verify(targetView).scaleX = 3f + verify(targetView).scaleY = 3f + verifyNoMoreInteractions(targetView) + } + + @Test + fun applyTo_targetView_noTransformation() { + val transformation = BackTransformation() + + transformation.applyTo(targetView = targetView) + + verifyNoMoreInteractions(targetView) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt new file mode 100644 index 000000000000..921f9a8fc7d6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt @@ -0,0 +1,63 @@ +package com.android.systemui.animation.back + +import android.util.DisplayMetrics +import android.window.BackEvent +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(JUnit4::class) +class OnBackAnimationCallbackExtensionTest : SysuiTestCase() { + private val onBackProgress: (BackTransformation) -> Unit = mock() + private val onBackStart: (BackEvent) -> Unit = mock() + private val onBackInvoke: () -> Unit = mock() + private val onBackCancel: () -> Unit = mock() + + private val displayMetrics = + DisplayMetrics().apply { + widthPixels = 100 + heightPixels = 100 + density = 1f + } + + private val onBackAnimationCallback = + onBackAnimationCallbackFrom( + backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics), + displayMetrics = displayMetrics, + onBackProgressed = onBackProgress, + onBackStarted = onBackStart, + onBackInvoked = onBackInvoke, + onBackCancelled = onBackCancel, + ) + + @Test + fun onBackProgressed_shouldInvoke_onBackProgress() { + val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT) + onBackAnimationCallback.onBackStarted(backEvent) + + onBackAnimationCallback.onBackProgressed(backEvent) + + verify(onBackProgress).invoke(BackTransformation(0f, 0f, 1f)) + } + + @Test + fun onBackStarted_shouldInvoke_onBackStart() { + val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT) + + onBackAnimationCallback.onBackStarted(backEvent) + + verify(onBackStart).invoke(backEvent) + } + + @Test + fun onBackInvoked_shouldInvoke_onBackInvoke() { + onBackAnimationCallback.onBackInvoked() + + verify(onBackInvoke).invoke() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index b92c5d039d45..fd931b0a794b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -51,14 +51,24 @@ import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG import android.view.WindowMetrics import androidx.test.filters.SmallTest import com.airbnb.lottie.LottieAnimationView +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.ViewMediatorCallback import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestableContext import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER +import com.android.systemui.keyguard.data.repository.FakeBiometricRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.recents.OverviewProxyService import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestCoroutineScope import org.junit.Before import org.junit.Rule import org.junit.Test @@ -101,6 +111,9 @@ class SideFpsControllerTest : SysuiTestCase() { @Captor lateinit var overlayCaptor: ArgumentCaptor<View> @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> + private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + private val featureFlags = FakeFeatureFlags() private val executor = FakeExecutor(FakeSystemClock()) private lateinit var overlayController: ISidefpsController private lateinit var sideFpsController: SideFpsController @@ -121,6 +134,24 @@ class SideFpsControllerTest : SysuiTestCase() { @Before fun setup() { + featureFlags.set(MODERN_ALTERNATE_BOUNCER, true) + keyguardBouncerRepository = + KeyguardBouncerRepository( + mock(ViewMediatorCallback::class.java), + FakeSystemClock(), + TestCoroutineScope(), + mock(TableLogBuffer::class.java), + ) + alternateBouncerInteractor = + AlternateBouncerInteractor( + keyguardBouncerRepository, + FakeBiometricRepository(), + FakeDeviceEntryFingerprintAuthRepository(), + FakeSystemClock(), + mock(KeyguardUpdateMonitor::class.java), + featureFlags, + ) + context.addMockSystemService(DisplayManager::class.java, displayManager) context.addMockSystemService(WindowManager::class.java, windowManager) @@ -217,7 +248,10 @@ class SideFpsControllerTest : SysuiTestCase() { displayManager, executor, handler, - dumpManager + alternateBouncerInteractor, + TestCoroutineScope(), + featureFlags, + dumpManager, ) overlayController = @@ -507,6 +541,26 @@ class SideFpsControllerTest : SysuiTestCase() { private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) { sideFpsController.overlayOffsets = sensorLocation + } + + fun alternateBouncerVisibility_showAndHideSideFpsUI() = testWithDisplay { + // WHEN alternate bouncer is visible + keyguardBouncerRepository.setAlternateVisible(true) + executor.runAllReady() + + // THEN side fps shows UI + verify(windowManager).addView(any(), any()) + verify(windowManager, never()).removeView(any()) + + // WHEN alternate bouncer is no longer visible + keyguardBouncerRepository.setAlternateVisible(false) + executor.runAllReady() + + // THEN side fps UI is hidden + verify(windowManager).removeView(any()) + } + + private fun hidesWithTaskbar(visible: Boolean) { overlayController.show(SENSOR_ID, REASON_UNKNOWN) executor.runAllReady() @@ -515,7 +569,7 @@ class SideFpsControllerTest : SysuiTestCase() { verify(windowManager).addView(any(), any()) verify(windowManager, never()).removeView(any()) - verify(sideFpsView).visibility = if (sfpsViewVisible) View.VISIBLE else View.GONE + verify(sideFpsView).visibility = if (visible) View.VISIBLE else View.GONE } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java index 9c32c38e665c..dbbc2663a879 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java @@ -37,7 +37,6 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shade.ShadeExpansionListener; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; @@ -71,7 +70,6 @@ public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase { protected @Mock SystemUIDialogManager mDialogManager; protected @Mock UdfpsController mUdfpsController; protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator; - protected @Mock KeyguardBouncer mBouncer; protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor; protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor; @@ -149,11 +147,8 @@ public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase { protected UdfpsKeyguardViewController createUdfpsKeyguardViewController( boolean useModernBouncer, boolean useExpandedOverlay) { - mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer); mFeatureFlags.set(Flags.MODERN_ALTERNATE_BOUNCER, useModernBouncer); mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay); - when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn( - useModernBouncer ? null : mBouncer); UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController( mView, mStatusBarStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index 7715f7fc5dc9..f437a8f009f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -32,24 +32,17 @@ import android.view.MotionEvent; import androidx.test.filters.SmallTest; -import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; import com.android.systemui.shade.ShadeExpansionListener; import com.android.systemui.statusbar.StatusBarState; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest { - private @Captor ArgumentCaptor<PrimaryBouncerExpansionCallback> - mBouncerExpansionCallbackCaptor; - private PrimaryBouncerExpansionCallback mBouncerExpansionCallback; - @Override public UdfpsKeyguardViewController createUdfpsKeyguardViewController() { return createUdfpsKeyguardViewController(/* useModernBouncer */ false, @@ -62,11 +55,9 @@ public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewController captureStatusBarStateListeners(); sendStatusBarStateChanged(StatusBarState.KEYGUARD); - captureBouncerExpansionCallback(); when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true); when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true); - mBouncerExpansionCallback.onVisibilityChanged(true); - + when(mView.getUnpausedAlpha()).thenReturn(0); assertTrue(mController.shouldPauseAuth()); } @@ -304,11 +295,6 @@ public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewController verify(mView, atLeastOnce()).setPauseAuth(false); } - private void captureBouncerExpansionCallback() { - verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture()); - mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue(); - } - @Test // TODO(b/259264861): Tracking Bug public void testUdfpsExpandedOverlayOn() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt index 9060922266c0..81a6bc2b25b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.BouncerView import com.android.systemui.keyguard.data.repository.BiometricRepository +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor @@ -91,6 +92,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle AlternateBouncerInteractor( keyguardBouncerRepository, mock(BiometricRepository::class.java), + mock(DeviceEntryFingerprintAuthRepository::class.java), mock(SystemClock::class.java), mock(KeyguardUpdateMonitor::class.java), mock(FeatureFlags::class.java) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt index 34ddf795c7e7..8e20303fd189 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt @@ -110,6 +110,28 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() expectedInteractionEvent = InteractionEvent.UP, expectedPointerOnSensorId = INVALID_POINTER_ID, ), + // MotionEvent.ACTION_HOVER_ENTER + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_HOVER_ENTER, + previousPointerOnSensorId = INVALID_POINTER_ID, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), + expectedInteractionEvent = InteractionEvent.DOWN, + expectedPointerOnSensorId = POINTER_ID_1, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_HOVER_ENTER, + previousPointerOnSensorId = INVALID_POINTER_ID, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_HOVER_ENTER, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), // MotionEvent.ACTION_MOVE genPositiveTestCases( motionEventAction = MotionEvent.ACTION_MOVE, @@ -161,6 +183,35 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() expectedInteractionEvent = InteractionEvent.UNCHANGED, expectedPointerOnSensorId = POINTER_ID_2, ), + // MotionEvent.ACTION_HOVER_MOVE + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_HOVER_MOVE, + previousPointerOnSensorId = INVALID_POINTER_ID, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), + expectedInteractionEvent = InteractionEvent.DOWN, + expectedPointerOnSensorId = POINTER_ID_1, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_HOVER_MOVE, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = POINTER_ID_1, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_HOVER_MOVE, + previousPointerOnSensorId = INVALID_POINTER_ID, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_HOVER_MOVE, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), // MotionEvent.ACTION_UP genPositiveTestCases( motionEventAction = MotionEvent.ACTION_UP, @@ -183,6 +234,28 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() expectedInteractionEvent = InteractionEvent.UNCHANGED, expectedPointerOnSensorId = INVALID_POINTER_ID, ), + // MotionEvent.ACTION_HOVER_EXIT + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_HOVER_EXIT, + previousPointerOnSensorId = INVALID_POINTER_ID, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_HOVER_EXIT, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_HOVER_EXIT, + previousPointerOnSensorId = INVALID_POINTER_ID, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), // MotionEvent.ACTION_CANCEL genPositiveTestCases( motionEventAction = MotionEvent.ACTION_CANCEL, @@ -315,13 +388,7 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() expectedPointerOnSensorId = POINTER_ID_2 ) ) - .flatten() + - listOf( - genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_ENTER), - genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_MOVE), - genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT) - ) - .flatten() + .flatten() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java index bdd496ec219b..71c335e6b173 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.clipboardoverlay; -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED; - import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE; import static org.junit.Assert.assertEquals; @@ -33,7 +31,6 @@ import android.content.ClipData; import android.content.ClipDescription; import android.content.ClipboardManager; import android.os.PersistableBundle; -import android.provider.DeviceConfig; import android.provider.Settings; import androidx.test.filters.SmallTest; @@ -41,9 +38,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.util.DeviceConfigProxyFake; import org.junit.Before; import org.junit.Test; @@ -63,18 +57,11 @@ public class ClipboardListenerTest extends SysuiTestCase { @Mock private ClipboardManager mClipboardManager; @Mock - private ClipboardOverlayControllerLegacyFactory mClipboardOverlayControllerLegacyFactory; - @Mock - private ClipboardOverlayControllerLegacy mOverlayControllerLegacy; - @Mock private ClipboardOverlayController mOverlayController; @Mock private ClipboardToast mClipboardToast; @Mock private UiEventLogger mUiEventLogger; - @Mock - private FeatureFlags mFeatureFlags; - private DeviceConfigProxyFake mDeviceConfigProxy; private ClipData mSampleClipData; private String mSampleSource = "Example source"; @@ -97,8 +84,6 @@ public class ClipboardListenerTest extends SysuiTestCase { mOverlayControllerProvider = () -> mOverlayController; MockitoAnnotations.initMocks(this); - when(mClipboardOverlayControllerLegacyFactory.create(any())) - .thenReturn(mOverlayControllerLegacy); when(mClipboardManager.hasPrimaryClip()).thenReturn(true); Settings.Secure.putInt( mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 1); @@ -108,26 +93,13 @@ public class ClipboardListenerTest extends SysuiTestCase { when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData); when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource); - mDeviceConfigProxy = new DeviceConfigProxyFake(); - - mClipboardListener = new ClipboardListener(getContext(), mDeviceConfigProxy, - mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory, - mClipboardToast, mClipboardManager, mUiEventLogger, mFeatureFlags); + mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider, + mClipboardToast, mClipboardManager, mUiEventLogger); } - @Test - public void test_disabled() { - mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, - "false", false); - mClipboardListener.start(); - verifyZeroInteractions(mClipboardManager); - verifyZeroInteractions(mUiEventLogger); - } @Test - public void test_enabled() { - mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, - "true", false); + public void test_initialization() { mClipboardListener.start(); verify(mClipboardManager).addPrimaryClipChangedListener(any()); verifyZeroInteractions(mUiEventLogger); @@ -135,45 +107,6 @@ public class ClipboardListenerTest extends SysuiTestCase { @Test public void test_consecutiveCopies() { - when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false); - - mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, - "true", false); - mClipboardListener.start(); - mClipboardListener.onPrimaryClipChanged(); - - verify(mClipboardOverlayControllerLegacyFactory).create(any()); - - verify(mOverlayControllerLegacy).setClipData( - mClipDataCaptor.capture(), mStringCaptor.capture()); - - assertEquals(mSampleClipData, mClipDataCaptor.getValue()); - assertEquals(mSampleSource, mStringCaptor.getValue()); - - verify(mOverlayControllerLegacy).setOnSessionCompleteListener(mRunnableCaptor.capture()); - - // Should clear the overlay controller - mRunnableCaptor.getValue().run(); - - mClipboardListener.onPrimaryClipChanged(); - - verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any()); - - // Not calling the runnable here, just change the clip again and verify that the overlay is - // NOT recreated. - - mClipboardListener.onPrimaryClipChanged(); - - verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any()); - verifyZeroInteractions(mOverlayControllerProvider); - } - - @Test - public void test_consecutiveCopies_new() { - when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true); - - mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, - "true", false); mClipboardListener.start(); mClipboardListener.onPrimaryClipChanged(); @@ -200,7 +133,6 @@ public class ClipboardListenerTest extends SysuiTestCase { mClipboardListener.onPrimaryClipChanged(); verify(mOverlayControllerProvider, times(2)).get(); - verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory); } @Test @@ -231,23 +163,6 @@ public class ClipboardListenerTest extends SysuiTestCase { @Test public void test_logging_enterAndReenter() { - when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false); - - mClipboardListener.start(); - - mClipboardListener.onPrimaryClipChanged(); - mClipboardListener.onPrimaryClipChanged(); - - verify(mUiEventLogger, times(1)).log( - ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource); - verify(mUiEventLogger, times(1)).log( - ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource); - } - - @Test - public void test_logging_enterAndReenter_new() { - when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true); - mClipboardListener.start(); mClipboardListener.onPrimaryClipChanged(); @@ -271,6 +186,5 @@ public class ClipboardListenerTest extends SysuiTestCase { ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource); verify(mClipboardToast, times(1)).showCopiedToast(); verifyZeroInteractions(mOverlayControllerProvider); - verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index b4e85c06933a..ca5b7af5695a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -47,6 +47,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.screenshot.TimeoutHandler; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -81,6 +82,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { private ClipboardOverlayUtils mClipboardUtils; @Mock private UiEventLogger mUiEventLogger; + private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock @@ -116,7 +118,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mFeatureFlags, mClipboardUtils, mExecutor, - mUiEventLogger); + mUiEventLogger, + mDisplayTracker); verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture()); mCallbacks = mOverlayCallbacksCaptor.getValue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt new file mode 100644 index 000000000000..fe352fd078f0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.common.ui.view + +import android.view.ViewConfiguration +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel +import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel.Down +import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel.Move +import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel.Up +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class LongPressHandlingViewInteractionHandlerTest : SysuiTestCase() { + + @Mock private lateinit var postDelayed: (Runnable, Long) -> DisposableHandle + @Mock private lateinit var onLongPressDetected: (Int, Int) -> Unit + @Mock private lateinit var onSingleTapDetected: () -> Unit + + private lateinit var underTest: LongPressHandlingViewInteractionHandler + + private var isAttachedToWindow: Boolean = true + private var delayedRunnable: Runnable? = null + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(postDelayed.invoke(any(), any())).thenAnswer { invocation -> + delayedRunnable = invocation.arguments[0] as Runnable + DisposableHandle { delayedRunnable = null } + } + + underTest = + LongPressHandlingViewInteractionHandler( + postDelayed = postDelayed, + isAttachedToWindow = { isAttachedToWindow }, + onLongPressDetected = onLongPressDetected, + onSingleTapDetected = onSingleTapDetected, + ) + underTest.isLongPressHandlingEnabled = true + } + + @Test + fun `long-press`() = runTest { + val downX = 123 + val downY = 456 + dispatchTouchEvents( + Down( + x = downX, + y = downY, + ), + Move( + distanceMoved = ViewConfiguration.getTouchSlop() - 0.1f, + ), + ) + delayedRunnable?.run() + + verify(onLongPressDetected).invoke(downX, downY) + verify(onSingleTapDetected, never()).invoke() + } + + @Test + fun `long-press but feature not enabled`() = runTest { + underTest.isLongPressHandlingEnabled = false + dispatchTouchEvents( + Down( + x = 123, + y = 456, + ), + ) + + assertThat(delayedRunnable).isNull() + verify(onLongPressDetected, never()).invoke(any(), any()) + verify(onSingleTapDetected, never()).invoke() + } + + @Test + fun `long-press but view not attached`() = runTest { + isAttachedToWindow = false + dispatchTouchEvents( + Down( + x = 123, + y = 456, + ), + ) + delayedRunnable?.run() + + verify(onLongPressDetected, never()).invoke(any(), any()) + verify(onSingleTapDetected, never()).invoke() + } + + @Test + fun `dragged too far to be considered a long-press`() = runTest { + dispatchTouchEvents( + Down( + x = 123, + y = 456, + ), + Move( + distanceMoved = ViewConfiguration.getTouchSlop() + 0.1f, + ), + ) + + assertThat(delayedRunnable).isNull() + verify(onLongPressDetected, never()).invoke(any(), any()) + verify(onSingleTapDetected, never()).invoke() + } + + @Test + fun `held down too briefly to be considered a long-press`() = runTest { + dispatchTouchEvents( + Down( + x = 123, + y = 456, + ), + Up( + distanceMoved = ViewConfiguration.getTouchSlop().toFloat(), + gestureDuration = ViewConfiguration.getLongPressTimeout() - 1L, + ), + ) + + assertThat(delayedRunnable).isNull() + verify(onLongPressDetected, never()).invoke(any(), any()) + verify(onSingleTapDetected).invoke() + } + + private fun dispatchTouchEvents( + vararg models: MotionEventModel, + ) { + models.forEach { model -> underTest.onTouchEvent(model) } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index 25f471b0d3e0..d54babfbdc0e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.backup.BackupHelper import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.controls.panels.AuthorizedPanelsRepository import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dump.DumpManager import com.android.systemui.settings.UserFileManager @@ -66,6 +67,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` +import org.mockito.Mockito.clearInvocations import org.mockito.MockitoAnnotations @SmallTest @@ -88,6 +90,8 @@ class ControlsControllerImplTest : SysuiTestCase() { private lateinit var userTracker: UserTracker @Mock private lateinit var userFileManager: UserFileManager + @Mock + private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository @Captor private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo> @@ -168,6 +172,7 @@ class ControlsControllerImplTest : SysuiTestCase() { listingController, userFileManager, userTracker, + authorizedPanelsRepository, Optional.of(persistenceWrapper), mock(DumpManager::class.java) ) @@ -224,6 +229,7 @@ class ControlsControllerImplTest : SysuiTestCase() { listingController, userFileManager, userTracker, + authorizedPanelsRepository, Optional.of(persistenceWrapper), mock(DumpManager::class.java) ) @@ -231,6 +237,26 @@ class ControlsControllerImplTest : SysuiTestCase() { } @Test + fun testAddAuthorizedPackagesFromSavedFavoritesOnStart() { + clearInvocations(authorizedPanelsRepository) + `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_STRUCTURE_INFO)) + ControlsControllerImpl( + mContext, + delayableExecutor, + uiController, + bindingController, + listingController, + userFileManager, + userTracker, + authorizedPanelsRepository, + Optional.of(persistenceWrapper), + mock(DumpManager::class.java) + ) + verify(authorizedPanelsRepository) + .addAuthorizedPanels(setOf(TEST_STRUCTURE_INFO.componentName.packageName)) + } + + @Test fun testOnActionResponse() { controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt index 765c4c0ac0f0..226ef3b85706 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt @@ -21,12 +21,15 @@ import android.content.res.Resources import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater +import android.view.View import androidx.test.filters.SmallTest import com.android.settingslib.core.lifecycle.Lifecycle import com.android.systemui.SysuiTestCase import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -36,8 +39,10 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import java.text.Collator @SmallTest @RunWith(AndroidTestingRunner::class) @@ -49,25 +54,18 @@ class AppAdapterTest : SysuiTestCase() { @Mock lateinit var lifecycle: Lifecycle @Mock lateinit var controlsListingController: ControlsListingController @Mock lateinit var layoutInflater: LayoutInflater - @Mock lateinit var onAppSelected: (ComponentName?) -> Unit + @Mock lateinit var onAppSelected: (ControlsServiceInfo) -> Unit @Mock lateinit var favoritesRenderer: FavoritesRenderer val resources: Resources = context.resources lateinit var adapter: AppAdapter @Before fun setUp() { MockitoAnnotations.initMocks(this) - adapter = AppAdapter(backgroundExecutor, - uiExecutor, - lifecycle, - controlsListingController, - layoutInflater, - onAppSelected, - favoritesRenderer, - resources) } @Test fun testOnServicesUpdated_nullLoadLabel() { + adapter = createAdapterWithAuthorizedPanels(emptySet()) val captor = ArgumentCaptor .forClass(ControlsListingController.ControlsListingCallback::class.java) val controlsServiceInfo = mock<ControlsServiceInfo>() @@ -76,14 +74,14 @@ class AppAdapterTest : SysuiTestCase() { verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture()) captor.value.onServicesUpdated(serviceInfo) - backgroundExecutor.runAllReady() - uiExecutor.runAllReady() + FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor) assertThat(adapter.itemCount).isEqualTo(serviceInfo.size) } @Test - fun testOnServicesUpdatedDoesntHavePanels() { + fun testOnServicesUpdated_showsNotAuthorizedPanels() { + adapter = createAdapterWithAuthorizedPanels(emptySet()) val captor = ArgumentCaptor .forClass(ControlsListingController.ControlsListingCallback::class.java) val serviceInfo = listOf( @@ -93,20 +91,88 @@ class AppAdapterTest : SysuiTestCase() { verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture()) captor.value.onServicesUpdated(serviceInfo) - backgroundExecutor.runAllReady() - uiExecutor.runAllReady() + FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor) + + assertThat(adapter.itemCount).isEqualTo(2) + } + + @Test + fun testOnServicesUpdated_doesntShowAuthorizedPanels() { + adapter = createAdapterWithAuthorizedPanels(setOf(TEST_PACKAGE)) + + val captor = ArgumentCaptor + .forClass(ControlsListingController.ControlsListingCallback::class.java) + val serviceInfo = listOf( + ControlsServiceInfo("no panel", null), + ControlsServiceInfo("panel", ComponentName(TEST_PACKAGE, "cls")) + ) + verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture()) + + captor.value.onServicesUpdated(serviceInfo) + FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor) assertThat(adapter.itemCount).isEqualTo(1) } - fun ControlsServiceInfo( - label: CharSequence, - panelComponentName: ComponentName? = null - ): ControlsServiceInfo { - return mock { - `when`(this.loadLabel()).thenReturn(label) - `when`(this.panelActivity).thenReturn(panelComponentName) - `when`(this.loadIcon()).thenReturn(mock()) + @Test + fun testOnBindSetsClickListenerToCallOnAppSelected() { + adapter = createAdapterWithAuthorizedPanels(emptySet()) + + val captor = ArgumentCaptor + .forClass(ControlsListingController.ControlsListingCallback::class.java) + val serviceInfo = listOf( + ControlsServiceInfo("no panel", null), + ControlsServiceInfo("panel", ComponentName(TEST_PACKAGE, "cls")) + ) + verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture()) + + captor.value.onServicesUpdated(serviceInfo) + FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor) + + val sorted = serviceInfo.sortedWith( + compareBy(Collator.getInstance(resources.configuration.locales[0])) { + it.loadLabel() ?: "" + }) + + sorted.forEachIndexed { index, info -> + val fakeView: View = mock() + val fakeHolder: AppAdapter.Holder = mock() + `when`(fakeHolder.view).thenReturn(fakeView) + + clearInvocations(onAppSelected) + adapter.onBindViewHolder(fakeHolder, index) + val listenerCaptor: ArgumentCaptor<View.OnClickListener> = argumentCaptor() + verify(fakeView).setOnClickListener(capture(listenerCaptor)) + listenerCaptor.value.onClick(fakeView) + + verify(onAppSelected).invoke(info) + } + } + + private fun createAdapterWithAuthorizedPanels(packages: Set<String>): AppAdapter { + return AppAdapter(backgroundExecutor, + uiExecutor, + lifecycle, + controlsListingController, + layoutInflater, + onAppSelected, + favoritesRenderer, + resources, + packages) + } + + companion object { + private fun ControlsServiceInfo( + label: CharSequence, + panelComponentName: ComponentName? = null + ): ControlsServiceInfo { + return mock { + `when`(loadLabel()).thenReturn(label) + `when`(panelActivity).thenReturn(panelComponentName) + `when`(loadIcon()).thenReturn(mock()) + } } + + private const val TEST_PACKAGE = "package" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt index 56c3efe1b8e6..8dfd22378a14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt @@ -16,7 +16,13 @@ package com.android.systemui.controls.management +import android.app.Dialog +import android.content.ComponentName import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.ServiceInfo +import android.graphics.drawable.Drawable +import android.os.Bundle import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.window.OnBackInvokedCallback @@ -25,14 +31,23 @@ import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController -import com.android.systemui.controls.ui.ControlsUiController +import com.android.systemui.controls.panels.AuthorizedPanelsRepository import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.settings.UserTracker +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.CountDownLatch import java.util.concurrent.Executor +import java.util.function.Consumer import org.junit.Before import org.junit.Rule import org.junit.Test @@ -41,7 +56,11 @@ import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations @SmallTest @@ -58,9 +77,10 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { @Mock lateinit var userTracker: UserTracker - @Mock lateinit var uiController: ControlsUiController + @Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository + + @Mock lateinit var dialogFactory: PanelConfirmationDialogFactory - private lateinit var controlsProviderSelectorActivity: ControlsProviderSelectorActivity_Factory private var latch: CountDownLatch = CountDownLatch(1) @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher @@ -81,7 +101,8 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { listingController, controlsController, userTracker, - uiController, + authorizedPanelsRepository, + dialogFactory, mockDispatcher, latch ) @@ -113,13 +134,99 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value) } - public class TestableControlsProviderSelectorActivity( + @Test + fun testOnAppSelectedForNonPanelStartsFavoritingActivity() { + val info = ControlsServiceInfo(ComponentName("test_pkg", "service"), "", null) + activityRule.activity.onAppSelected(info) + + verifyNoMoreInteractions(dialogFactory) + + assertThat(activityRule.activity.lastStartedActivity?.component?.className) + .isEqualTo(ControlsFavoritingActivity::class.java.name) + + assertThat(activityRule.activity.triedToFinish).isTrue() + } + + @Test + fun testOnAppSelectedForPanelTriggersDialog() { + val label = "label" + val info = + ControlsServiceInfo( + ComponentName("test_pkg", "service"), + label, + ComponentName("test_pkg", "activity") + ) + + val dialog: Dialog = mock() + whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog) + + activityRule.activity.onAppSelected(info) + verify(dialogFactory).createConfirmationDialog(any(), eq(label), any()) + verify(dialog).show() + + assertThat(activityRule.activity.triedToFinish).isFalse() + } + + @Test + fun dialogAcceptAddsPackage() { + val label = "label" + val info = + ControlsServiceInfo( + ComponentName("test_pkg", "service"), + label, + ComponentName("test_pkg", "activity") + ) + + val dialog: Dialog = mock() + whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog) + + activityRule.activity.onAppSelected(info) + + val captor: ArgumentCaptor<Consumer<Boolean>> = argumentCaptor() + verify(dialogFactory).createConfirmationDialog(any(), any(), capture(captor)) + + captor.value.accept(true) + + val setCaptor: ArgumentCaptor<Set<String>> = argumentCaptor() + verify(authorizedPanelsRepository).addAuthorizedPanels(capture(setCaptor)) + assertThat(setCaptor.value).containsExactly(info.componentName.packageName) + + assertThat(activityRule.activity.triedToFinish).isTrue() + } + + @Test + fun dialogCancelDoesntAddPackage() { + val label = "label" + val info = + ControlsServiceInfo( + ComponentName("test_pkg", "service"), + label, + ComponentName("test_pkg", "activity") + ) + + val dialog: Dialog = mock() + whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog) + + activityRule.activity.onAppSelected(info) + + val captor: ArgumentCaptor<Consumer<Boolean>> = argumentCaptor() + verify(dialogFactory).createConfirmationDialog(any(), any(), capture(captor)) + + captor.value.accept(false) + + verify(authorizedPanelsRepository, never()).addAuthorizedPanels(any()) + + assertThat(activityRule.activity.triedToFinish).isFalse() + } + + class TestableControlsProviderSelectorActivity( executor: Executor, backExecutor: Executor, listingController: ControlsListingController, controlsController: ControlsController, userTracker: UserTracker, - uiController: ControlsUiController, + authorizedPanelsRepository: AuthorizedPanelsRepository, + dialogFactory: PanelConfirmationDialogFactory, private val mockDispatcher: OnBackInvokedDispatcher, private val latch: CountDownLatch ) : @@ -129,16 +236,50 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { listingController, controlsController, userTracker, - uiController + authorizedPanelsRepository, + dialogFactory ) { + + var lastStartedActivity: Intent? = null + var triedToFinish = false + override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher { return mockDispatcher } + override fun startActivity(intent: Intent?, options: Bundle?) { + lastStartedActivity = intent + } + override fun onStop() { super.onStop() // ensures that test runner thread does not proceed until ui thread is done latch.countDown() } + + override fun animateExitAndFinish() { + // Activity should only be finished from the rule. + triedToFinish = true + } + } + + companion object { + private fun ControlsServiceInfo( + componentName: ComponentName, + label: CharSequence, + panelComponentName: ComponentName? = null + ): ControlsServiceInfo { + val serviceInfo = + ServiceInfo().apply { + applicationInfo = ApplicationInfo() + packageName = componentName.packageName + name = componentName.className + } + return Mockito.spy(ControlsServiceInfo(mock(), serviceInfo)).apply { + doReturn(label).`when`(this).loadLabel() + doReturn(mock<Drawable>()).`when`(this).loadIcon() + doReturn(panelComponentName).`when`(this).panelActivity + } + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt new file mode 100644 index 000000000000..756f267671e1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.controls.management + +import android.content.DialogInterface +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class PanelConfirmationDialogFactoryTest : SysuiTestCase() { + + @Test + fun testDialogHasCorrectInfo() { + val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) } + val factory = PanelConfirmationDialogFactory { mockDialog } + val appName = "appName" + + factory.createConfirmationDialog(context, appName) {} + + verify(mockDialog).setCanceledOnTouchOutside(true) + verify(mockDialog) + .setTitle(context.getString(R.string.controls_panel_authorization_title, appName)) + verify(mockDialog) + .setMessage(context.getString(R.string.controls_panel_authorization, appName)) + } + + @Test + fun testDialogPositiveButton() { + val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) } + val factory = PanelConfirmationDialogFactory { mockDialog } + + var response: Boolean? = null + + factory.createConfirmationDialog(context, "") { response = it } + + val captor: ArgumentCaptor<DialogInterface.OnClickListener> = argumentCaptor() + verify(mockDialog).setPositiveButton(eq(R.string.controls_dialog_ok), capture(captor)) + + captor.value.onClick(mockDialog, DialogInterface.BUTTON_POSITIVE) + + assertThat(response).isTrue() + } + + @Test + fun testDialogNeutralButton() { + val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) } + val factory = PanelConfirmationDialogFactory { mockDialog } + + var response: Boolean? = null + + factory.createConfirmationDialog(context, "") { response = it } + + val captor: ArgumentCaptor<DialogInterface.OnClickListener> = argumentCaptor() + verify(mockDialog).setNeutralButton(eq(R.string.cancel), capture(captor)) + + captor.value.onClick(mockDialog, DialogInterface.BUTTON_NEUTRAL) + + assertThat(response).isFalse() + } + + @Test + fun testDialogCancel() { + val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) } + val factory = PanelConfirmationDialogFactory { mockDialog } + + var response: Boolean? = null + + factory.createConfirmationDialog(context, "") { response = it } + + val captor: ArgumentCaptor<DialogInterface.OnCancelListener> = argumentCaptor() + verify(mockDialog).setOnCancelListener(capture(captor)) + + captor.value.onCancel(mockDialog) + + assertThat(response).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt new file mode 100644 index 000000000000..b91a3fd4b28c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.controls.panels + +import android.content.SharedPreferences +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.FakeSharedPreferences +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import java.io.File +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() { + + @Mock private lateinit var userTracker: UserTracker + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mContext.orCreateTestableResources.addOverride( + R.array.config_controlsPreferredPackages, + arrayOf<String>() + ) + whenever(userTracker.userId).thenReturn(0) + } + + @Test + fun testPreApprovedPackagesAreSeededIfNoSavedPreferences() { + mContext.orCreateTestableResources.addOverride( + R.array.config_controlsPreferredPackages, + arrayOf(TEST_PACKAGE) + ) + val sharedPrefs = FakeSharedPreferences() + val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs)) + val repository = createRepository(fileManager) + + assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE) + assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE) + } + + @Test + fun testPreApprovedPackagesNotSeededIfEmptySavedPreferences() { + mContext.orCreateTestableResources.addOverride( + R.array.config_controlsPreferredPackages, + arrayOf(TEST_PACKAGE) + ) + val sharedPrefs = FakeSharedPreferences() + sharedPrefs.edit().putStringSet(KEY, emptySet()).apply() + val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs)) + createRepository(fileManager) + + assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty() + } + + @Test + fun testPreApprovedPackagesOnlySetForUserThatDoesntHaveThem() { + mContext.orCreateTestableResources.addOverride( + R.array.config_controlsPreferredPackages, + arrayOf(TEST_PACKAGE) + ) + val sharedPrefs_0 = FakeSharedPreferences() + val sharedPrefs_1 = FakeSharedPreferences() + sharedPrefs_1.edit().putStringSet(KEY, emptySet()).apply() + val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs_0, 1 to sharedPrefs_1)) + val repository = createRepository(fileManager) + + assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE) + whenever(userTracker.userId).thenReturn(1) + assertThat(repository.getAuthorizedPanels()).isEmpty() + } + + @Test + fun testGetAuthorizedPackages() { + val sharedPrefs = FakeSharedPreferences() + sharedPrefs.edit().putStringSet(KEY, mutableSetOf(TEST_PACKAGE)).apply() + val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs)) + + val repository = createRepository(fileManager) + assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE) + } + + @Test + fun testSetAuthorizedPackage() { + val sharedPrefs = FakeSharedPreferences() + val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs)) + + val repository = createRepository(fileManager) + repository.addAuthorizedPanels(setOf(TEST_PACKAGE)) + assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE) + } + + private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl { + return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker) + } + + private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) : + UserFileManager { + override fun getFile(fileName: String, userId: Int): File { + throw UnsupportedOperationException() + } + + override fun getSharedPreferences( + fileName: String, + mode: Int, + userId: Int + ): SharedPreferences { + if (fileName != FILE_NAME) { + throw IllegalArgumentException("Preference files must be $FILE_NAME") + } + return sharedPrefs.getValue(userId) + } + } + + companion object { + private const val FILE_NAME = "controls_prefs" + private const val KEY = "authorized_panels" + private const val TEST_PACKAGE = "package" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt index edc6882e71c0..85f9961bf449 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt @@ -20,6 +20,7 @@ import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager import android.content.pm.ServiceInfo import android.os.UserHandle import android.service.controls.ControlsProviderService @@ -39,8 +40,10 @@ import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsProviderSelectorActivity +import com.android.systemui.controls.panels.AuthorizedPanelsRepository import com.android.systemui.controls.settings.FakeControlsSettingsRepository import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker @@ -91,6 +94,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() { @Mock lateinit var userTracker: UserTracker @Mock lateinit var taskViewFactory: TaskViewFactory @Mock lateinit var dumpManager: DumpManager + @Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository + @Mock lateinit var featureFlags: FeatureFlags + @Mock lateinit var packageManager: PackageManager val sharedPreferences = FakeSharedPreferences() lateinit var controlsSettingsRepository: FakeControlsSettingsRepository @@ -120,6 +126,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { ControlsUiControllerImpl( Lazy { controlsController }, context, + packageManager, uiExecutor, bgExecutor, Lazy { controlsListingController }, @@ -132,6 +139,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() { userTracker, Optional.of(taskViewFactory), controlsSettingsRepository, + authorizedPanelsRepository, + featureFlags, dumpManager ) `when`( @@ -240,7 +249,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() { @Test fun testPanelCallsTaskViewFactoryCreate() { mockLayoutInflater() - val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls")) + val packageName = "pkg" + `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName)) + val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls")) val serviceInfo = setUpPanel(panel) underTest.show(parent, {}, context) @@ -258,9 +269,11 @@ class ControlsUiControllerImplTest : SysuiTestCase() { @Test fun testPanelControllerStartActivityWithCorrectArguments() { mockLayoutInflater() + val packageName = "pkg" + `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName)) controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true) - val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls")) + val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls")) val serviceInfo = setUpPanel(panel) underTest.show(parent, {}, context) @@ -290,9 +303,11 @@ class ControlsUiControllerImplTest : SysuiTestCase() { @Test fun testPendingIntentExtrasAreModified() { mockLayoutInflater() + val packageName = "pkg" + `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName)) controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true) - val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls")) + val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls")) val serviceInfo = setUpPanel(panel) underTest.show(parent, {}, context) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt new file mode 100644 index 000000000000..dbaf94f1018c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.controls.ui + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class OverflowMenuAdapterTest : SysuiTestCase() { + + @Test + fun testGetItemId() { + val ids = listOf(27L, 73L) + val labels = listOf("first", "second") + val adapter = + OverflowMenuAdapter( + context, + layoutId = 0, + labels.zip(ids).map { OverflowMenuAdapter.MenuItem(it.first, it.second) } + ) { true } + + ids.forEachIndexed { index, id -> assertThat(adapter.getItemId(index)).isEqualTo(id) } + } + + @Test + fun testCheckEnabled() { + val ids = listOf(27L, 73L) + val labels = listOf("first", "second") + val adapter = + OverflowMenuAdapter( + context, + layoutId = 0, + labels.zip(ids).map { OverflowMenuAdapter.MenuItem(it.first, it.second) } + ) { position -> position == 0 } + + assertThat(adapter.isEnabled(0)).isTrue() + assertThat(adapter.isEnabled(1)).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index ff883eb16bde..6b095ffd3977 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -36,11 +36,10 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.complication.ComplicationHostViewController; +import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; import com.android.systemui.statusbar.BlurUtils; -import com.android.systemui.statusbar.phone.KeyguardBouncer; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import org.junit.Before; import org.junit.Test; @@ -81,12 +80,6 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { BlurUtils mBlurUtils; @Mock - StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - - @Mock - KeyguardBouncer mBouncer; - - @Mock ViewRootImpl mViewRoot; @Mock @@ -96,6 +89,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { DreamOverlayAnimationsController mAnimationsController; @Mock + BouncerlessScrimController mBouncerlessScrimController; + + @Mock DreamOverlayStateController mStateController; DreamOverlayContainerViewController mController; @@ -106,7 +102,6 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { when(mDreamOverlayContainerView.getResources()).thenReturn(mResources); when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver); - when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(mBouncer); when(mDreamOverlayContainerView.getViewRootImpl()).thenReturn(mViewRoot); mController = new DreamOverlayContainerViewController( @@ -114,7 +109,6 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mComplicationHostViewController, mDreamOverlayContentView, mDreamOverlayStatusBarViewController, - mStatusBarKeyguardViewManager, mBlurUtils, mHandler, mResources, @@ -123,7 +117,8 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { MILLIS_UNTIL_FULL_JITTER, mPrimaryBouncerCallbackInteractor, mAnimationsController, - mStateController); + mStateController, + mBouncerlessScrimController); } @Test @@ -170,7 +165,8 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor = ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class); mController.onViewAttached(); - verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture()); + verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback( + bouncerExpansionCaptor.capture()); bouncerExpansionCaptor.getValue().onExpansionChanged(0.5f); verify(mBlurUtils, never()).applyBlur(eq(mViewRoot), anyInt(), eq(false)); @@ -181,7 +177,8 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor = ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class); mController.onViewAttached(); - verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture()); + verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback( + bouncerExpansionCaptor.capture()); final float blurRadius = 1337f; when(mBlurUtils.blurRadiusOfRatio(anyFloat())).thenReturn(blurRadius); @@ -217,6 +214,27 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { } @Test + public void testSkipEntryAnimationsWhenExitingLowLight() { + ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor = + ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); + when(mStateController.isLowLightActive()).thenReturn(false); + + // Call onInit so that the callback is added. + mController.onInit(); + verify(mStateController).addCallback(callbackCaptor.capture()); + + // Send the signal that low light is exiting + callbackCaptor.getValue().onExitLowLight(); + + // View is attached to trigger animations. + mController.onViewAttached(); + + // Entry animations should be started then immediately ended to skip to the end. + verify(mAnimationsController).startEntryAnimations(); + verify(mAnimationsController).endAnimations(); + } + + @Test public void testCancelDreamEntryAnimationsOnDetached() { mController.onViewAttached(); mController.onViewDetached(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index 3b8bb8089910..dfb4d5baeef5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -31,6 +31,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.service.dreams.IDreamOverlay; import android.service.dreams.IDreamOverlayCallback; +import android.service.dreams.IDreamOverlayClient; +import android.service.dreams.IDreamOverlayClientCallback; import android.testing.AndroidTestingRunner; import android.view.View; import android.view.ViewGroup; @@ -58,6 +60,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @SmallTest @@ -148,13 +151,25 @@ public class DreamOverlayServiceTest extends SysuiTestCase { mDreamOverlayCallbackController); } - @Test - public void testOnStartMetricsLogged() throws Exception { + public IDreamOverlayClient getClient() throws RemoteException { final IBinder proxy = mService.onBind(new Intent()); final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClientCallback callback = + Mockito.mock(IDreamOverlayClientCallback.class); + overlay.getClient(callback); + final ArgumentCaptor<IDreamOverlayClient> clientCaptor = + ArgumentCaptor.forClass(IDreamOverlayClient.class); + verify(callback).onDreamOverlayClient(clientCaptor.capture()); + + return clientCaptor.getValue(); + } + + @Test + public void testOnStartMetricsLogged() throws Exception { + final IDreamOverlayClient client = getClient(); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, false /*shouldShowComplication*/); mMainExecutor.runAllReady(); @@ -165,11 +180,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testOverlayContainerViewAddedToWindow() throws Exception { - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClient client = getClient(); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, false /*shouldShowComplication*/); mMainExecutor.runAllReady(); @@ -178,11 +192,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testDreamOverlayContainerViewControllerInitialized() throws Exception { - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClient client = getClient(); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, false /*shouldShowComplication*/); mMainExecutor.runAllReady(); @@ -196,11 +209,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase { .thenReturn(mDreamOverlayContainerViewParent) .thenReturn(null); - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClient client = getClient(); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, false /*shouldShowComplication*/); mMainExecutor.runAllReady(); @@ -209,11 +221,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testShouldShowComplicationsSetByStartDream() throws RemoteException { - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClient client = getClient(); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, true /*shouldShowComplication*/); assertThat(mService.shouldShowComplications()).isTrue(); @@ -221,11 +232,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testLowLightSetByStartDream() throws RemoteException { - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClient client = getClient(); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback, + client.startDream(mWindowParams, mDreamOverlayCallback, LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/); mMainExecutor.runAllReady(); @@ -235,11 +245,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testOnEndDream() throws RemoteException { - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClient client = getClient(); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback, + client.startDream(mWindowParams, mDreamOverlayCallback, LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/); mMainExecutor.runAllReady(); @@ -261,11 +270,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testDestroy() throws RemoteException { - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClient client = getClient(); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback, + client.startDream(mWindowParams, mDreamOverlayCallback, LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/); mMainExecutor.runAllReady(); @@ -305,15 +313,14 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception { - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClient client = getClient(); // Destroy the service. mService.onDestroy(); mMainExecutor.runAllReady(); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, false /*shouldShowComplication*/); mMainExecutor.runAllReady(); @@ -331,11 +338,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException { - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClient client = getClient(); // Inform the overlay service of dream starting. Do not show dream complications. - overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, false /*shouldShowComplication*/); mMainExecutor.runAllReady(); @@ -352,7 +358,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase { // New dream starting with dream complications showing. Note that when a new dream is // binding to the dream overlay service, it receives the same instance of IBinder as the // first one. - overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, true /*shouldShowComplication*/); mMainExecutor.runAllReady(); @@ -371,11 +377,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testWakeUp() throws RemoteException { - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + final IDreamOverlayClient client = getClient(); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, true /*shouldShowComplication*/); mMainExecutor.runAllReady(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index ee989d1ddab6..b7d0f294ecd4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -251,6 +251,30 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { } @Test + public void testNotifyLowLightExit() { + final DreamOverlayStateController stateController = + new DreamOverlayStateController(mExecutor, true); + + stateController.addCallback(mCallback); + mExecutor.runAllReady(); + assertThat(stateController.isLowLightActive()).isFalse(); + + // Turn low light on then off to trigger the exiting callback. + stateController.setLowLightActive(true); + stateController.setLowLightActive(false); + + // Callback was only called once, when + mExecutor.runAllReady(); + verify(mCallback, times(1)).onExitLowLight(); + assertThat(stateController.isLowLightActive()).isFalse(); + + // Set with false again, which should not cause the callback to trigger again. + stateController.setLowLightActive(false); + mExecutor.runAllReady(); + verify(mCallback, times(1)).onExitLowLight(); + } + + @Test public void testNotifyEntryAnimationsFinishedChanged() { final DreamOverlayStateController stateController = new DreamOverlayStateController(mExecutor, true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java index 89c728082cc5..a4cf15c3fafa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java @@ -31,13 +31,13 @@ import android.content.ComponentName; import android.content.Context; import android.testing.AndroidTestingRunner; import android.view.View; -import android.widget.ImageView; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.common.ui.view.LaunchableImageView; import com.android.systemui.controls.ControlsServiceInfo; import com.android.systemui.controls.controller.ControlsController; import com.android.systemui.controls.controller.StructureInfo; @@ -90,7 +90,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { private View mView; @Mock - private ImageView mHomeControlsView; + private LaunchableImageView mHomeControlsView; @Mock private ActivityStarter mActivityStarter; diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java new file mode 100644 index 000000000000..19347c768524 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.conditions; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.shared.condition.Condition; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class DreamConditionTest extends SysuiTestCase { + @Mock + Context mContext; + + @Mock + Condition.Callback mCallback; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Ensure a dreaming state immediately triggers the condition. + */ + @Test + public void testInitialState() { + final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED); + when(mContext.registerReceiver(any(), any())).thenReturn(intent); + final DreamCondition condition = new DreamCondition(mContext); + condition.addCallback(mCallback); + condition.start(); + + verify(mCallback).onConditionChanged(eq(condition)); + assertThat(condition.isConditionMet()).isTrue(); + } + + /** + * Ensure that changing dream state triggers condition. + */ + @Test + public void testChange() { + final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED); + final ArgumentCaptor<BroadcastReceiver> receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + when(mContext.registerReceiver(receiverCaptor.capture(), any())).thenReturn(intent); + final DreamCondition condition = new DreamCondition(mContext); + condition.addCallback(mCallback); + condition.start(); + clearInvocations(mCallback); + receiverCaptor.getValue().onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STOPPED)); + verify(mCallback).onConditionChanged(eq(condition)); + assertThat(condition.isConditionMet()).isFalse(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java index f64179deec35..3a168d4e234b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java @@ -41,12 +41,13 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.touch.scrim.ScrimController; +import com.android.systemui.dreams.touch.scrim.ScrimManager; import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.wm.shell.animation.FlingAnimationUtils; import org.junit.Before; @@ -63,10 +64,13 @@ import java.util.Optional; @RunWith(AndroidTestingRunner.class) public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @Mock - StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + CentralSurfaces mCentralSurfaces; @Mock - CentralSurfaces mCentralSurfaces; + ScrimManager mScrimManager; + + @Mock + ScrimController mScrimController; @Mock NotificationShadeWindowController mNotificationShadeWindowController; @@ -111,7 +115,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mTouchHandler = new BouncerSwipeTouchHandler( mDisplayMetrics, - mStatusBarKeyguardViewManager, + mScrimManager, Optional.of(mCentralSurfaces), mNotificationShadeWindowController, mValueAnimatorCreator, @@ -121,6 +125,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { TOUCH_REGION, mUiEventLogger); + when(mScrimManager.getCurrentController()).thenReturn(mScrimController); when(mCentralSurfaces.isBouncerShowing()).thenReturn(false); when(mCentralSurfaces.getDisplayHeight()).thenReturn((float) SCREEN_HEIGHT_PX); when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator); @@ -193,7 +198,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { assertThat(gestureListener.onScroll(event1, event2, 0, distanceY)) .isTrue(); - verify(mStatusBarKeyguardViewManager, never()).onPanelExpansionChanged(any()); + verify(mScrimController, never()).expand(any()); } /** @@ -220,7 +225,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { assertThat(gestureListener.onScroll(event1, event2, 0, distanceY)) .isTrue(); - verify(mStatusBarKeyguardViewManager, never()).onPanelExpansionChanged(any()); + verify(mScrimController, never()).expand(any()); } /** @@ -274,12 +279,12 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, direction == Direction.UP ? SCREEN_HEIGHT_PX - distanceY : distanceY, 0); - reset(mStatusBarKeyguardViewManager); + reset(mScrimController); assertThat(gestureListener.onScroll(event1, event2, 0, distanceY)) .isTrue(); // Ensure only called once - verify(mStatusBarKeyguardViewManager).onPanelExpansionChanged(any()); + verify(mScrimController).expand(any()); final float expansion = isBouncerInitiallyShowing ? percent : 1 - percent; final float dragDownAmount = event2.getY() - event1.getY(); @@ -288,7 +293,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { ShadeExpansionChangeEvent event = new ShadeExpansionChangeEvent( expansion, /* expanded= */ false, /* tracking= */ true, dragDownAmount); - verify(mStatusBarKeyguardViewManager).onPanelExpansionChanged(event); + verify(mScrimController).expand(event); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java index a807407f170d..178b9cc72726 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java @@ -424,6 +424,32 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { verify(gestureListener2).onDown(eq(followupEvent)); } + @Test + public void testOnRemovedCallbackOnStopMonitoring() { + final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class); + final DreamTouchHandler.TouchSession.Callback callback = + Mockito.mock(DreamTouchHandler.TouchSession.Callback.class); + + final Environment environment = new Environment(Stream.of(touchHandler) + .collect(Collectors.toCollection(HashSet::new))); + + final InputEvent initialEvent = Mockito.mock(InputEvent.class); + environment.publishInputEvent(initialEvent); + + final DreamTouchHandler.TouchSession session = captureSession(touchHandler); + session.registerCallback(callback); + + environment.executeAll(); + + environment.updateLifecycle(observerOwnerPair -> { + observerOwnerPair.first.onPause(observerOwnerPair.second); + }); + + environment.executeAll(); + + verify(callback).onRemoved(); + } + public GestureDetector.OnGestureListener registerGestureListener(DreamTouchHandler handler) { final GestureDetector.OnGestureListener gestureListener = Mockito.mock( GestureDetector.OnGestureListener.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java new file mode 100644 index 000000000000..79c535aea2d0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.touch.scrim; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.os.PowerManager; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.shade.ShadeExpansionChangeEvent; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class BouncerlessScrimControllerTest extends SysuiTestCase { + @Mock + BouncerlessScrimController.Callback mCallback; + + @Mock + PowerManager mPowerManager; + + private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testWakeupOnSwipeOpen() { + final BouncerlessScrimController scrimController = + new BouncerlessScrimController(mExecutor, mPowerManager); + scrimController.addCallback(mCallback); + scrimController.expand(new ShadeExpansionChangeEvent(.5f, true, false, 0.0f)); + mExecutor.runAllReady(); + verify(mPowerManager).wakeUp(anyLong(), eq(PowerManager.WAKE_REASON_GESTURE), any()); + verify(mCallback).onWakeup(); + } + + @Test + public void testExpansionPropagation() { + final BouncerlessScrimController scrimController = + new BouncerlessScrimController(mExecutor, mPowerManager); + scrimController.addCallback(mCallback); + final ShadeExpansionChangeEvent expansionEvent = + new ShadeExpansionChangeEvent(0.5f, false, false, 0.0f); + scrimController.expand(expansionEvent); + mExecutor.runAllReady(); + verify(mCallback).onExpansion(eq(expansionEvent)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java new file mode 100644 index 000000000000..ac9822db85d6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.touch.scrim; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ScrimManagerTest extends SysuiTestCase { + @Mock + ScrimController mBouncerlessScrimController; + + @Mock + ScrimController mBouncerScrimController; + + @Mock + KeyguardStateController mKeyguardStateController; + + @Mock + ScrimManager.Callback mCallback; + + private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testControllerSelection() { + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false); + ArgumentCaptor<KeyguardStateController.Callback> callbackCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback.class); + final ScrimManager manager = new ScrimManager(mExecutor, mBouncerScrimController, + mBouncerlessScrimController, mKeyguardStateController); + verify(mKeyguardStateController).addCallback(callbackCaptor.capture()); + + assertThat(manager.getCurrentController()).isEqualTo(mBouncerScrimController); + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); + callbackCaptor.getValue().onKeyguardShowingChanged(); + mExecutor.runAllReady(); + assertThat(manager.getCurrentController()).isEqualTo(mBouncerlessScrimController); + } + + @Test + public void testCallback() { + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false); + ArgumentCaptor<KeyguardStateController.Callback> callbackCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback.class); + final ScrimManager manager = new ScrimManager(mExecutor, mBouncerScrimController, + mBouncerlessScrimController, mKeyguardStateController); + verify(mKeyguardStateController).addCallback(callbackCaptor.capture()); + + manager.addCallback(mCallback); + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); + callbackCaptor.getValue().onKeyguardShowingChanged(); + mExecutor.runAllReady(); + verify(mCallback).onScrimControllerChanged(eq(mBouncerlessScrimController)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt index 170a70f2fc40..35f0f6c68798 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt @@ -125,7 +125,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() { flags.set(unreleasedFlag, false) flags.set(unreleasedFlag, false) - listener.verifyInOrder(unreleasedFlag.id, unreleasedFlag.id) + listener.verifyInOrder(unreleasedFlag.name, unreleasedFlag.name) } @Test @@ -137,7 +137,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() { flags.set(stringFlag, "Test") flags.set(stringFlag, "Test") - listener.verifyInOrder(stringFlag.id) + listener.verifyInOrder(stringFlag.name) } @Test @@ -149,7 +149,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() { flags.removeListener(listener) flags.set(unreleasedFlag, false) - listener.verifyInOrder(unreleasedFlag.id) + listener.verifyInOrder(unreleasedFlag.name) } @Test @@ -162,7 +162,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() { flags.removeListener(listener) flags.set(stringFlag, "Other") - listener.verifyInOrder(stringFlag.id) + listener.verifyInOrder(stringFlag.name) } @Test @@ -175,7 +175,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() { flags.set(releasedFlag, true) flags.set(unreleasedFlag, true) - listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id) + listener.verifyInOrder(releasedFlag.name, unreleasedFlag.name) } @Test @@ -191,7 +191,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() { flags.set(releasedFlag, false) flags.set(unreleasedFlag, false) - listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id) + listener.verifyInOrder(releasedFlag.name, unreleasedFlag.name) } @Test @@ -204,8 +204,8 @@ class FakeFeatureFlagsTest : SysuiTestCase() { flags.set(releasedFlag, true) - listener1.verifyInOrder(releasedFlag.id) - listener2.verifyInOrder(releasedFlag.id) + listener1.verifyInOrder(releasedFlag.name) + listener2.verifyInOrder(releasedFlag.name) } @Test @@ -220,18 +220,18 @@ class FakeFeatureFlagsTest : SysuiTestCase() { flags.removeListener(listener2) flags.set(releasedFlag, false) - listener1.verifyInOrder(releasedFlag.id, releasedFlag.id) - listener2.verifyInOrder(releasedFlag.id) + listener1.verifyInOrder(releasedFlag.name, releasedFlag.name) + listener2.verifyInOrder(releasedFlag.name) } class VerifyingListener : FlagListenable.Listener { - var flagEventIds = mutableListOf<Int>() + var flagEventNames = mutableListOf<String>() override fun onFlagChanged(event: FlagListenable.FlagEvent) { - flagEventIds.add(event.flagId) + flagEventNames.add(event.flagName) } - fun verifyInOrder(vararg eventIds: Int) { - assertThat(flagEventIds).containsExactlyElementsIn(eventIds.asList()) + fun verifyInOrder(vararg eventNames: String) { + assertThat(flagEventNames).containsExactlyElementsIn(eventNames.asList()) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt index 7592cc527190..d8bbd04bfd4a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt @@ -23,12 +23,11 @@ import android.content.res.Resources import android.content.res.Resources.NotFoundException import android.test.suitebuilder.annotation.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.commandline.CommandRegistry -import com.android.systemui.util.DeviceConfigProxyFake import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat import org.junit.Assert @@ -62,21 +61,20 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Mock private lateinit var mockContext: Context @Mock + private lateinit var globalSettings: GlobalSettings + @Mock private lateinit var secureSettings: SecureSettings @Mock private lateinit var systemProperties: SystemPropertiesHelper @Mock private lateinit var resources: Resources @Mock - private lateinit var commandRegistry: CommandRegistry - @Mock private lateinit var restarter: Restarter - private val flagMap = mutableMapOf<Int, Flag<*>>() + private val flagMap = mutableMapOf<String, Flag<*>>() private lateinit var broadcastReceiver: BroadcastReceiver - private lateinit var clearCacheAction: Consumer<Int> + private lateinit var clearCacheAction: Consumer<String> private val serverFlagReader = ServerFlagReaderFake() - private val deviceConfig = DeviceConfigProxyFake() private val teamfoodableFlagA = UnreleasedFlag( 500, name = "a", namespace = "test", teamfood = true ) @@ -87,11 +85,13 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - flagMap.put(teamfoodableFlagA.id, teamfoodableFlagA) - flagMap.put(teamfoodableFlagB.id, teamfoodableFlagB) + flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD) + flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA) + flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB) mFeatureFlagsDebug = FeatureFlagsDebug( flagManager, mockContext, + globalSettings, secureSettings, systemProperties, resources, @@ -110,14 +110,14 @@ class FeatureFlagsDebugTest : SysuiTestCase() { clearCacheAction = withArgCaptor { verify(flagManager).clearCacheAction = capture() } - whenever(flagManager.idToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" } + whenever(flagManager.nameToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" } } @Test fun readBooleanFlag() { // Remember that the TEAMFOOD flag is id#1 and has special behavior. - whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true) - whenever(flagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false) + whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true) + whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false) assertThat( mFeatureFlagsDebug.isEnabled( @@ -141,7 +141,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { mFeatureFlagsDebug.isEnabled( ReleasedFlag( 4, - name = "3", + name = "4", namespace = "test" ) ) @@ -150,7 +150,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { mFeatureFlagsDebug.isEnabled( UnreleasedFlag( 5, - name = "4", + name = "5", namespace = "test" ) ) @@ -159,7 +159,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun teamFoodFlag_False() { - whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(false) + whenever(flagManager.readFlagValue<Boolean>( + eq(Flags.TEAMFOOD.name), any())).thenReturn(false) assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse() assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue() @@ -170,7 +171,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun teamFoodFlag_True() { - whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true) + whenever(flagManager.readFlagValue<Boolean>( + eq(Flags.TEAMFOOD.name), any())).thenReturn(true) assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue() @@ -181,11 +183,12 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun teamFoodFlag_Overridden() { - whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any())) + whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.name), any())) .thenReturn(true) - whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any())) + whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any())) .thenReturn(false) - whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true) + whenever(flagManager.readFlagValue<Boolean>( + eq(Flags.TEAMFOOD.name), any())).thenReturn(true) assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse() @@ -202,8 +205,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(resources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() } whenever(resources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() } - whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true) - whenever(flagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false) + whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true) + whenever(flagManager.readFlagValue<Boolean>(eq("5"), any())).thenReturn(false) assertThat( mFeatureFlagsDebug.isEnabled( @@ -255,8 +258,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun readStringFlag() { - whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo") - whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar") + whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo") + whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar") assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz") assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz") assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo") @@ -272,9 +275,9 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(resources.getString(1005)).thenAnswer { throw NameNotFoundException() } whenever(resources.getString(1006)).thenAnswer { throw NameNotFoundException() } - whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("override3") - whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4") - whenever(flagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6") + whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("override3") + whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("override4") + whenever(flagManager.readFlagValue<String>(eq("6"), any())).thenReturn("override6") assertThat( mFeatureFlagsDebug.getString( @@ -322,8 +325,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun readIntFlag() { - whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(22) - whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(48) + whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22) + whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48) assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12) assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93) assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22) @@ -368,12 +371,12 @@ class FeatureFlagsDebugTest : SysuiTestCase() { broadcastReceiver.onReceive(mockContext, Intent()) broadcastReceiver.onReceive(mockContext, Intent("invalid action")) broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG)) - setByBroadcast(0, false) // unknown id does nothing - setByBroadcast(1, "string") // wrong type does nothing - setByBroadcast(2, 123) // wrong type does nothing - setByBroadcast(3, false) // wrong type does nothing - setByBroadcast(4, 123) // wrong type does nothing - verifyNoMoreInteractions(flagManager, secureSettings) + setByBroadcast("0", false) // unknown id does nothing + setByBroadcast("1", "string") // wrong type does nothing + setByBroadcast("2", 123) // wrong type does nothing + setByBroadcast("3", false) // wrong type does nothing + setByBroadcast("4", 123) // wrong type does nothing + verifyNoMoreInteractions(flagManager, globalSettings) } @Test @@ -383,16 +386,16 @@ class FeatureFlagsDebugTest : SysuiTestCase() { // trying to erase an id not in the map does nothing broadcastReceiver.onReceive( mockContext, - Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 0) + Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "") ) - verifyNoMoreInteractions(flagManager, secureSettings) + verifyNoMoreInteractions(flagManager, globalSettings) // valid id with no value puts empty string in the setting broadcastReceiver.onReceive( mockContext, - Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 1) + Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1") ) - verifyPutData(1, "", numReads = 0) + verifyPutData("1", "", numReads = 0) } @Test @@ -402,51 +405,51 @@ class FeatureFlagsDebugTest : SysuiTestCase() { addFlag(ResourceBooleanFlag(3, "3", "test", 1003)) addFlag(ResourceBooleanFlag(4, "4", "test", 1004)) - setByBroadcast(1, false) - verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}") + setByBroadcast("1", false) + verifyPutData("1", "{\"type\":\"boolean\",\"value\":false}") - setByBroadcast(2, true) - verifyPutData(2, "{\"type\":\"boolean\",\"value\":true}") + setByBroadcast("2", true) + verifyPutData("2", "{\"type\":\"boolean\",\"value\":true}") - setByBroadcast(3, false) - verifyPutData(3, "{\"type\":\"boolean\",\"value\":false}") + setByBroadcast("3", false) + verifyPutData("3", "{\"type\":\"boolean\",\"value\":false}") - setByBroadcast(4, true) - verifyPutData(4, "{\"type\":\"boolean\",\"value\":true}") + setByBroadcast("4", true) + verifyPutData("4", "{\"type\":\"boolean\",\"value\":true}") } @Test fun setStringFlag() { - addFlag(StringFlag(1, "flag1", "1", "test")) + addFlag(StringFlag(1, "1", "1", "test")) addFlag(ResourceStringFlag(2, "2", "test", 1002)) - setByBroadcast(1, "override1") - verifyPutData(1, "{\"type\":\"string\",\"value\":\"override1\"}") + setByBroadcast("1", "override1") + verifyPutData("1", "{\"type\":\"string\",\"value\":\"override1\"}") - setByBroadcast(2, "override2") - verifyPutData(2, "{\"type\":\"string\",\"value\":\"override2\"}") + setByBroadcast("2", "override2") + verifyPutData("2", "{\"type\":\"string\",\"value\":\"override2\"}") } @Test fun setFlag_ClearsCache() { val flag1 = addFlag(StringFlag(1, "1", "test", "flag1")) - whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("original") + whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original") // gets the flag & cache it assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original") - verify(flagManager).readFlagValue(eq(1), eq(StringFlagSerializer)) + verify(flagManager, times(1)).readFlagValue(eq("1"), eq(StringFlagSerializer)) // hit the cache assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original") verifyNoMoreInteractions(flagManager) // set the flag - setByBroadcast(1, "new") - verifyPutData(1, "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2) - whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("new") + setByBroadcast("1", "new") + verifyPutData("1", "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2) + whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("new") assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("new") - verify(flagManager, times(3)).readFlagValue(eq(1), eq(StringFlagSerializer)) + verify(flagManager, times(3)).readFlagValue(eq("1"), eq(StringFlagSerializer)) } @Test @@ -463,7 +466,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() { val flag = UnreleasedFlag(100, name = "100", namespace = "test") serverFlagReader.setFlagValue(flag.namespace, flag.name, true) - assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue() } @@ -503,26 +505,26 @@ class FeatureFlagsDebugTest : SysuiTestCase() { assertThat(dump).contains(" sysui_flag_7: [length=9] \"override7\"\n") } - private fun verifyPutData(id: Int, data: String, numReads: Int = 1) { - inOrder(flagManager, secureSettings).apply { - verify(flagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>()) - verify(flagManager).idToSettingsKey(eq(id)) - verify(secureSettings).putStringForUser(eq("key-$id"), eq(data), anyInt()) - verify(flagManager).dispatchListenersAndMaybeRestart(eq(id), any()) + private fun verifyPutData(name: String, data: String, numReads: Int = 1) { + inOrder(flagManager, globalSettings).apply { + verify(flagManager, times(numReads)).readFlagValue(eq(name), any<FlagSerializer<*>>()) + verify(flagManager).nameToSettingsKey(eq(name)) + verify(globalSettings).putStringForUser(eq("key-$name"), eq(data), anyInt()) + verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any()) }.verifyNoMoreInteractions() - verifyNoMoreInteractions(flagManager, secureSettings) + verifyNoMoreInteractions(flagManager, globalSettings) } - private fun setByBroadcast(id: Int, value: Serializable?) { + private fun setByBroadcast(name: String, value: Serializable?) { val intent = Intent(FlagManager.ACTION_SET_FLAG) - intent.putExtra(FlagManager.EXTRA_ID, id) + intent.putExtra(FlagManager.EXTRA_NAME, name) intent.putExtra(FlagManager.EXTRA_VALUE, value) broadcastReceiver.onReceive(mockContext, intent) } private fun <F : Flag<*>> addFlag(flag: F): F { - val old = flagMap.put(flag.id, flag) - check(old == null) { "Flag ${flag.id} already registered" } + val old = flagMap.put(flag.name, flag) + check(old == null) { "Flag ${flag.name} already registered" } return flag } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt index d5b5a4a4101e..4c6028c4c9b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt @@ -19,7 +19,6 @@ import android.content.pm.PackageManager.NameNotFoundException import android.content.res.Resources import android.test.suitebuilder.annotation.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.util.DeviceConfigProxyFake import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows import org.junit.Before @@ -39,9 +38,8 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { @Mock private lateinit var mResources: Resources @Mock private lateinit var mSystemProperties: SystemPropertiesHelper @Mock private lateinit var restarter: Restarter - private val flagMap = mutableMapOf<Int, Flag<*>>() + private val flagMap = mutableMapOf<String, Flag<*>>() private val serverFlagReader = ServerFlagReaderFake() - private val deviceConfig = DeviceConfigProxyFake() @Before fun setup() { @@ -49,7 +47,6 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { mFeatureFlagsRelease = FeatureFlagsRelease( mResources, mSystemProperties, - deviceConfig, serverFlagReader, flagMap, restarter) diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt index fea91c53424d..28131b50f04c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt @@ -32,7 +32,7 @@ class FlagCommandTest : SysuiTestCase() { @Mock private lateinit var featureFlags: FeatureFlagsDebug @Mock private lateinit var pw: PrintWriter - private val flagMap = mutableMapOf<Int, Flag<*>>() + private val flagMap = mutableMapOf<String, Flag<*>>() private val flagA = UnreleasedFlag(500, "500", "test") private val flagB = ReleasedFlag(501, "501", "test") private val stringFlag = StringFlag(502, "502", "test", "abracadabra") @@ -53,59 +53,59 @@ class FlagCommandTest : SysuiTestCase() { (invocation.getArgument(0) as IntFlag).default } - flagMap.put(flagA.id, flagA) - flagMap.put(flagB.id, flagB) - flagMap.put(stringFlag.id, stringFlag) - flagMap.put(intFlag.id, intFlag) + flagMap.put(flagA.name, flagA) + flagMap.put(flagB.name, flagB) + flagMap.put(stringFlag.name, stringFlag) + flagMap.put(intFlag.name, intFlag) cmd = FlagCommand(featureFlags, flagMap) } @Test fun readBooleanFlagCommand() { - cmd.execute(pw, listOf(flagA.id.toString())) + cmd.execute(pw, listOf(flagA.name)) Mockito.verify(featureFlags).isEnabled(flagA) } @Test fun readStringFlagCommand() { - cmd.execute(pw, listOf(stringFlag.id.toString())) + cmd.execute(pw, listOf(stringFlag.name)) Mockito.verify(featureFlags).getString(stringFlag) } @Test fun readIntFlag() { - cmd.execute(pw, listOf(intFlag.id.toString())) + cmd.execute(pw, listOf(intFlag.name)) Mockito.verify(featureFlags).getInt(intFlag) } @Test fun setBooleanFlagCommand() { - cmd.execute(pw, listOf(flagB.id.toString(), "on")) + cmd.execute(pw, listOf(flagB.name, "on")) Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true) } @Test fun setStringFlagCommand() { - cmd.execute(pw, listOf(stringFlag.id.toString(), "set", "foobar")) + cmd.execute(pw, listOf(stringFlag.name, "set", "foobar")) Mockito.verify(featureFlags).setStringFlagInternal(stringFlag, "foobar") } @Test fun setIntFlag() { - cmd.execute(pw, listOf(intFlag.id.toString(), "put", "123")) + cmd.execute(pw, listOf(intFlag.name, "put", "123")) Mockito.verify(featureFlags).setIntFlagInternal(intFlag, 123) } @Test fun toggleBooleanFlagCommand() { - cmd.execute(pw, listOf(flagB.id.toString(), "toggle")) + cmd.execute(pw, listOf(flagB.name, "toggle")) Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false) } @Test fun eraseFlagCommand() { - cmd.execute(pw, listOf(flagA.id.toString(), "erase")) + cmd.execute(pw, listOf(flagA.name, "erase")) Mockito.verify(featureFlags).eraseFlag(flagA) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt index fca7e96fb148..e679d47537b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt @@ -87,14 +87,14 @@ class FlagManagerTest : SysuiTestCase() { @Test fun testObserverClearsCache() { val listener = mock<FlagListenable.Listener>() - val clearCacheAction = mock<Consumer<Int>>() + val clearCacheAction = mock<Consumer<String>>() mFlagManager.clearCacheAction = clearCacheAction mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener) val observer = withArgCaptor<ContentObserver> { verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture()) } observer.onChange(false, flagUri(1)) - verify(clearCacheAction).accept(eq(1)) + verify(clearCacheAction).accept(eq("1")) } @Test @@ -110,14 +110,14 @@ class FlagManagerTest : SysuiTestCase() { val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> { verify(listener1).onFlagChanged(capture()) } - assertThat(flagEvent1.flagId).isEqualTo(1) + assertThat(flagEvent1.flagName).isEqualTo("1") verifyNoMoreInteractions(listener1, listener10) observer.onChange(false, flagUri(10)) val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> { verify(listener10).onFlagChanged(capture()) } - assertThat(flagEvent10.flagId).isEqualTo(10) + assertThat(flagEvent10.flagName).isEqualTo("10") verifyNoMoreInteractions(listener1, listener10) } @@ -130,18 +130,18 @@ class FlagManagerTest : SysuiTestCase() { mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1) mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10) - mFlagManager.dispatchListenersAndMaybeRestart(1, null) + mFlagManager.dispatchListenersAndMaybeRestart("1", null) val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> { verify(listener1).onFlagChanged(capture()) } - assertThat(flagEvent1.flagId).isEqualTo(1) + assertThat(flagEvent1.flagName).isEqualTo("1") verifyNoMoreInteractions(listener1, listener10) - mFlagManager.dispatchListenersAndMaybeRestart(10, null) + mFlagManager.dispatchListenersAndMaybeRestart("10", null) val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> { verify(listener10).onFlagChanged(capture()) } - assertThat(flagEvent10.flagId).isEqualTo(10) + assertThat(flagEvent10.flagName).isEqualTo("10") verifyNoMoreInteractions(listener1, listener10) } @@ -151,25 +151,25 @@ class FlagManagerTest : SysuiTestCase() { mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener) mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener) - mFlagManager.dispatchListenersAndMaybeRestart(1, null) + mFlagManager.dispatchListenersAndMaybeRestart("1", null) val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> { verify(listener).onFlagChanged(capture()) } - assertThat(flagEvent1.flagId).isEqualTo(1) + assertThat(flagEvent1.flagName).isEqualTo("1") verifyNoMoreInteractions(listener) - mFlagManager.dispatchListenersAndMaybeRestart(10, null) + mFlagManager.dispatchListenersAndMaybeRestart("10", null) val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> { verify(listener, times(2)).onFlagChanged(capture()) } - assertThat(flagEvent10.flagId).isEqualTo(10) + assertThat(flagEvent10.flagName).isEqualTo("10") verifyNoMoreInteractions(listener) } @Test fun testRestartWithNoListeners() { val restartAction = mock<Consumer<Boolean>>() - mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction) + mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction) verify(restartAction).accept(eq(false)) verifyNoMoreInteractions(restartAction) } @@ -180,7 +180,7 @@ class FlagManagerTest : SysuiTestCase() { mFlagManager.addListener(ReleasedFlag(1, "1", "test")) { event -> event.requestNoRestart() } - mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction) + mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction) verify(restartAction).accept(eq(true)) verifyNoMoreInteractions(restartAction) } @@ -191,7 +191,7 @@ class FlagManagerTest : SysuiTestCase() { mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event -> event.requestNoRestart() } - mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction) + mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction) verify(restartAction).accept(eq(false)) verifyNoMoreInteractions(restartAction) } @@ -205,7 +205,7 @@ class FlagManagerTest : SysuiTestCase() { mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { // do not request } - mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction) + mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction) verify(restartAction).accept(eq(false)) verifyNoMoreInteractions(restartAction) } @@ -214,31 +214,31 @@ class FlagManagerTest : SysuiTestCase() { fun testReadBooleanFlag() { // test that null string returns null whenever(mFlagSettingsHelper.getString(any())).thenReturn(null) - assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull() + assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull() // test that empty string returns null whenever(mFlagSettingsHelper.getString(any())).thenReturn("") - assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull() + assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull() // test false whenever(mFlagSettingsHelper.getString(any())) .thenReturn("{\"type\":\"boolean\",\"value\":false}") - assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isFalse() + assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isFalse() // test true whenever(mFlagSettingsHelper.getString(any())) .thenReturn("{\"type\":\"boolean\",\"value\":true}") - assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isTrue() + assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isTrue() // Reading a value of a different type should just return null whenever(mFlagSettingsHelper.getString(any())) .thenReturn("{\"type\":\"string\",\"value\":\"foo\"}") - assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull() + assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull() // Reading a value that isn't json should throw an exception assertThrows(InvalidFlagStorageException::class.java) { whenever(mFlagSettingsHelper.getString(any())).thenReturn("1") - mFlagManager.readFlagValue(1, BooleanFlagSerializer) + mFlagManager.readFlagValue("1", BooleanFlagSerializer) } } @@ -257,31 +257,31 @@ class FlagManagerTest : SysuiTestCase() { fun testReadStringFlag() { // test that null string returns null whenever(mFlagSettingsHelper.getString(any())).thenReturn(null) - assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull() + assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull() // test that empty string returns null whenever(mFlagSettingsHelper.getString(any())).thenReturn("") - assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull() + assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull() // test json with the empty string value returns empty string whenever(mFlagSettingsHelper.getString(any())) .thenReturn("{\"type\":\"string\",\"value\":\"\"}") - assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("") + assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isEqualTo("") // test string with value is returned whenever(mFlagSettingsHelper.getString(any())) .thenReturn("{\"type\":\"string\",\"value\":\"foo\"}") - assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("foo") + assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isEqualTo("foo") // Reading a value of a different type should just return null whenever(mFlagSettingsHelper.getString(any())) .thenReturn("{\"type\":\"boolean\",\"value\":false}") - assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull() + assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull() // Reading a value that isn't json should throw an exception assertThrows(InvalidFlagStorageException::class.java) { whenever(mFlagSettingsHelper.getString(any())).thenReturn("1") - mFlagManager.readFlagValue(1, StringFlagSerializer) + mFlagManager.readFlagValue("1", StringFlagSerializer) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt index 77c837b803af..a2dc1eb606ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt @@ -14,6 +14,7 @@ import org.junit.Test @SmallTest class FragmentServiceTest : SysuiTestCase() { private val fragmentCreator = TestFragmentCreator() + private val fragmenetHostManagerFactory: FragmentHostManager.Factory = mock() private val fragmentCreatorFactory = FragmentService.FragmentCreator.Factory { fragmentCreator } private lateinit var fragmentService: FragmentService @@ -24,7 +25,13 @@ class FragmentServiceTest : SysuiTestCase() { Looper.prepare() } - fragmentService = FragmentService(fragmentCreatorFactory, mock(), DumpManager()) + fragmentService = + FragmentService( + fragmentCreatorFactory, + fragmenetHostManagerFactory, + mock(), + DumpManager() + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index c0af0cb4089e..fb54d6d6b2d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -62,6 +62,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -184,6 +185,7 @@ class CustomizationProviderTest : SysuiTestCase() { mainDispatcher = testDispatcher, backgroundHandler = backgroundHandler, ) + underTest.mainDispatcher = UnconfinedTestDispatcher() underTest.attachInfoForTesting( context, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt index 8da4eae2f64a..58cdec447cc6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.quickaffordance import android.app.StatusBarManager import android.content.Context +import android.content.pm.PackageManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.camera.CameraGestureHelper @@ -32,7 +33,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Mock -import org.mockito.Mockito.anyInt import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -43,16 +43,19 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() { @Mock private lateinit var cameraGestureHelper: CameraGestureHelper @Mock private lateinit var context: Context + @Mock private lateinit var packageManager: PackageManager private lateinit var underTest: CameraQuickAffordanceConfig @Before fun setUp() { MockitoAnnotations.initMocks(this) + setLaunchable(true) underTest = CameraQuickAffordanceConfig( context, + packageManager, ) { cameraGestureHelper } @@ -86,6 +89,7 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() { } private fun setLaunchable(isLaunchable: Boolean) { - whenever(cameraGestureHelper.canCameraGestureBeLaunched(anyInt())).thenReturn(isLaunchable) + whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) + .thenReturn(isLaunchable) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt new file mode 100644 index 000000000000..a3740d88e1a9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt @@ -0,0 +1,140 @@ +/* + * 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.keyguard.data.quickaffordance + +import android.content.Context +import android.media.AudioManager +import androidx.lifecycle.LiveData +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.RingerModeTracker +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.mockito.whenever +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class MuteQuickAffordanceConfigTest : SysuiTestCase() { + + private lateinit var underTest: MuteQuickAffordanceConfig + @Mock + private lateinit var ringerModeTracker: RingerModeTracker + @Mock + private lateinit var audioManager: AudioManager + @Mock + private lateinit var userTracker: UserTracker + @Mock + private lateinit var userFileManager: UserFileManager + + private lateinit var testScope: TestScope + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + testScope = TestScope() + + whenever(userTracker.userContext).thenReturn(context) + whenever(userFileManager.getSharedPreferences(any(), any(), any())) + .thenReturn(context.getSharedPreferences("mutequickaffordancetest", Context.MODE_PRIVATE)) + + underTest = MuteQuickAffordanceConfig( + context, + userTracker, + userFileManager, + ringerModeTracker, + audioManager + ) + } + + @Test + fun `picker state - volume fixed - not available`() = testScope.runTest { + //given + whenever(audioManager.isVolumeFixed).thenReturn(true) + + //when + val result = underTest.getPickerScreenState() + + //then + assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, result) + } + + @Test + fun `picker state - volume not fixed - available`() = testScope.runTest { + //given + whenever(audioManager.isVolumeFixed).thenReturn(false) + + //when + val result = underTest.getPickerScreenState() + + //then + assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default(), result) + } + + @Test + fun `triggered - state was previously NORMAL - currently SILENT - move to previous state`() { + //given + val ringerModeCapture = argumentCaptor<Int>() + val ringerModeInternal = mock<LiveData<Int>>() + whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal) + whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL) + underTest.onTriggered(null) + whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_SILENT) + + //when + val result = underTest.onTriggered(null) + verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture() + + //then + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(AudioManager.RINGER_MODE_NORMAL, ringerModeCapture.value) + } + + @Test + fun `triggered - state is not SILENT - move to SILENT ringer`() { + //given + val ringerModeCapture = argumentCaptor<Int>() + val ringerModeInternal = mock<LiveData<Int>>() + whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal) + whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL) + + //when + val result = underTest.onTriggered(null) + verify(audioManager).ringerModeInternal = ringerModeCapture.capture() + + //then + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(AudioManager.RINGER_MODE_SILENT, ringerModeCapture.value) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt new file mode 100644 index 000000000000..26601b63e02d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import android.content.Context +import android.media.AudioManager +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.RingerModeTracker +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() { + + @Mock + private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var userTracker: UserTracker + @Mock + private lateinit var ringerModeTracker: RingerModeTracker + @Mock + private lateinit var userFileManager: UserFileManager + @Mock + private lateinit var keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository + + private lateinit var testScope: TestScope + + private lateinit var underTest: MuteQuickAffordanceCoreStartable + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(true) + + val config: KeyguardQuickAffordanceConfig = mock() + whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE) + + val emission = MutableStateFlow(mapOf("testQuickAffordanceKey" to listOf(config))) + whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission) + + testScope = TestScope() + + underTest = MuteQuickAffordanceCoreStartable( + featureFlags, + userTracker, + ringerModeTracker, + userFileManager, + keyguardQuickAffordanceRepository, + testScope, + ) + } + + @Test + fun `feature flag is OFF - do nothing with keyguardQuickAffordanceRepository`() = testScope.runTest { + //given + whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(false) + + //when + underTest.start() + + //then + verifyZeroInteractions(keyguardQuickAffordanceRepository) + coroutineContext.cancelChildren() + } + + @Test + fun `feature flag is ON - call to keyguardQuickAffordanceRepository`() = testScope.runTest { + //given + val ringerModeInternal = mock<MutableLiveData<Int>>() + whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal) + + //when + underTest.start() + runCurrent() + + //then + verify(keyguardQuickAffordanceRepository).selections + coroutineContext.cancelChildren() + } + + @Test + fun `ringer mode is changed to SILENT - do not save to shared preferences`() = testScope.runTest { + //given + val ringerModeInternal = mock<MutableLiveData<Int>>() + val observerCaptor = argumentCaptor<Observer<Int>>() + whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal) + + //when + underTest.start() + runCurrent() + verify(ringerModeInternal).observeForever(observerCaptor.capture()) + observerCaptor.value.onChanged(AudioManager.RINGER_MODE_SILENT) + + //then + verifyZeroInteractions(userFileManager) + coroutineContext.cancelChildren() + } + + @Test + fun `ringerModeInternal changes to something not SILENT - is set in sharedpreferences`() = testScope.runTest { + //given + val newRingerMode = 99 + val observerCaptor = argumentCaptor<Observer<Int>>() + val ringerModeInternal = mock<MutableLiveData<Int>>() + val sharedPrefs = context.getSharedPreferences("quick_affordance_mute_ringer_mode_cache_test", Context.MODE_PRIVATE) + whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal) + whenever( + userFileManager.getSharedPreferences(eq("quick_affordance_mute_ringer_mode_cache"), any(), any()) + ).thenReturn(sharedPrefs) + + //when + underTest.start() + runCurrent() + verify(ringerModeInternal).observeForever(observerCaptor.capture()) + observerCaptor.value.onChanged(newRingerMode) + val result = sharedPrefs.getInt("key_last_non_silent_ringer_mode", -1) + + //then + assertEquals(newRingerMode, result) + coroutineContext.cancelChildren() + } + + @Test + fun `MUTE is in selections - observe ringerModeInternal`() = testScope.runTest { + //given + val ringerModeInternal = mock<MutableLiveData<Int>>() + whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal) + + //when + underTest.start() + runCurrent() + + //then + verify(ringerModeInternal).observeForever(any()) + coroutineContext.cancelChildren() + } + + @Test + fun `MUTE is in selections 2x - observe ringerModeInternal`() = testScope.runTest { + //given + val config: KeyguardQuickAffordanceConfig = mock() + whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE) + val emission = MutableStateFlow(mapOf("testKey" to listOf(config), "testkey2" to listOf(config))) + whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission) + val ringerModeInternal = mock<MutableLiveData<Int>>() + whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal) + + //when + underTest.start() + runCurrent() + + //then + verify(ringerModeInternal).observeForever(any()) + coroutineContext.cancelChildren() + } + + @Test + fun `MUTE is not in selections - stop observing ringerModeInternal`() = testScope.runTest { + //given + val config: KeyguardQuickAffordanceConfig = mock() + whenever(config.key).thenReturn("notmutequickaffordance") + val emission = MutableStateFlow(mapOf("testKey" to listOf(config))) + whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission) + val ringerModeInternal = mock<MutableLiveData<Int>>() + whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal) + + //when + underTest.start() + runCurrent() + + //then + verify(ringerModeInternal).removeObserver(any()) + coroutineContext.cancelChildren() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt index c4ae2db93569..9203f05602b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt @@ -55,7 +55,11 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testScope = TestScope() - underTest = DeviceEntryFingerprintAuthRepositoryImpl(keyguardUpdateMonitor) + underTest = + DeviceEntryFingerprintAuthRepositoryImpl( + keyguardUpdateMonitor, + testScope.backgroundScope, + ) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index b071a028865d..6099f011a90d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -275,10 +275,10 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { expected: Map<String, List<KeyguardQuickAffordanceConfig>>, ) { assertThat(observed).isEqualTo(expected) - assertThat(underTest.getSelections()) + assertThat(underTest.getCurrentSelections()) .isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } }) expected.forEach { (slotId, configs) -> - assertThat(underTest.getSelections(slotId)).isEqualTo(configs) + assertThat(underTest.getCurrentSelections(slotId)).isEqualTo(configs) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt index 1da7241e58bd..68fff262214a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeBiometricRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.util.time.FakeSystemClock @@ -46,6 +47,8 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { private lateinit var underTest: AlternateBouncerInteractor private lateinit var bouncerRepository: KeyguardBouncerRepository private lateinit var biometricRepository: FakeBiometricRepository + private lateinit var deviceEntryFingerprintAuthRepository: + FakeDeviceEntryFingerprintAuthRepository @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var bouncerLogger: TableLogBuffer @@ -62,11 +65,13 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { bouncerLogger, ) biometricRepository = FakeBiometricRepository() + deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) } underTest = AlternateBouncerInteractor( bouncerRepository, biometricRepository, + deviceEntryFingerprintAuthRepository, systemClock, keyguardUpdateMonitor, featureFlags, @@ -112,6 +117,14 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { } @Test + fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() { + givenCanShowAlternateBouncer() + deviceEntryFingerprintAuthRepository.setLockedOut(true) + + assertFalse(underTest.canShowAlternateBouncerForFingerprint()) + } + + @Test fun show_whenCanShow() { givenCanShowAlternateBouncer() @@ -148,6 +161,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { biometricRepository.setFingerprintEnrolled(true) biometricRepository.setStrongBiometricAllowed(true) biometricRepository.setFingerprintEnabledByDevicePolicy(true) + deviceEntryFingerprintAuthRepository.setLockedOut(false) } private fun givenCannotShowAlternateBouncer() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt new file mode 100644 index 000000000000..9d60b16eba8b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.content.Intent +import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class KeyguardLongPressInteractorTest : SysuiTestCase() { + + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var logger: UiEventLogger + + private lateinit var underTest: KeyguardLongPressInteractor + + private lateinit var testScope: TestScope + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + runBlocking { createUnderTest() } + } + + @Test + fun isEnabled() = + testScope.runTest { + val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled) + KeyguardState.values().forEach { keyguardState -> + setUpState( + keyguardState = keyguardState, + ) + + if (keyguardState == KeyguardState.LOCKSCREEN) { + assertThat(isEnabled()).isTrue() + } else { + assertThat(isEnabled()).isFalse() + } + } + } + + @Test + fun `isEnabled - always false when quick settings are visible`() = + testScope.runTest { + val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled) + KeyguardState.values().forEach { keyguardState -> + setUpState( + keyguardState = keyguardState, + isQuickSettingsVisible = true, + ) + + assertThat(isEnabled()).isFalse() + } + } + + @Test + fun `long-pressed - pop-up clicked - starts activity`() = + testScope.runTest { + val menu = collectLastValue(underTest.menu) + runCurrent() + + val x = 100 + val y = 123 + underTest.onLongPress(x, y) + assertThat(menu()).isNotNull() + assertThat(menu()?.position?.x).isEqualTo(x) + assertThat(menu()?.position?.y).isEqualTo(y) + + menu()?.onClicked?.invoke() + + assertThat(menu()).isNull() + verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean()) + } + + @Test + fun `long-pressed - pop-up dismissed - never starts activity`() = + testScope.runTest { + val menu = collectLastValue(underTest.menu) + runCurrent() + + menu()?.onDismissed?.invoke() + + assertThat(menu()).isNull() + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), any(), anyBoolean()) + } + + @Suppress("DEPRECATION") // We're okay using ACTION_CLOSE_SYSTEM_DIALOGS on system UI. + @Test + fun `long pressed - close dialogs broadcast received - popup dismissed`() = + testScope.runTest { + val menu = collectLastValue(underTest.menu) + runCurrent() + + underTest.onLongPress(123, 456) + assertThat(menu()).isNotNull() + + fakeBroadcastDispatcher.registeredReceivers.forEach { broadcastReceiver -> + broadcastReceiver.onReceive(context, Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) + } + + assertThat(menu()).isNull() + } + + @Test + fun `logs when menu is shown`() = + testScope.runTest { + collectLastValue(underTest.menu) + runCurrent() + + underTest.onLongPress(100, 123) + + verify(logger) + .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN) + } + + @Test + fun `logs when menu is clicked`() = + testScope.runTest { + val menu = collectLastValue(underTest.menu) + runCurrent() + + underTest.onLongPress(100, 123) + menu()?.onClicked?.invoke() + + verify(logger) + .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED) + } + + private suspend fun createUnderTest( + isLongPressFeatureEnabled: Boolean = true, + isRevampedWppFeatureEnabled: Boolean = true, + ) { + testScope = TestScope() + keyguardRepository = FakeKeyguardRepository() + keyguardTransitionRepository = FakeKeyguardTransitionRepository() + + underTest = + KeyguardLongPressInteractor( + unsafeContext = context, + scope = testScope.backgroundScope, + transitionInteractor = + KeyguardTransitionInteractor( + repository = keyguardTransitionRepository, + ), + repository = keyguardRepository, + activityStarter = activityStarter, + logger = logger, + featureFlags = + FakeFeatureFlags().apply { + set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled) + set(Flags.REVAMPED_WALLPAPER_UI, isRevampedWppFeatureEnabled) + }, + broadcastDispatcher = fakeBroadcastDispatcher, + ) + setUpState() + } + + private suspend fun setUpState( + keyguardState: KeyguardState = KeyguardState.LOCKSCREEN, + isQuickSettingsVisible: Boolean = false, + ) { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + to = keyguardState, + ), + ) + keyguardRepository.setQuickSettingsVisible(isVisible = isQuickSettingsVisible) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 5a7a3d49b628..3a871b4de8bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -519,6 +519,43 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun `GONE to LOCKSREEN`() = + testScope.runTest { + // GIVEN a prior transition has run to GONE + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN the keyguard starts to show + keyguardRepository.setKeyguardShowing(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to AOD should occur + assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.GONE) + assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun `GONE to DREAMING`() = testScope.runTest { // GIVEN a device that is not dreaming or dozing 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 022afdd61fc2..4b04b7b54dcd 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 @@ -234,7 +234,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { @Test fun `startButton - in preview mode - visible even when keyguard not showing`() = testScope.runTest { - underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START) + underTest.enablePreviewMode( + initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + shouldHighlightSelectedAffordance = true, + ) repository.setKeyguardShowing(false) val latest = collectLastValue(underTest.startButton) @@ -263,6 +266,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { icon = icon, canShowWhileLocked = false, intent = Intent("action"), + isSelected = true, ), configKey = configKey, ) @@ -270,6 +274,60 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { } @Test + fun `endButton - in higlighted preview mode - dimmed when other is selected`() = + testScope.runTest { + underTest.enablePreviewMode( + initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + shouldHighlightSelectedAffordance = true, + ) + repository.setKeyguardShowing(false) + val startButton = collectLastValue(underTest.startButton) + val endButton = collectLastValue(underTest.endButton) + + val icon: Icon = mock() + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = + TestConfig( + isVisible = true, + isClickable = true, + isActivated = true, + icon = icon, + canShowWhileLocked = false, + intent = Intent("action"), + ), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_END, + testConfig = + TestConfig( + isVisible = true, + isClickable = true, + isActivated = true, + icon = icon, + canShowWhileLocked = false, + intent = Intent("action"), + ), + ) + + assertQuickAffordanceViewModel( + viewModel = endButton(), + testConfig = + TestConfig( + isVisible = true, + isClickable = false, + isActivated = true, + icon = icon, + canShowWhileLocked = false, + intent = Intent("action"), + isDimmed = true, + ), + configKey = configKey, + ) + } + + @Test fun `endButton - present - visible model - do nothing on click`() = testScope.runTest { repository.setKeyguardShowing(true) @@ -377,7 +435,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { @Test fun `alpha - in preview mode - does not change`() = testScope.runTest { - underTest.enablePreviewMode(null) + underTest.enablePreviewMode( + initiallySelectedSlotId = null, + shouldHighlightSelectedAffordance = false, + ) val value = collectLastValue(underTest.alpha) assertThat(value()).isEqualTo(1f) @@ -639,6 +700,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible) assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable) assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated) + assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected) + assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed) if (testConfig.isVisible) { assertThat(viewModel.icon).isEqualTo(testConfig.icon) viewModel.onClicked.invoke( @@ -664,6 +727,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { val icon: Icon? = null, val canShowWhileLocked: Boolean = false, val intent: Intent? = null, + val isSelected: Boolean = false, + val isDimmed: Boolean = false, ) { init { check(!isVisible || icon != null) { "Must supply non-null icon if visible!" } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt index c88f84a028ed..54fc4938e1b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt @@ -71,7 +71,7 @@ class KeyguardTransitionRunner( waitUntilComplete(info.animator!!) } - suspend private fun waitUntilComplete(animator: ValueAnimator) { + private suspend fun waitUntilComplete(animator: ValueAnimator) { withContext(Dispatchers.Main) { val startTime = System.currentTimeMillis() while (!isTerminated && animator.isRunning()) { @@ -96,6 +96,6 @@ class KeyguardTransitionRunner( override fun setFrameDelay(delay: Long) {} companion object { - private const val MAX_TEST_DURATION = 100L + private const val MAX_TEST_DURATION = 200L } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 1687fdc9f76c..1ac66952fd3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -134,7 +134,8 @@ class MediaDataManagerTest : SysuiTestCase() { private val clock = FakeSystemClock() @Mock private lateinit var tunerService: TunerService @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable> - @Captor lateinit var callbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit> + @Captor lateinit var stateCallbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit> + @Captor lateinit var sessionCallbackCaptor: ArgumentCaptor<(String) -> Unit> @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig> private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20) @@ -184,6 +185,8 @@ class MediaDataManagerTest : SysuiTestCase() { ) verify(tunerService) .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) + verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor) + verify(mediaTimeoutListener).sessionCallback = capture(sessionCallbackCaptor) session = MediaSession(context, "MediaDataManagerTestSession") mediaNotification = SbnBuilder().run { @@ -230,6 +233,7 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false) whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true) + whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false) whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId()) } @@ -547,6 +551,7 @@ class MediaDataManagerTest : SysuiTestCase() { mediaDataManager.onNotificationAdded(KEY_2, mediaNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(2) assertThat(foregroundExecutor.runAllReady()).isEqualTo(2) + verify(listener) .onMediaDataLoaded( eq(KEY), @@ -558,9 +563,21 @@ class MediaDataManagerTest : SysuiTestCase() { ) val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() - val resumableData = data.copy(resumeAction = Runnable {}) - mediaDataManager.onMediaDataLoaded(KEY, null, resumableData) - mediaDataManager.onMediaDataLoaded(KEY_2, null, resumableData) + + verify(listener) + .onMediaDataLoaded( + eq(KEY_2), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + val data2 = mediaDataCaptor.value + assertThat(data2.resumption).isFalse() + + mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) + mediaDataManager.onMediaDataLoaded(KEY_2, null, data2.copy(resumeAction = Runnable {})) reset(listener) // WHEN the first is removed mediaDataManager.onNotificationRemoved(KEY) @@ -1310,11 +1327,10 @@ class MediaDataManagerTest : SysuiTestCase() { fun testPlaybackStateChange_keyExists_callsListener() { // Notification has been added addNotificationAndLoad() - verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) // Callback gets an updated state val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build() - callbackCaptor.value.invoke(KEY, state) + stateCallbackCaptor.value.invoke(KEY, state) // Listener is notified of updated state verify(listener) @@ -1332,11 +1348,10 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testPlaybackStateChange_keyDoesNotExist_doesNothing() { val state = PlaybackState.Builder().build() - verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) // No media added with this key - callbackCaptor.value.invoke(KEY, state) + stateCallbackCaptor.value.invoke(KEY, state) verify(listener, never()) .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @@ -1352,10 +1367,9 @@ class MediaDataManagerTest : SysuiTestCase() { // And then get a state update val state = PlaybackState.Builder().build() - verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) // Then no changes are made - callbackCaptor.value.invoke(KEY, state) + stateCallbackCaptor.value.invoke(KEY, state) verify(listener, never()) .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @@ -1367,8 +1381,7 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(controller.playbackState).thenReturn(state) addNotificationAndLoad() - verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) - callbackCaptor.value.invoke(KEY, state) + stateCallbackCaptor.value.invoke(KEY, state) verify(listener) .onMediaDataLoaded( @@ -1410,8 +1423,7 @@ class MediaDataManagerTest : SysuiTestCase() { backgroundExecutor.runAllReady() foregroundExecutor.runAllReady() - verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) - callbackCaptor.value.invoke(PACKAGE_NAME, state) + stateCallbackCaptor.value.invoke(PACKAGE_NAME, state) verify(listener) .onMediaDataLoaded( @@ -1436,8 +1448,7 @@ class MediaDataManagerTest : SysuiTestCase() { .build() addNotificationAndLoad() - verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) - callbackCaptor.value.invoke(KEY, state) + stateCallbackCaptor.value.invoke(KEY, state) verify(listener) .onMediaDataLoaded( @@ -1485,6 +1496,177 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(mediaDataCaptor.value.isClearable).isFalse() } + @Test + fun testRetain_notifPlayer_notifRemoved_setToResume() { + whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true) + + // When a media control based on notification is added, times out, and then removed + addNotificationAndLoad() + mediaDataManager.setTimedOut(KEY, timedOut = true) + assertThat(mediaDataCaptor.value.active).isFalse() + mediaDataManager.onNotificationRemoved(KEY) + + // It is converted to a resume player + verify(listener) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value.resumption).isTrue() + assertThat(mediaDataCaptor.value.active).isFalse() + verify(logger) + .logActiveConvertedToResume( + anyInt(), + eq(PACKAGE_NAME), + eq(mediaDataCaptor.value.instanceId) + ) + } + + @Test + fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() { + whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true) + + // When a media control based on notification is added and times out + addNotificationAndLoad() + mediaDataManager.setTimedOut(KEY, timedOut = true) + assertThat(mediaDataCaptor.value.active).isFalse() + + // and then the session is destroyed + sessionCallbackCaptor.value.invoke(KEY) + + // It remains as a regular player + verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()) + .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) + } + + @Test + fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() { + whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true) + + // When a media control based on notification is added and then removed, without timing out + addNotificationAndLoad() + val data = mediaDataCaptor.value + assertThat(data.active).isTrue() + mediaDataManager.onNotificationRemoved(KEY) + + // It is fully removed + verify(listener).onMediaDataRemoved(eq(KEY)) + verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) + verify(listener, never()) + .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) + } + + @Test + fun testRetain_canResume_removeWhileActive_setToResume() { + whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true) + + // When a media control that supports resumption is added + addNotificationAndLoad() + val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {}) + mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable) + + // And then removed while still active + mediaDataManager.onNotificationRemoved(KEY) + + // It is converted to a resume player + verify(listener) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value.resumption).isTrue() + assertThat(mediaDataCaptor.value.active).isFalse() + verify(logger) + .logActiveConvertedToResume( + anyInt(), + eq(PACKAGE_NAME), + eq(mediaDataCaptor.value.instanceId) + ) + } + + @Test + fun testRetain_sessionPlayer_notifRemoved_doesNotChange() { + whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true) + whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) + addPlaybackStateAction() + + // When a media control with PlaybackState actions is added, times out, + // and then the notification is removed + addNotificationAndLoad() + val data = mediaDataCaptor.value + assertThat(data.active).isTrue() + mediaDataManager.setTimedOut(KEY, timedOut = true) + mediaDataManager.onNotificationRemoved(KEY) + + // It remains as a regular player + verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()) + .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) + } + + @Test + fun testRetain_sessionPlayer_sessionDestroyed_setToResume() { + whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true) + whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) + addPlaybackStateAction() + + // When a media control with PlaybackState actions is added, times out, + // and then the session is destroyed + addNotificationAndLoad() + val data = mediaDataCaptor.value + assertThat(data.active).isTrue() + mediaDataManager.setTimedOut(KEY, timedOut = true) + sessionCallbackCaptor.value.invoke(KEY) + + // It is converted to a resume player + verify(listener) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value.resumption).isTrue() + assertThat(mediaDataCaptor.value.active).isFalse() + verify(logger) + .logActiveConvertedToResume( + anyInt(), + eq(PACKAGE_NAME), + eq(mediaDataCaptor.value.instanceId) + ) + } + + @Test + fun testRetain_sessionPlayer_destroyedWhileActive_fullyRemoved() { + whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true) + whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) + addPlaybackStateAction() + + // When a media control using session actions is added, and then the session is destroyed + // without timing out first + addNotificationAndLoad() + val data = mediaDataCaptor.value + assertThat(data.active).isTrue() + sessionCallbackCaptor.value.invoke(KEY) + + // It is fully removed + verify(listener).onMediaDataRemoved(eq(KEY)) + verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) + verify(listener, never()) + .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) + } + /** Helper function to add a media notification and capture the resulting MediaData */ private fun addNotificationAndLoad() { mediaDataManager.onNotificationAdded(KEY, mediaNotification) @@ -1500,4 +1682,12 @@ class MediaDataManagerTest : SysuiTestCase() { eq(false) ) } + + /** Helper function to set up a PlaybackState with action */ + private fun addPlaybackStateAction() { + val stateActions = PlaybackState.ACTION_PLAY_PAUSE + val stateBuilder = PlaybackState.Builder().setActions(stateActions) + stateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 1.0f) + whenever(controller.playbackState).thenReturn(stateBuilder.build()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt index 344dffafb448..92bf84ce285c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt @@ -72,6 +72,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { private lateinit var executor: FakeExecutor @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit @Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit + @Mock private lateinit var sessionCallback: (String) -> Unit @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback> @Captor private lateinit var dozingCallbackCaptor: @@ -99,6 +100,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { ) mediaTimeoutListener.timeoutCallback = timeoutCallback mediaTimeoutListener.stateCallback = stateCallback + mediaTimeoutListener.sessionCallback = sessionCallback // Create a media session and notification for testing. metadataBuilder = @@ -284,6 +286,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { verify(mediaController).unregisterCallback(anyObject()) assertThat(executor.numPending()).isEqualTo(0) verify(logger).logSessionDestroyed(eq(KEY)) + verify(sessionCallback).invoke(eq(KEY)) } @Test @@ -322,6 +325,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // THEN the controller is unregistered, but the timeout is still scheduled verify(mediaController).unregisterCallback(anyObject()) assertThat(executor.numPending()).isEqualTo(1) + verify(sessionCallback, never()).invoke(eq(KEY)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index e4e95e580a7c..5e5dc8b20c65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.media.controls.models.recommendation.SmartspaceMedia import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS +import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager @@ -87,6 +88,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Mock lateinit var smartspaceMediaData: SmartspaceMediaData @Mock lateinit var mediaCarousel: MediaScrollView @Mock lateinit var pageIndicator: PageIndicator + @Mock lateinit var mediaFlags: MediaFlags @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener> @Captor lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener> @@ -114,7 +116,8 @@ class MediaCarouselControllerTest : SysuiTestCase() { falsingManager, dumpManager, logger, - debugLogger + debugLogger, + mediaFlags, ) verify(configurationController).addCallback(capture(configListener)) verify(mediaDataManager).addListener(capture(listener)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt index b35dd266e422..ce22b19b3721 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt @@ -204,6 +204,9 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var coverContainer1: ViewGroup @Mock private lateinit var coverContainer2: ViewGroup @Mock private lateinit var coverContainer3: ViewGroup + @Mock private lateinit var recAppIconItem: CachingIconView + @Mock private lateinit var recCardTitle: TextView + @Mock private lateinit var coverItem: ImageView private lateinit var coverItem1: ImageView private lateinit var coverItem2: ImageView private lateinit var coverItem3: ImageView @@ -220,6 +223,7 @@ public class MediaControlPanelTest : SysuiTestCase() { this.set(Flags.UMO_TURBULENCE_NOISE, false) this.set(Flags.MEDIA_FALSING_PENALTY, true) this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true) + this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false) } @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -2059,6 +2063,51 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test + fun bindRecommendation_setAfterExecutors() { + fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true) + whenever(recommendationViewHolder.mediaAppIcons) + .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem)) + whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle) + whenever(recommendationViewHolder.mediaCoverItems) + .thenReturn(listOf(coverItem, coverItem, coverItem)) + + val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bmp) + canvas.drawColor(Color.RED) + val albumArt = Icon.createWithBitmap(bmp) + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", "title1") + .setSubtitle("subtitle1") + .setIcon(albumArt) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("subtitle1") + .setIcon(albumArt) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id3", "title3") + .setSubtitle("subtitle1") + .setIcon(albumArt) + .setExtras(Bundle.EMPTY) + .build() + ) + ) + + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(data) + bgExecutor.runAllReady() + mainExecutor.runAllReady() + + verify(recCardTitle).setTextColor(any<Int>()) + verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java)) + verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java)) + } + + @Test fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() { fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true) val semanticActions = diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt index 4ed6d7cf6bd0..2f7eac2ad4ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt @@ -22,6 +22,7 @@ import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.util.animation.MeasurementInput import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionViewState @@ -55,6 +56,7 @@ class MediaViewControllerTest : SysuiTestCase() { @Mock private lateinit var mediaTitleWidgetState: WidgetState @Mock private lateinit var mediaSubTitleWidgetState: WidgetState @Mock private lateinit var mediaContainerWidgetState: WidgetState + @Mock private lateinit var mediaFlags: MediaFlags val delta = 0.1F @@ -64,7 +66,13 @@ class MediaViewControllerTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) mediaViewController = - MediaViewController(context, configurationController, mediaHostStatesManager, logger) + MediaViewController( + context, + configurationController, + mediaHostStatesManager, + logger, + mediaFlags, + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 7c3c9d2a1bb1..8fd15c1f6b90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -29,6 +29,7 @@ import android.graphics.drawable.Icon; import android.testing.AndroidTestingRunner; import android.view.View; import android.widget.LinearLayout; +import android.widget.SeekBar; import androidx.core.graphics.drawable.IconCompat; import androidx.test.filters.SmallTest; @@ -43,6 +44,8 @@ import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import java.util.ArrayList; import java.util.List; @@ -57,6 +60,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { private static final String TEST_DEVICE_ID_1 = "test_device_id_1"; private static final String TEST_DEVICE_ID_2 = "test_device_id_2"; private static final String TEST_SESSION_NAME = "test_session_name"; + private static final int TEST_MAX_VOLUME = 20; private static final int TEST_CURRENT_VOLUME = 10; @@ -69,6 +73,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { private IconCompat mIconCompat = mock(IconCompat.class); private View mDialogLaunchView = mock(View.class); + @Captor + private ArgumentCaptor<SeekBar.OnSeekBarChangeListener> mOnSeekBarChangeListenerCaptor; private MediaOutputAdapter mMediaOutputAdapter; private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder; private List<MediaDevice> mMediaDevices = new ArrayList<>(); @@ -78,6 +84,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Before public void setUp() { when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false); + when(mMediaOutputController.isSubStatusSupported()).thenReturn(false); when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems); when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices); when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false); @@ -348,6 +355,24 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test + public void onBindViewHolder_dragSeekbar_setsVolume() { + mOnSeekBarChangeListenerCaptor = ArgumentCaptor.forClass( + SeekBar.OnSeekBarChangeListener.class); + MediaOutputSeekbar mSpySeekbar = spy(mViewHolder.mSeekBar); + mViewHolder.mSeekBar = mSpySeekbar; + when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME); + when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_MAX_VOLUME); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); + + verify(mViewHolder.mSeekBar).setOnSeekBarChangeListener( + mOnSeekBarChangeListenerCaptor.capture()); + + mOnSeekBarChangeListenerCaptor.getValue().onStopTrackingTouch(mViewHolder.mSeekBar); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); + verify(mMediaOutputController).logInteractionAdjustVolume(mMediaDevice1); + } + + @Test public void onBindViewHolder_bindSelectableDevice_verifyView() { List<MediaDevice> selectableDevices = new ArrayList<>(); selectableDevices.add(mMediaDevice2); @@ -404,6 +429,24 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test + public void subStatusSupported_onBindViewHolder_bindFailedStateDevice_verifyView() { + String deviceStatus = ""; + when(mMediaOutputController.isSubStatusSupported()).thenReturn(true); + when(mMediaDevice2.hasDisabledReason()).thenReturn(true); + when(mMediaDevice2.getDisableReason()).thenReturn(-1); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); + + assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mSubTitleText.getText()).isEqualTo(deviceStatus); + assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_2); + } + + @Test public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() { when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true); when(mMediaDevice1.getState()).thenReturn( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 117751c47004..7c36e4680a72 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -93,6 +93,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { private static final String TEST_SONG = "test_song"; private static final String TEST_SESSION_ID = "test_session_id"; private static final String TEST_SESSION_NAME = "test_session_name"; + private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); + private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock( + ActivityLaunchAnimator.Controller.class); + private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( + NearbyMediaDevicesManager.class); // Mock private MediaController mMediaController = mock(MediaController.class); private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); @@ -111,12 +116,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { private KeyguardManager mKeyguardManager = mock(KeyguardManager.class); private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class); - private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); private FeatureFlags mFlags = mock(FeatureFlags.class); - private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock( - ActivityLaunchAnimator.Controller.class); - private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( - NearbyMediaDevicesManager.class); private View mDialogLaunchView = mock(View.class); private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class); @@ -522,6 +522,17 @@ public class MediaOutputControllerTest extends SysuiTestCase { } @Test + public void logInteractionAdjustVolume_triggersFromMetricLogger() { + MediaOutputMetricLogger spyMediaOutputMetricLogger = spy( + mMediaOutputController.mMetricLogger); + mMediaOutputController.mMetricLogger = spyMediaOutputMetricLogger; + + mMediaOutputController.logInteractionAdjustVolume(mMediaDevice1); + + verify(spyMediaOutputMetricLogger).logInteractionAdjustVolume(mMediaDevice1); + } + + @Test public void getSessionVolumeMax_triggersFromLocalMediaManager() { mMediaOutputController.getSessionVolumeMax(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt new file mode 100644 index 000000000000..8da1c646109d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt @@ -0,0 +1,86 @@ +/* + * 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.media.taptotransfer.common + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogcatEchoTracker +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.mock + +@SmallTest +class MediaTttLoggerUtilsTest : SysuiTestCase() { + + private lateinit var buffer: LogBuffer + + @Before + fun setUp() { + buffer = + LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)) + .create("buffer", 10) + } + + @Test + fun logStateChange_bufferHasDeviceTypeTagAndParamInfo() { + val stateName = "test state name" + val id = "test id" + val packageName = "this.is.a.package" + + MediaTttLoggerUtils.logStateChange(buffer, TAG, stateName, id, packageName) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(TAG) + assertThat(actualString).contains(stateName) + assertThat(actualString).contains(id) + assertThat(actualString).contains(packageName) + } + + @Test + fun logStateChangeError_hasState() { + MediaTttLoggerUtils.logStateChangeError(buffer, TAG, 3456) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(TAG) + assertThat(actualString).contains("3456") + } + + @Test + fun logPackageNotFound_bufferHasPackageName() { + val packageName = "this.is.a.package" + + MediaTttLoggerUtils.logPackageNotFound(buffer, TAG, packageName) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(TAG) + assertThat(actualString).contains(packageName) + } + + private fun getStringFromBuffer(): String { + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + return stringWriter.toString() + } +} + +private const val TAG = "TEST TAG" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt index 561867f78e60..8055b987973b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt @@ -25,7 +25,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Icon -import com.android.systemui.temporarydisplay.TemporaryViewInfo import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -41,7 +40,6 @@ class MediaTttUtilsTest : SysuiTestCase() { private lateinit var appIconFromPackageName: Drawable @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var applicationInfo: ApplicationInfo - @Mock private lateinit var logger: MediaTttLogger<TemporaryViewInfo> @Before fun setUp() { @@ -67,8 +65,7 @@ class MediaTttUtilsTest : SysuiTestCase() { @Test fun getIconInfoFromPackageName_nullPackageName_returnsDefault() { - val iconInfo = - MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger) + val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null) {} assertThat(iconInfo.isAppIcon).isFalse() assertThat(iconInfo.contentDescription.loadContentDescription(context)) @@ -77,8 +74,19 @@ class MediaTttUtilsTest : SysuiTestCase() { } @Test + fun getIconInfoFromPackageName_nullPackageName_exceptionFnNotTriggered() { + var exceptionTriggered = false + + MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null) { + exceptionTriggered = true + } + + assertThat(exceptionTriggered).isFalse() + } + + @Test fun getIconInfoFromPackageName_invalidPackageName_returnsDefault() { - val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger) + val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName") {} assertThat(iconInfo.isAppIcon).isFalse() assertThat(iconInfo.contentDescription.loadContentDescription(context)) @@ -87,8 +95,19 @@ class MediaTttUtilsTest : SysuiTestCase() { } @Test + fun getIconInfoFromPackageName_invalidPackageName_exceptionFnTriggered() { + var exceptionTriggered = false + + MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = "fakePackageName") { + exceptionTriggered = true + } + + assertThat(exceptionTriggered).isTrue() + } + + @Test fun getIconInfoFromPackageName_validPackageName_returnsAppInfo() { - val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger) + val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME) {} assertThat(iconInfo.isAppIcon).isTrue() assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Loaded(appIconFromPackageName)) @@ -96,6 +115,17 @@ class MediaTttUtilsTest : SysuiTestCase() { } @Test + fun getIconInfoFromPackageName_validPackageName_exceptionFnNotTriggered() { + var exceptionTriggered = false + + MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME) { + exceptionTriggered = true + } + + assertThat(exceptionTriggered).isFalse() + } + + @Test fun iconInfo_toTintedIcon_loaded() { val contentDescription = ContentDescription.Loaded("test") val drawable = context.getDrawable(R.drawable.ic_cake)!! diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt index b3e621e32d08..bd042c23856d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt @@ -24,7 +24,6 @@ import android.view.WindowManager import android.view.accessibility.AccessibilityManager import com.android.systemui.dump.DumpManager import com.android.systemui.media.taptotransfer.MediaTttFlags -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor @@ -35,7 +34,7 @@ import com.android.systemui.util.wakelock.WakeLock class FakeMediaTttChipControllerReceiver( commandQueue: CommandQueue, context: Context, - logger: MediaTttLogger<ChipReceiverInfo>, + logger: MediaTttReceiverLogger, windowManager: WindowManager, mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 5e40898030cf..dba2da7d74b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -36,7 +36,6 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.media.taptotransfer.MediaTttFlags -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor @@ -68,7 +67,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Mock private lateinit var applicationInfo: ApplicationInfo @Mock - private lateinit var logger: MediaTttLogger<ChipReceiverInfo> + private lateinit var logger: MediaTttReceiverLogger @Mock private lateinit var accessibilityManager: AccessibilityManager @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt index 0e7bf8d9d465..95df484cddc2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.media.taptotransfer.common +package com.android.systemui.media.taptotransfer.receiver import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -22,7 +22,6 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.log.LogBufferFactory import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogcatEchoTracker -import com.android.systemui.temporarydisplay.TemporaryViewInfo import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter @@ -31,16 +30,17 @@ import org.junit.Test import org.mockito.Mockito.mock @SmallTest -class MediaTttLoggerTest : SysuiTestCase() { +class MediaTttReceiverLoggerTest : SysuiTestCase() { private lateinit var buffer: LogBuffer - private lateinit var logger: MediaTttLogger<TemporaryViewInfo> + private lateinit var logger: MediaTttReceiverLogger @Before - fun setUp () { - buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)) - .create("buffer", 10) - logger = MediaTttLogger(DEVICE_TYPE_TAG, buffer) + fun setUp() { + buffer = + LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)) + .create("buffer", 10) + logger = MediaTttReceiverLogger(buffer) } @Test @@ -52,32 +52,27 @@ class MediaTttLoggerTest : SysuiTestCase() { logger.logStateChange(stateName, id, packageName) val actualString = getStringFromBuffer() - assertThat(actualString).contains(DEVICE_TYPE_TAG) assertThat(actualString).contains(stateName) assertThat(actualString).contains(id) assertThat(actualString).contains(packageName) } @Test - fun logPackageNotFound_bufferHasPackageName() { - val packageName = "this.is.a.package" - - logger.logPackageNotFound(packageName) + fun logStateChangeError_hasState() { + logger.logStateChangeError(3456) val actualString = getStringFromBuffer() - assertThat(actualString).contains(packageName) + assertThat(actualString).contains("3456") } @Test - fun logRemovalBypass_bufferHasReasons() { - val removalReason = "fakeRemovalReason" - val bypassReason = "fakeBypassReason" + fun logPackageNotFound_bufferHasPackageName() { + val packageName = "this.is.a.package" - logger.logRemovalBypass(removalReason, bypassReason) + logger.logPackageNotFound(packageName) val actualString = getStringFromBuffer() - assertThat(actualString).contains(removalReason) - assertThat(actualString).contains(bypassReason) + assertThat(actualString).contains(packageName) } private fun getStringFromBuffer(): String { @@ -86,5 +81,3 @@ class MediaTttLoggerTest : SysuiTestCase() { return stringWriter.toString() } } - -private const val DEVICE_TYPE_TAG = "TEST TYPE" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index 30bd2dceca7b..ef10e40631e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -40,7 +40,6 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.dump.DumpManager import com.android.systemui.media.taptotransfer.MediaTttFlags -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.VibratorHelper @@ -48,7 +47,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryViewDisplayController import com.android.systemui.temporarydisplay.chipbar.ChipbarAnimator import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator -import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler import com.android.systemui.util.concurrency.FakeExecutor @@ -92,7 +90,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var falsingCollector: FalsingCollector @Mock private lateinit var chipbarLogger: ChipbarLogger - @Mock private lateinit var logger: MediaTttLogger<ChipbarInfo> + @Mock private lateinit var logger: MediaTttSenderLogger @Mock private lateinit var mediaTttFlags: MediaTttFlags @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var powerManager: PowerManager @@ -164,6 +162,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { chipbarCoordinator, commandQueue, context, + dumpManager, logger, mediaTttFlags, uiEventLogger, @@ -182,6 +181,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { chipbarCoordinator, commandQueue, context, + dumpManager, logger, mediaTttFlags, uiEventLogger, @@ -543,6 +543,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { val viewCaptor = ArgumentCaptor.forClass(View::class.java) verify(windowManager).addView(viewCaptor.capture(), any()) verify(windowManager).removeView(viewCaptor.value) + verify(logger).logStateMapRemoval(eq(DEFAULT_ID), any()) } @Test @@ -742,6 +743,99 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { verify(windowManager, never()).addView(any(), any()) } + /** Regression test for b/266217596. */ + @Test + fun toReceiver_triggeredThenFar_thenSucceeded_updatesToSucceeded() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfo, + null, + ) + + // WHEN a FAR command comes in + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null, + ) + + // THEN it is ignored and the chipbar is stilled displayed + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + verify(windowManager, never()).removeView(any()) + + // WHEN a SUCCEEDED command comes in + val succeededRouteInfo = + MediaRoute2Info.Builder(DEFAULT_ID, "Tablet Succeeded") + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + succeededRouteInfo, + /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() {} + }, + ) + + // THEN it is *not* marked as an invalid transition and the chipbar updates to the succeeded + // state. (The "invalid transition" would be FAR => SUCCEEDED.) + assertThat(chipbarView.getChipText()) + .isEqualTo( + ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText( + "Tablet Succeeded" + ) + ) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE) + } + + /** Regression test for b/266217596. */ + @Test + fun toThisDevice_triggeredThenFar_thenSucceeded_updatesToSucceeded() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + routeInfo, + null, + ) + + // WHEN a FAR command comes in + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null, + ) + + // THEN it is ignored and the chipbar is stilled displayed + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + verify(windowManager, never()).removeView(any()) + + // WHEN a SUCCEEDED command comes in + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() {} + }, + ) + + // THEN it is *not* marked as an invalid transition and the chipbar updates to the succeeded + // state. (The "invalid transition" would be FAR => SUCCEEDED.) + assertThat(chipbarView.getChipText()) + .isEqualTo( + ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText( + "Tablet Succeeded" + ) + ) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE) + } + @Test fun receivesNewStateFromCommandQueue_isLogged() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( @@ -935,6 +1029,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { mockChipbarCoordinator, commandQueue, context, + dumpManager, logger, mediaTttFlags, uiEventLogger, @@ -961,6 +1056,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { mockChipbarCoordinator, commandQueue, context, + dumpManager, logger, mediaTttFlags, uiEventLogger, @@ -978,9 +1074,10 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor)) // WHEN the listener is notified that the view has been removed - listenerCaptor.value.onInfoPermanentlyRemoved(DEFAULT_ID) + listenerCaptor.value.onInfoPermanentlyRemoved(DEFAULT_ID, "reason") // THEN the media coordinator unregisters the listener + verify(logger).logStateMapRemoval(DEFAULT_ID, "reason") verify(mockChipbarCoordinator).unregisterListener(listenerCaptor.value) } @@ -992,6 +1089,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { mockChipbarCoordinator, commandQueue, context, + dumpManager, logger, mediaTttFlags, uiEventLogger, @@ -1009,7 +1107,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor)) // WHEN the listener is notified that a different view has been removed - listenerCaptor.value.onInfoPermanentlyRemoved("differentViewId") + listenerCaptor.value.onInfoPermanentlyRemoved("differentViewId", "reason") // THEN the media coordinator doesn't unregister the listener verify(mockChipbarCoordinator, never()).unregisterListener(listenerCaptor.value) @@ -1023,6 +1121,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { mockChipbarCoordinator, commandQueue, context, + dumpManager, logger, mediaTttFlags, uiEventLogger, @@ -1058,6 +1157,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { mockChipbarCoordinator, commandQueue, context, + dumpManager, logger, mediaTttFlags, uiEventLogger, @@ -1087,10 +1187,173 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { verify(mockChipbarCoordinator, atLeast(1)).registerListener(capture(listenerCaptor)) // THEN one of them is removed - listenerCaptor.value.onInfoPermanentlyRemoved("route1") + listenerCaptor.value.onInfoPermanentlyRemoved("route1", "reason") // THEN the media coordinator doesn't unregister the listener (since route2 is still active) verify(mockChipbarCoordinator, never()).unregisterListener(listenerCaptor.value) + verify(logger).logStateMapRemoval("route1", "reason") + } + + /** Regression test for b/266218672. */ + @Test + fun twoIdsDisplayed_oldIdIsFar_viewStillDisplayed() { + // WHEN there are two different media transfers with different IDs + val route1 = + MediaRoute2Info.Builder("route1", OTHER_DEVICE_NAME) + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + route1, + null, + ) + verify(windowManager).addView(any(), any()) + reset(windowManager) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + MediaRoute2Info.Builder("route2", "Route 2 name") + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build(), + null, + ) + val newView = getChipbarView() + + // WHEN there's a FAR event for the earlier one + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + route1, + null, + ) + + // THEN it's ignored and the more recent one is still displayed + assertThat(newView.getChipText()) + .isEqualTo( + ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText("Route 2 name") + ) + } + + /** Regression test for b/266218672. */ + @Test + fun receiverSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToEnd() { + displayReceiverTriggered() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + null, + ) + + fakeClock.advanceTime(TIMEOUT + 1L) + verify(windowManager).removeView(any()) + + reset(windowManager) + + // WHEN we try to show ALMOST_CLOSE_TO_END + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + routeInfo, + null, + ) + + // THEN it succeeds + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST.getExpectedStateText()) + } + + /** Regression test for b/266218672. */ + @Test + fun receiverSucceededThenTimedOut_internalStateResetAndCanDisplayReceiverTriggered() { + displayReceiverTriggered() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + null, + ) + + fakeClock.advanceTime(TIMEOUT + 1L) + verify(windowManager).removeView(any()) + + reset(windowManager) + + // WHEN we try to show RECEIVER_TRIGGERED + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfo, + null, + ) + + // THEN it succeeds + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + } + + /** Regression test for b/266218672. */ + @Test + fun toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToStart() { + displayThisDeviceTriggered() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + null, + ) + + fakeClock.advanceTime(TIMEOUT + 1L) + verify(windowManager).removeView(any()) + + reset(windowManager) + + // WHEN we try to show ALMOST_CLOSE_TO_START + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + null, + ) + + // THEN it succeeds + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText()) + } + + /** Regression test for b/266218672. */ + @Test + fun toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayThisDeviceTriggered() { + displayThisDeviceTriggered() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + null, + ) + + fakeClock.advanceTime(TIMEOUT + 1L) + verify(windowManager).removeView(any()) + + reset(windowManager) + + // WHEN we try to show THIS_DEVICE_TRIGGERED + val newRouteInfo = + MediaRoute2Info.Builder(DEFAULT_ID, "New Name") + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + newRouteInfo, + null, + ) + + // THEN it succeeds + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo( + ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText("New Name") + ) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) } private fun getChipbarView(): ViewGroup { @@ -1110,8 +1373,10 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button) - private fun ChipStateSender.getExpectedStateText(): String? { - return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context) + private fun ChipStateSender.getExpectedStateText( + otherDeviceName: String = OTHER_DEVICE_NAME, + ): String? { + return this.getChipTextString(context, otherDeviceName).loadText(context) } // display receiver triggered state helper method to make sure we start from a valid state diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt new file mode 100644 index 000000000000..003375709a16 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt @@ -0,0 +1,121 @@ +/* + * 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.media.taptotransfer.sender + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogcatEchoTracker +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.mock + +@SmallTest +class MediaTttSenderLoggerTest : SysuiTestCase() { + + private lateinit var buffer: LogBuffer + private lateinit var logger: MediaTttSenderLogger + + @Before + fun setUp() { + buffer = + LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)) + .create("buffer", 10) + logger = MediaTttSenderLogger(buffer) + } + + @Test + fun logStateChange_bufferHasDeviceTypeTagAndParamInfo() { + val stateName = "test state name" + val id = "test id" + val packageName = "this.is.a.package" + + logger.logStateChange(stateName, id, packageName) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(stateName) + assertThat(actualString).contains(id) + assertThat(actualString).contains(packageName) + } + + @Test + fun logStateChangeError_hasState() { + logger.logStateChangeError(3456) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains("3456") + } + + @Test + fun logPackageNotFound_bufferHasPackageName() { + val packageName = "this.is.a.package" + + logger.logPackageNotFound(packageName) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(packageName) + } + + @Test + fun logRemovalBypass_bufferHasReasons() { + val removalReason = "fakeRemovalReason" + val bypassReason = "fakeBypassReason" + + logger.logRemovalBypass(removalReason, bypassReason) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(removalReason) + assertThat(actualString).contains(bypassReason) + } + + @Test + fun logStateMap_bufferHasInfo() { + val map = + mapOf( + "123" to ChipStateSender.ALMOST_CLOSE_TO_START_CAST, + "456" to ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED, + ) + + logger.logStateMap(map) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains("123") + assertThat(actualString).contains(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.name) + assertThat(actualString).contains("456") + assertThat(actualString).contains(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.name) + } + + @Test + fun logStateMapRemoval_bufferHasInfo() { + logger.logStateMapRemoval("456", "testReason") + + val actualString = getStringFromBuffer() + assertThat(actualString).contains("456") + assertThat(actualString).contains("testReason") + } + + private fun getStringFromBuffer(): String { + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + return stringWriter.toString() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt new file mode 100644 index 000000000000..e8b6f9bd3478 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt @@ -0,0 +1,703 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.mediaprojection.devicepolicy + +import android.app.admin.DevicePolicyManager +import android.os.UserHandle +import android.os.UserManager +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertWithMessage +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters +import org.mockito.ArgumentMatchers.any + +abstract class BaseScreenCaptureDevicePolicyResolverTest(private val precondition: Preconditions) : + SysuiTestCase() { + + abstract class Preconditions( + val personalScreenCaptureDisabled: Boolean, + val workScreenCaptureDisabled: Boolean, + val disallowShareIntoManagedProfile: Boolean + ) + + protected val devicePolicyManager: DevicePolicyManager = mock() + protected val userManager: UserManager = mock() + + protected val personalUserHandle: UserHandle = UserHandle.of(123) + protected val workUserHandle: UserHandle = UserHandle.of(456) + + protected val policyResolver = + ScreenCaptureDevicePolicyResolver( + devicePolicyManager, + userManager, + personalUserHandle, + workUserHandle + ) + + @Before + fun setUp() { + setUpPolicies() + } + + private fun setUpPolicies() { + whenever( + devicePolicyManager.getScreenCaptureDisabled( + any(), + eq(personalUserHandle.identifier) + ) + ) + .thenReturn(precondition.personalScreenCaptureDisabled) + + whenever(devicePolicyManager.getScreenCaptureDisabled(any(), eq(workUserHandle.identifier))) + .thenReturn(precondition.workScreenCaptureDisabled) + + whenever( + userManager.hasUserRestrictionForUser( + eq(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE), + eq(workUserHandle) + ) + ) + .thenReturn(precondition.disallowShareIntoManagedProfile) + } +} + +@RunWith(Parameterized::class) +@SmallTest +class IsAllowedScreenCaptureDevicePolicyResolverTest( + private val test: IsScreenCaptureAllowedTestCase +) : BaseScreenCaptureDevicePolicyResolverTest(test.given) { + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun getParams() = + listOf( + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false, + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true, + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false, + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true, + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = true, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureAllowed = false, + ), + IsScreenCaptureAllowedTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + isTargetInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureAllowed = false, + ), + ) + } + + class Preconditions( + personalScreenCaptureDisabled: Boolean, + workScreenCaptureDisabled: Boolean, + disallowShareIntoManagedProfile: Boolean, + val isHostInWorkProfile: Boolean, + val isTargetInWorkProfile: Boolean, + ) : + BaseScreenCaptureDevicePolicyResolverTest.Preconditions( + personalScreenCaptureDisabled, + workScreenCaptureDisabled, + disallowShareIntoManagedProfile + ) + + data class IsScreenCaptureAllowedTestCase( + val given: Preconditions, + val expectedScreenCaptureAllowed: Boolean + ) { + override fun toString(): String = + "isScreenCaptureAllowed: " + + "host[${if (given.isHostInWorkProfile) "work" else "personal"} profile], " + + "target[${if (given.isTargetInWorkProfile) "work" else "personal"} profile], " + + "personal screen capture disabled = ${given.personalScreenCaptureDisabled}, " + + "work screen capture disabled = ${given.workScreenCaptureDisabled}, " + + "disallow share into managed profile = ${given.disallowShareIntoManagedProfile}, " + + "expected screen capture allowed = $expectedScreenCaptureAllowed" + } + + @Test + fun test() { + val targetAppUserHandle = + if (test.given.isTargetInWorkProfile) workUserHandle else personalUserHandle + val hostAppUserHandle = + if (test.given.isHostInWorkProfile) workUserHandle else personalUserHandle + + val screenCaptureAllowed = + policyResolver.isScreenCaptureAllowed(targetAppUserHandle, hostAppUserHandle) + + assertWithMessage("Screen capture policy resolved incorrectly") + .that(screenCaptureAllowed) + .isEqualTo(test.expectedScreenCaptureAllowed) + } +} + +@RunWith(Parameterized::class) +@SmallTest +class IsCompletelyNotAllowedScreenCaptureDevicePolicyResolverTest( + private val test: IsScreenCaptureCompletelyDisabledTestCase +) : BaseScreenCaptureDevicePolicyResolverTest(test.given) { + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun getParams() = + listOf( + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureCompletelyDisabled = false, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureCompletelyDisabled = false, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureCompletelyDisabled = false, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureCompletelyDisabled = false, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureCompletelyDisabled = true, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureCompletelyDisabled = true, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureCompletelyDisabled = true, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = false, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureCompletelyDisabled = true, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureCompletelyDisabled = false, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureCompletelyDisabled = false, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureCompletelyDisabled = true, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + personalScreenCaptureDisabled = false, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureCompletelyDisabled = true, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureCompletelyDisabled = false, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = false, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureCompletelyDisabled = false, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = false + ), + expectedScreenCaptureCompletelyDisabled = true, + ), + IsScreenCaptureCompletelyDisabledTestCase( + given = + Preconditions( + isHostInWorkProfile = true, + personalScreenCaptureDisabled = true, + workScreenCaptureDisabled = true, + disallowShareIntoManagedProfile = true + ), + expectedScreenCaptureCompletelyDisabled = true, + ) + ) + } + + class Preconditions( + personalScreenCaptureDisabled: Boolean, + workScreenCaptureDisabled: Boolean, + disallowShareIntoManagedProfile: Boolean, + val isHostInWorkProfile: Boolean, + ) : + BaseScreenCaptureDevicePolicyResolverTest.Preconditions( + personalScreenCaptureDisabled, + workScreenCaptureDisabled, + disallowShareIntoManagedProfile + ) + + data class IsScreenCaptureCompletelyDisabledTestCase( + val given: Preconditions, + val expectedScreenCaptureCompletelyDisabled: Boolean + ) { + override fun toString(): String = + "isScreenCaptureCompletelyDisabled: " + + "host[${if (given.isHostInWorkProfile) "work" else "personal"} profile], " + + "personal screen capture disabled = ${given.personalScreenCaptureDisabled}, " + + "work screen capture disabled = ${given.workScreenCaptureDisabled}, " + + "disallow share into managed profile = ${given.disallowShareIntoManagedProfile}, " + + "expected screen capture completely disabled = $expectedScreenCaptureCompletelyDisabled" + } + + @Test + fun test() { + val hostAppUserHandle = + if (test.given.isHostInWorkProfile) workUserHandle else personalUserHandle + + val completelyDisabled = policyResolver.isScreenCaptureCompletelyDisabled(hostAppUserHandle) + + assertWithMessage("Screen capture policy resolved incorrectly") + .that(completelyDisabled) + .isEqualTo(test.expectedScreenCaptureCompletelyDisabled) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java index 9bcfd5b2ae52..1a93adc7a631 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.settings.FakeDisplayTracker; import org.junit.Before; import org.junit.Test; @@ -46,7 +47,8 @@ public class SysUiStateTest extends SysuiTestCase { @Before public void setup() { - mFlagsContainer = new SysUiState(); + FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext); + mFlagsContainer = new SysUiState(displayTracker); mCallback = mock(SysUiState.SysUiStateCallback.class); mFlagsContainer.addCallback(mCallback); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java index 92652a788bcb..3eb73290636f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java @@ -40,6 +40,7 @@ import com.android.systemui.SysuiTestableContext; import com.android.systemui.assist.AssistManager; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.After; @@ -65,6 +66,7 @@ public class NavigationBarButtonTest extends SysuiTestCase { private ImageReader mReader; private NavigationBarView mNavBar; private VirtualDisplay mVirtualDisplay; + private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); @Before public void setup() { @@ -83,6 +85,7 @@ public class NavigationBarButtonTest extends SysuiTestCase { mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class, mEdgeBackGestureHandlerFactory); mNavBar = new NavigationBarView(context, null); + mNavBar.setDisplayTracker(mDisplayTracker); } private Display createVirtualDisplay() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index 8058b85e205a..aacbf8f2adeb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Configuration; +import android.hardware.display.DisplayManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.util.SparseArray; @@ -50,6 +51,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.CommandQueue; @@ -81,6 +83,7 @@ public class NavigationBarControllerTest extends SysuiTestCase { private NavigationBar mDefaultNavBar; private NavigationBar mSecondaryNavBar; private StaticMockitoSession mMockitoSession; + private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); @Mock private CommandQueue mCommandQueue; @@ -92,6 +95,7 @@ public class NavigationBarControllerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); mNavigationBarController = spy( new NavigationBarController(mContext, mock(OverviewProxyService.class), @@ -110,7 +114,8 @@ public class NavigationBarControllerTest extends SysuiTestCase { Optional.of(mock(Pip.class)), Optional.of(mock(BackAnimation.class)), mock(FeatureFlags.class), - mock(SecureSettings.class))); + mock(SecureSettings.class), + mDisplayTracker)); initializeNavigationBars(); mMockitoSession = mockitoSession().mockStatic(Utilities.class).startMocking(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 2ad865e6ef11..764ddc49d110 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -87,6 +87,7 @@ import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowView; @@ -495,7 +496,8 @@ public class NavigationBarTest extends SysuiTestCase { Optional.of(mock(BackAnimation.class)), mUserContextProvider, mWakefulnessLifecycle, - mTaskStackChangeListeners)); + mTaskStackChangeListeners, + new FakeDisplayTracker(mContext))); } private void processAllMessages() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java index cafd2cf7cea0..5270737b7f3e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java @@ -36,6 +36,7 @@ import com.android.systemui.assist.AssistManager; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.LightBarTransitionsController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -63,6 +64,7 @@ public class NavigationBarTransitionsTest extends SysuiTestCase { IWindowManager mIWindowManager; private NavigationBarTransitions mTransitions; + private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); @Before public void setup() { @@ -86,7 +88,7 @@ public class NavigationBarTransitionsTest extends SysuiTestCase { when(navBar.getCurrentView()).thenReturn(navBar); when(navBar.findViewById(anyInt())).thenReturn(navBar); mTransitions = new NavigationBarTransitions( - navBar, mIWindowManager, mLightBarTransitionsFactory); + navBar, mIWindowManager, mLightBarTransitionsFactory, mDisplayTracker); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt index 36e02cb1df06..bc67df6507fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt @@ -16,40 +16,50 @@ import org.junit.runners.Parameterized internal class FloatingRotationButtonPositionCalculatorTest(private val testCase: TestCase) : SysuiTestCase() { - private val calculator = FloatingRotationButtonPositionCalculator( - MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM - ) - @Test fun calculatePosition() { - val position = calculator.calculatePosition( + val position = testCase.calculator.calculatePosition( testCase.rotation, testCase.taskbarVisible, testCase.taskbarStashed ) - assertThat(position).isEqualTo(testCase.expectedPosition) } internal class TestCase( + val calculator: FloatingRotationButtonPositionCalculator, val rotation: Int, val taskbarVisible: Boolean, val taskbarStashed: Boolean, val expectedPosition: Position ) { override fun toString(): String = - "when rotation = $rotation, " + + "when calculator = $calculator, " + + "rotation = $rotation, " + "taskbarVisible = $taskbarVisible, " + "taskbarStashed = $taskbarStashed - " + "expected $expectedPosition" } companion object { + private const val MARGIN_DEFAULT = 10 + private const val MARGIN_TASKBAR_LEFT = 20 + private const val MARGIN_TASKBAR_BOTTOM = 30 + + private val posLeftCalculator = FloatingRotationButtonPositionCalculator( + MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM, true + ) + private val posRightCalculator = FloatingRotationButtonPositionCalculator( + MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM, false + ) + @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<TestCase> = listOf( + // Position left TestCase( + calculator = posLeftCalculator, rotation = Surface.ROTATION_0, taskbarVisible = false, taskbarStashed = false, @@ -60,6 +70,7 @@ internal class FloatingRotationButtonPositionCalculatorTest(private val testCase ) ), TestCase( + calculator = posLeftCalculator, rotation = Surface.ROTATION_90, taskbarVisible = false, taskbarStashed = false, @@ -70,6 +81,7 @@ internal class FloatingRotationButtonPositionCalculatorTest(private val testCase ) ), TestCase( + calculator = posLeftCalculator, rotation = Surface.ROTATION_180, taskbarVisible = false, taskbarStashed = false, @@ -80,6 +92,7 @@ internal class FloatingRotationButtonPositionCalculatorTest(private val testCase ) ), TestCase( + calculator = posLeftCalculator, rotation = Surface.ROTATION_270, taskbarVisible = false, taskbarStashed = false, @@ -90,6 +103,7 @@ internal class FloatingRotationButtonPositionCalculatorTest(private val testCase ) ), TestCase( + calculator = posLeftCalculator, rotation = Surface.ROTATION_0, taskbarVisible = true, taskbarStashed = false, @@ -100,6 +114,7 @@ internal class FloatingRotationButtonPositionCalculatorTest(private val testCase ) ), TestCase( + calculator = posLeftCalculator, rotation = Surface.ROTATION_0, taskbarVisible = true, taskbarStashed = true, @@ -110,6 +125,7 @@ internal class FloatingRotationButtonPositionCalculatorTest(private val testCase ) ), TestCase( + calculator = posLeftCalculator, rotation = Surface.ROTATION_90, taskbarVisible = true, taskbarStashed = false, @@ -118,11 +134,86 @@ internal class FloatingRotationButtonPositionCalculatorTest(private val testCase translationX = -MARGIN_TASKBAR_LEFT, translationY = -MARGIN_TASKBAR_BOTTOM ) + ), + + // Position right + TestCase( + calculator = posRightCalculator, + rotation = Surface.ROTATION_0, + taskbarVisible = false, + taskbarStashed = false, + expectedPosition = Position( + gravity = Gravity.BOTTOM or Gravity.RIGHT, + translationX = -MARGIN_DEFAULT, + translationY = -MARGIN_DEFAULT + ) + ), + TestCase( + calculator = posRightCalculator, + rotation = Surface.ROTATION_90, + taskbarVisible = false, + taskbarStashed = false, + expectedPosition = Position( + gravity = Gravity.TOP or Gravity.RIGHT, + translationX = -MARGIN_DEFAULT, + translationY = MARGIN_DEFAULT + ) + ), + TestCase( + calculator = posRightCalculator, + rotation = Surface.ROTATION_180, + taskbarVisible = false, + taskbarStashed = false, + expectedPosition = Position( + gravity = Gravity.TOP or Gravity.LEFT, + translationX = MARGIN_DEFAULT, + translationY = MARGIN_DEFAULT + ) + ), + TestCase( + calculator = posRightCalculator, + rotation = Surface.ROTATION_270, + taskbarVisible = false, + taskbarStashed = false, + expectedPosition = Position( + gravity = Gravity.BOTTOM or Gravity.LEFT, + translationX = MARGIN_DEFAULT, + translationY = -MARGIN_DEFAULT + ) + ), + TestCase( + calculator = posRightCalculator, + rotation = Surface.ROTATION_0, + taskbarVisible = true, + taskbarStashed = false, + expectedPosition = Position( + gravity = Gravity.BOTTOM or Gravity.RIGHT, + translationX = -MARGIN_TASKBAR_LEFT, + translationY = -MARGIN_TASKBAR_BOTTOM + ) + ), + TestCase( + calculator = posRightCalculator, + rotation = Surface.ROTATION_0, + taskbarVisible = true, + taskbarStashed = true, + expectedPosition = Position( + gravity = Gravity.BOTTOM or Gravity.RIGHT, + translationX = -MARGIN_DEFAULT, + translationY = -MARGIN_DEFAULT + ) + ), + TestCase( + calculator = posRightCalculator, + rotation = Surface.ROTATION_90, + taskbarVisible = true, + taskbarStashed = false, + expectedPosition = Position( + gravity = Gravity.TOP or Gravity.RIGHT, + translationX = -MARGIN_TASKBAR_LEFT, + translationY = MARGIN_TASKBAR_BOTTOM + ) ) ) - - private const val MARGIN_DEFAULT = 10 - private const val MARGIN_TASKBAR_LEFT = 20 - private const val MARGIN_TASKBAR_BOTTOM = 30 } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt index bbe60f4ba493..18be92ba27cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt @@ -16,17 +16,14 @@ package com.android.systemui.notetask -import android.content.ComponentName +import android.app.role.RoleManager import android.content.Intent -import android.content.pm.ActivityInfo -import android.content.pm.ApplicationInfo import android.content.pm.PackageManager -import android.content.pm.PackageManager.ResolveInfoFlags -import android.content.pm.ResolveInfo import android.test.suitebuilder.annotation.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -47,172 +44,39 @@ import org.mockito.MockitoAnnotations internal class NoteTaskIntentResolverTest : SysuiTestCase() { @Mock lateinit var packageManager: PackageManager + @Mock lateinit var roleManager: RoleManager - private lateinit var resolver: NoteTaskIntentResolver + private lateinit var underTest: NoteTaskIntentResolver @Before fun setUp() { MockitoAnnotations.initMocks(this) - resolver = NoteTaskIntentResolver(packageManager) - } - - private fun createResolveInfo( - activityInfo: ActivityInfo? = createActivityInfo(), - ): ResolveInfo { - return ResolveInfo().apply { this.activityInfo = activityInfo } - } - - private fun createActivityInfo( - packageName: String = "PackageName", - name: String? = "ActivityName", - exported: Boolean = true, - enabled: Boolean = true, - showWhenLocked: Boolean = true, - turnScreenOn: Boolean = true, - ): ActivityInfo { - return ActivityInfo().apply { - this.name = name - this.exported = exported - this.enabled = enabled - if (showWhenLocked) { - flags = flags or ActivityInfo.FLAG_SHOW_WHEN_LOCKED - } - if (turnScreenOn) { - flags = flags or ActivityInfo.FLAG_TURN_SCREEN_ON - } - this.applicationInfo = ApplicationInfo().apply { this.packageName = packageName } - } - } - - private fun givenQueryIntentActivities(block: () -> List<ResolveInfo>) { - whenever(packageManager.queryIntentActivities(any(), any<ResolveInfoFlags>())) - .thenReturn(block()) - } - - private fun givenResolveActivity(block: () -> ResolveInfo?) { - whenever(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenReturn(block()) - } - - @Test - fun resolveIntent_shouldReturnNotesIntent() { - givenQueryIntentActivities { listOf(createResolveInfo()) } - givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo()) } - - val actual = resolver.resolveIntent() - - val expected = - Intent(ACTION_CREATE_NOTE) - .setPackage("PackageName") - .setComponent(ComponentName("PackageName", "ActivityName")) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - // Compares the string representation of both intents, as they are different instances. - assertThat(actual.toString()).isEqualTo(expected.toString()) - } - - @Test - fun resolveIntent_activityInfoEnabledIsFalse_shouldReturnNull() { - givenQueryIntentActivities { listOf(createResolveInfo()) } - givenResolveActivity { - createResolveInfo(activityInfo = createActivityInfo(enabled = false)) - } - - val actual = resolver.resolveIntent() - - assertThat(actual).isNull() - } - - @Test - fun resolveIntent_activityInfoExportedIsFalse_shouldReturnNull() { - givenQueryIntentActivities { listOf(createResolveInfo()) } - givenResolveActivity { - createResolveInfo(activityInfo = createActivityInfo(exported = false)) - } - - val actual = resolver.resolveIntent() - - assertThat(actual).isNull() - } - - @Test - fun resolveIntent_activityInfoShowWhenLockedIsFalse_shouldReturnNull() { - givenQueryIntentActivities { listOf(createResolveInfo()) } - givenResolveActivity { - createResolveInfo(activityInfo = createActivityInfo(showWhenLocked = false)) - } - - val actual = resolver.resolveIntent() - - assertThat(actual).isNull() + underTest = NoteTaskIntentResolver(context, roleManager) } @Test - fun resolveIntent_activityInfoTurnScreenOnIsFalse_shouldReturnNull() { - givenQueryIntentActivities { listOf(createResolveInfo()) } - givenResolveActivity { - createResolveInfo(activityInfo = createActivityInfo(turnScreenOn = false)) - } + fun resolveIntent_shouldReturnIntentInStylusMode() { + val packageName = "com.android.note.app" + whenever(roleManager.getRoleHoldersAsUser(NoteTaskIntentResolver.ROLE_NOTES, context.user)) + .then { listOf(packageName) } - val actual = resolver.resolveIntent() + val actual = underTest.resolveIntent() - assertThat(actual).isNull() - } - - @Test - fun resolveIntent_activityInfoNameIsBlank_shouldReturnNull() { - givenQueryIntentActivities { listOf(createResolveInfo()) } - givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = "")) } - - val actual = resolver.resolveIntent() - - assertThat(actual).isNull() - } - - @Test - fun resolveIntent_activityInfoNameIsNull_shouldReturnNull() { - givenQueryIntentActivities { listOf(createResolveInfo()) } - givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = null)) } - - val actual = resolver.resolveIntent() - - assertThat(actual).isNull() - } - - @Test - fun resolveIntent_activityInfoIsNull_shouldReturnNull() { - givenQueryIntentActivities { listOf(createResolveInfo()) } - givenResolveActivity { createResolveInfo(activityInfo = null) } - - val actual = resolver.resolveIntent() - - assertThat(actual).isNull() - } - - @Test - fun resolveIntent_resolveActivityIsNull_shouldReturnNull() { - givenQueryIntentActivities { listOf(createResolveInfo()) } - givenResolveActivity { null } - - val actual = resolver.resolveIntent() - - assertThat(actual).isNull() - } - - @Test - fun resolveIntent_packageNameIsBlank_shouldReturnNull() { - givenQueryIntentActivities { - listOf(createResolveInfo(createActivityInfo(packageName = ""))) - } - - val actual = resolver.resolveIntent() - - assertThat(actual).isNull() + requireNotNull(actual) { "Intent must not be null" } + assertThat(actual.action).isEqualTo(ACTION_CREATE_NOTE) + assertThat(actual.`package`).isEqualTo(packageName) + val expectedExtra = actual.getExtra(NoteTaskIntentResolver.INTENT_EXTRA_USE_STYLUS_MODE) + assertThat(expectedExtra).isEqualTo(true) + val expectedFlag = actual.flags and Intent.FLAG_ACTIVITY_NEW_TASK + assertThat(expectedFlag).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) } @Test - fun resolveIntent_activityNotFoundForAction_shouldReturnNull() { - givenQueryIntentActivities { emptyList() } + fun resolveIntent_noRoleHolderIsSet_shouldReturnNull() { + whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskIntentResolver.ROLE_NOTES), any())) + .then { listOf<String>() } - val actual = resolver.resolveIntent() + val actual = underTest.resolveIntent() assertThat(actual).isNull() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java index 43fb1bd8636e..dee1cc8b0354 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java @@ -59,6 +59,7 @@ import java.util.concurrent.Executor; @SmallTest public class AutoAddTrackerTest extends SysuiTestCase { + private static final int END_POSITION = -1; private static final int USER = 0; @Mock @@ -142,6 +143,29 @@ public class AutoAddTrackerTest extends SysuiTestCase { } @Test + public void testRestoredTilePositionPreserved() { + verify(mBroadcastDispatcher).registerReceiver( + mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any()); + String restoredTiles = "saver,internet,work,cast"; + Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles); + + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); + + assertEquals(2, mAutoTracker.getRestoredTilePosition("work")); + } + + @Test + public void testNoRestoredTileReturnsEndPosition() { + verify(mBroadcastDispatcher).registerReceiver( + mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any()); + Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, null); + + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); + + assertEquals(END_POSITION, mAutoTracker.getRestoredTilePosition("work")); + } + + @Test public void testBroadcastReceiverRegistered() { verify(mBroadcastDispatcher).registerReceiver( any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 42ef9c2914ce..4caa50fa847d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -60,6 +60,7 @@ import com.android.systemui.qs.dagger.QSFragmentComponent; import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -494,7 +495,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { MockitoAnnotations.initMocks(this); - CommandQueue commandQueue = new CommandQueue(context); + CommandQueue commandQueue = new CommandQueue(context, new FakeDisplayTracker(context)); setupQsComponent(); setUpViews(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index 2bd068a674ae..8644b5ea18ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -35,12 +35,14 @@ import android.view.View import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.LaunchableFrameLayout import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable @@ -90,6 +92,7 @@ class CustomTileTest : SysuiTestCase() { private lateinit var customTile: CustomTile private lateinit var testableLooper: TestableLooper private lateinit var customTileBuilder: CustomTile.Builder + private val displayTracker = FakeDisplayTracker(mContext) @Before fun setUp() { @@ -119,7 +122,8 @@ class CustomTileTest : SysuiTestCase() { activityStarter, qsLogger, customTileStatePersister, - tileServices + tileServices, + displayTracker ) customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) @@ -339,7 +343,7 @@ class CustomTileTest : SysuiTestCase() { val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) tile.qsTile.activityLaunchForClick = pi - tile.handleClick(mock(View::class.java)) + tile.handleClick(mock(LaunchableFrameLayout::class.java)) testableLooper.processAllMessages() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt index b6a595b0077a..7ba2cf7f6374 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt @@ -35,9 +35,33 @@ class ActionIntentCreatorTest : SysuiTestCase() { @Test fun testCreateShareIntent() { val uri = Uri.parse("content://fake") + + val output = ActionIntentCreator.createShareIntent(uri) + + assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER) + assertFlagsSet( + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_GRANT_READ_URI_PERMISSION, + output.flags + ) + + val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND) + assertThat(wrappedIntent?.data).isEqualTo(uri) + assertThat(wrappedIntent?.type).isEqualTo("image/png") + assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull() + assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isNull() + assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) + .isEqualTo(uri) + } + + @Test + fun testCreateShareIntentWithSubject() { + val uri = Uri.parse("content://fake") val subject = "Example subject" - val output = ActionIntentCreator.createShareIntent(uri, subject) + val output = ActionIntentCreator.createShareIntentWithSubject(uri, subject) assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER) assertFlagsSet( @@ -52,16 +76,34 @@ class ActionIntentCreatorTest : SysuiTestCase() { assertThat(wrappedIntent?.data).isEqualTo(uri) assertThat(wrappedIntent?.type).isEqualTo("image/png") assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isEqualTo(subject) + assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isNull() assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) .isEqualTo(uri) } @Test - fun testCreateShareIntent_noSubject() { + fun testCreateShareIntentWithExtraText() { val uri = Uri.parse("content://fake") - val output = ActionIntentCreator.createShareIntent(uri, null) + val extraText = "Extra text" + + val output = ActionIntentCreator.createShareIntentWithExtraText(uri, extraText) + + assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER) + assertFlagsSet( + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_GRANT_READ_URI_PERMISSION, + output.flags + ) + val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND) + assertThat(wrappedIntent?.data).isEqualTo(uri) + assertThat(wrappedIntent?.type).isEqualTo("image/png") assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull() + assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(extraText) + assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) + .isEqualTo(uri) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java index e1eda117177d..d5014fa366f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java @@ -39,6 +39,7 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -67,6 +68,7 @@ public class ActionProxyReceiverTest extends SysuiTestCase { private PendingIntent mMockPendingIntent; private Intent mIntent; + private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); @Before public void setup() throws InterruptedException, ExecutionException, TimeoutException { @@ -135,10 +137,11 @@ public class ActionProxyReceiverTest extends SysuiTestCase { if (withStatusBar) { return new ActionProxyReceiver( Optional.of(mMockCentralSurfaces), mMockActivityManagerWrapper, - mMockScreenshotSmartActions); + mMockScreenshotSmartActions, mDisplayTracker); } else { return new ActionProxyReceiver( - Optional.empty(), mMockActivityManagerWrapper, mMockScreenshotSmartActions); + Optional.empty(), mMockActivityManagerWrapper, mMockScreenshotSmartActions, + mDisplayTracker); } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt new file mode 100644 index 000000000000..9f0a803fac40 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt @@ -0,0 +1,143 @@ +package com.android.systemui.screenshot + +import android.graphics.drawable.Drawable +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.Guideline +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class MessageContainerControllerTest : SysuiTestCase() { + lateinit var messageContainer: MessageContainerController + + @Mock lateinit var workProfileMessageController: WorkProfileMessageController + + @Mock lateinit var screenshotDetectionController: ScreenshotDetectionController + + @Mock lateinit var icon: Drawable + + lateinit var workProfileFirstRunView: ViewGroup + lateinit var detectionNoticeView: ViewGroup + lateinit var container: FrameLayout + + var featureFlags = FakeFeatureFlags() + lateinit var screenshotView: ViewGroup + + val userHandle = UserHandle.of(5) + val screenshotData = ScreenshotData.forTesting() + + val appName = "app name" + lateinit var workProfileData: WorkProfileMessageController.WorkProfileFirstRunData + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + messageContainer = + MessageContainerController( + workProfileMessageController, + screenshotDetectionController, + featureFlags + ) + screenshotView = ConstraintLayout(mContext) + workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon) + + val guideline = Guideline(mContext) + guideline.id = com.android.systemui.R.id.guideline + screenshotView.addView(guideline) + + container = FrameLayout(mContext) + container.id = com.android.systemui.R.id.screenshot_message_container + screenshotView.addView(container) + + workProfileFirstRunView = FrameLayout(mContext) + workProfileFirstRunView.id = com.android.systemui.R.id.work_profile_first_run + container.addView(workProfileFirstRunView) + + detectionNoticeView = FrameLayout(mContext) + detectionNoticeView.id = com.android.systemui.R.id.screenshot_detection_notice + container.addView(detectionNoticeView) + + messageContainer.setView(screenshotView) + + screenshotData.userHandle = userHandle + } + + @Test + fun testOnScreenshotTakenUserHandle_noWorkProfileFirstRun() { + featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true) + // (just being explicit here) + whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(null) + + messageContainer.onScreenshotTaken(userHandle) + + verify(workProfileMessageController, never()).populateView(any(), any(), any()) + } + + @Test + fun testOnScreenshotTakenUserHandle_noWorkProfileFlag() { + featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false) + + messageContainer.onScreenshotTaken(userHandle) + + verify(workProfileMessageController, never()).onScreenshotTaken(any()) + verify(workProfileMessageController, never()).populateView(any(), any(), any()) + } + + @Test + fun testOnScreenshotTakenUserHandle_withWorkProfileFirstRun() { + featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true) + whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle))) + .thenReturn(workProfileData) + messageContainer.onScreenshotTaken(userHandle) + + verify(workProfileMessageController) + .populateView(eq(workProfileFirstRunView), eq(workProfileData), any()) + assertEquals(View.VISIBLE, workProfileFirstRunView.visibility) + assertEquals(View.GONE, detectionNoticeView.visibility) + } + + @Test + fun testOnScreenshotTakenScreenshotData_flagsOff() { + featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false) + featureFlags.set(Flags.SCREENSHOT_DETECTION, false) + + messageContainer.onScreenshotTaken(screenshotData) + + verify(workProfileMessageController, never()).onScreenshotTaken(any()) + verify(screenshotDetectionController, never()).maybeNotifyOfScreenshot(any()) + + assertEquals(View.GONE, container.visibility) + } + + @Test + fun testOnScreenshotTakenScreenshotData_nothingToShow() { + featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true) + featureFlags.set(Flags.SCREENSHOT_DETECTION, true) + + messageContainer.onScreenshotTaken(screenshotData) + + verify(workProfileMessageController, never()).populateView(any(), any(), any()) + verify(screenshotDetectionController, never()).populateView(any(), any()) + + assertEquals(View.GONE, container.visibility) + } +} 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 ed3f1a059e61..541d6c25192f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt @@ -73,6 +73,30 @@ class RequestProcessorTest { assertThat(result).isEqualTo(request) } + /** Tests the Java-compatible function wrapper, ensures callback is invoked. */ + @Test + fun testProcessAsync_ScreenshotData() { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false) + + val request = ScreenshotData.fromRequest( + ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()) + val processor = RequestProcessor(imageCapture, policy, flags, scope) + + var result: ScreenshotData? = null + var callbackCount = 0 + val callback: (ScreenshotData) -> Unit = { processedRequest: ScreenshotData -> + 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) @@ -85,6 +109,11 @@ class RequestProcessorTest { // No changes assertThat(processedRequest).isEqualTo(request) + + val screenshotData = ScreenshotData.fromRequest(request) + val processedData = processor.process(screenshotData) + + assertThat(processedData).isEqualTo(screenshotData) } @Test @@ -108,6 +137,13 @@ class RequestProcessorTest { assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN) assertThat(processedRequest.source).isEqualTo(SCREENSHOT_OTHER) assertThat(processedRequest.topComponent).isEqualTo(component) + + val processedData = processor.process(ScreenshotData.fromRequest(request)) + + // Request has topComponent added, but otherwise unchanged. + assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN) + assertThat(processedData.source).isEqualTo(SCREENSHOT_OTHER) + assertThat(processedData.topComponent).isEqualTo(component) } @Test @@ -140,6 +176,18 @@ class RequestProcessorTest { assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID) assertThat(processedRequest.userId).isEqualTo(USER_ID) assertThat(processedRequest.topComponent).isEqualTo(component) + + val processedData = processor.process(ScreenshotData.fromRequest(request)) + + // Expect a task snapshot is taken, overriding the full screen mode + assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE) + assertThat(processedData.bitmap).isEqualTo(bitmap) + assertThat(processedData.screenBounds).isEqualTo(bounds) + assertThat(processedData.insets).isEqualTo(Insets.NONE) + assertThat(processedData.taskId).isEqualTo(TASK_ID) + assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID) + assertThat(processedRequest.userId).isEqualTo(USER_ID) + assertThat(processedRequest.topComponent).isEqualTo(component) } @Test @@ -165,6 +213,11 @@ class RequestProcessorTest { // No changes assertThat(processedRequest).isEqualTo(request) + + val screenshotData = ScreenshotData.fromRequest(request) + val processedData = processor.process(screenshotData) + + assertThat(processedData).isEqualTo(screenshotData) } @Test @@ -192,6 +245,11 @@ class RequestProcessorTest { // No changes assertThat(processedRequest).isEqualTo(request) + + val screenshotData = ScreenshotData.fromRequest(request) + val processedData = processor.process(screenshotData) + + assertThat(processedData).isEqualTo(screenshotData) } @Test @@ -220,6 +278,11 @@ class RequestProcessorTest { // Work profile, but already a task snapshot, so no changes assertThat(processedRequest).isEqualTo(request) + + val screenshotData = ScreenshotData.fromRequest(request) + val processedData = processor.process(screenshotData) + + assertThat(processedData).isEqualTo(screenshotData) } private fun makeHardwareBitmap(width: Int, height: Int): Bitmap { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt new file mode 100644 index 000000000000..43e99393b874 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.content.ComponentName +import android.graphics.Insets +import android.graphics.Rect +import android.os.UserHandle +import android.view.WindowManager +import com.android.internal.util.ScreenshotRequest +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class ScreenshotDataTest { + private val type = WindowManager.TAKE_SCREENSHOT_FULLSCREEN + private val source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER + private val bounds = Rect(1, 2, 3, 4) + private val taskId = 123 + private val userId = 1 + private val insets = Insets.of(1, 2, 3, 4) + private val component = ComponentName("android.test", "android.test.Component") + + @Test + fun testConstruction() { + val request = + ScreenshotRequest.Builder(type, source) + .setBoundsOnScreen(bounds) + .setInsets(insets) + .setTaskId(taskId) + .setUserId(userId) + .setTopComponent(component) + .build() + + val data = ScreenshotData.fromRequest(request) + + assertThat(data.source).isEqualTo(source) + assertThat(data.type).isEqualTo(type) + assertThat(data.screenBounds).isEqualTo(bounds) + assertThat(data.insets).isEqualTo(insets) + assertThat(data.taskId).isEqualTo(taskId) + assertThat(data.userHandle).isEqualTo(UserHandle.of(userId)) + assertThat(data.topComponent).isEqualTo(component) + } + + @Test + fun testNegativeUserId() { + val request = ScreenshotRequest.Builder(type, source).setUserId(-1).build() + + val data = ScreenshotData.fromRequest(request) + + assertThat(data.userHandle).isNull() + } + + @Test + fun testPackageNameAsString() { + val request = ScreenshotRequest.Builder(type, source).setTopComponent(component).build() + + val data = ScreenshotData.fromRequest(request) + + assertThat(data.packageNameString).isEqualTo("android.test") + } + + @Test + fun testPackageNameAsString_null() { + val request = ScreenshotRequest.Builder(type, source).build() + + val data = ScreenshotData.fromRequest(request) + + assertThat(data.packageNameString).isEqualTo("") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt index 17396b13036c..e70fa2f915bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt @@ -31,6 +31,7 @@ import android.os.UserManager import android.testing.AndroidTestingRunner import com.android.systemui.SysuiTestCase import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo +import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers @@ -126,8 +127,10 @@ class ScreenshotPolicyImplTest : SysuiTestCase() { val userManager = mock<UserManager>() val atmService = mock<IActivityTaskManager>() val dispatcher = Dispatchers.Unconfined + val displayTracker = FakeDisplayTracker(mContext) - return object : ScreenshotPolicyImpl(context, userManager, atmService, dispatcher) { + return object : ScreenshotPolicyImpl(context, userManager, atmService, dispatcher, + displayTracker) { override suspend fun isManagedProfile(userId: Int) = (userId == MANAGED_PROFILE_USER) override suspend fun getAllRootTaskInfosOnDisplay(displayId: Int) = tasks override suspend fun isNotificationShadeExpanded() = shadeExpanded diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index f93501928844..74969d0e4737 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -29,7 +29,7 @@ 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_KEY_OTHER import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE @@ -39,7 +39,8 @@ import com.android.internal.util.ScreenshotRequest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY -import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD +import com.android.systemui.flags.Flags.SCREENSHOT_METADATA +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback import com.android.systemui.util.mockito.any @@ -106,15 +107,22 @@ class TakeScreenshotServiceTest : SysuiTestCase() { // Stub request processor as a synchronous no-op for tests with the flag enabled doAnswer { - val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest - val consumer: Consumer<ScreenshotRequest> = it.getArgument(1) - consumer.accept(request) - } - .`when`(requestProcessor) - .processAsync(/* request= */ any(), /* callback= */ any()) + val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest + val consumer: Consumer<ScreenshotRequest> = it.getArgument(1) + consumer.accept(request) + }.`when`(requestProcessor).processAsync( + /* request= */ any(ScreenshotRequest::class.java), /* callback= */ any()) + + doAnswer { + val request: ScreenshotData = it.getArgument(0) as ScreenshotData + val consumer: Consumer<ScreenshotData> = it.getArgument(1) + consumer.accept(request) + }.`when`(requestProcessor).processAsync( + /* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any()) // Flipped in selected test cases flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false) + flags.set(SCREENSHOT_METADATA, false) service.attach( mContext, @@ -141,7 +149,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { @Test fun takeScreenshotFullscreen() { val request = - ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD) + ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER) .setTopComponent(topComponent) .build() @@ -157,16 +165,34 @@ class TakeScreenshotServiceTest : SysuiTestCase() { 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 - ) + assertEquals("Expected SCREENSHOT_REQUESTED UiEvent", + logEvent.eventId, SCREENSHOT_REQUESTED_KEY_OTHER.id) + assertEquals("Expected supplied package name", + topComponent.packageName, eventLogger.get(0).packageName) + } + + @Test + fun takeScreenshotFullscreen_screenshotDataEnabled() { + flags.set(SCREENSHOT_METADATA, true) + + val request = ScreenshotRequest.Builder( + TAKE_SCREENSHOT_FULLSCREEN, + SCREENSHOT_KEY_OTHER).setTopComponent(topComponent).build() + + service.handleRequest(request, { /* onSaved */ }, callback) + + verify(controller, times(1)).handleScreenshot( + eq(ScreenshotData.fromRequest(request)), + /* 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_OTHER.id) + assertEquals("Expected supplied package name", + topComponent.packageName, eventLogger.get(0).packageName) } @Test @@ -218,7 +244,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { whenever(userManager.isUserUnlocked).thenReturn(false) val request = - ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD) + ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER) .setTopComponent(topComponent) .build() @@ -244,7 +270,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN") val request = - ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD) + ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER) .setTopComponent(topComponent) .build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java index bd04b3ccc039..576652f4dbdb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java @@ -16,13 +16,11 @@ package com.android.systemui.screenshot; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; @@ -33,24 +31,33 @@ import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; import android.testing.AndroidTestingRunner; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; import androidx.test.filters.SmallTest; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; import com.android.systemui.util.FakeSharedPreferences; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import kotlin.Unit; + @SmallTest @RunWith(AndroidTestingRunner.class) -public class WorkProfileMessageControllerTest { +public class WorkProfileMessageControllerTest extends SysuiTestCase { private static final String DEFAULT_LABEL = "default label"; private static final String BADGED_DEFAULT_LABEL = "badged default label"; private static final String APP_LABEL = "app label"; @@ -63,17 +70,13 @@ public class WorkProfileMessageControllerTest { @Mock private PackageManager mPackageManager; @Mock - private Context mContext; - @Mock - private WorkProfileMessageController.WorkProfileMessageDisplay mMessageDisplay; + private Context mMockContext; @Mock private Drawable mActivityIcon; @Mock private Drawable mBadgedActivityIcon; @Mock private ActivityInfo mActivityInfo; - @Captor - private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; private FakeSharedPreferences mSharedPreferences = new FakeSharedPreferences(); @@ -84,10 +87,10 @@ public class WorkProfileMessageControllerTest { MockitoAnnotations.initMocks(this); when(mUserManager.isManagedProfile(eq(WORK_USER.getIdentifier()))).thenReturn(true); - when(mContext.getSharedPreferences( + when(mMockContext.getSharedPreferences( eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME), eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences); - when(mContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL); + when(mMockContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL); when(mPackageManager.getUserBadgedLabel(eq(DEFAULT_LABEL), any())) .thenReturn(BADGED_DEFAULT_LABEL); when(mPackageManager.getUserBadgedLabel(eq(APP_LABEL), any())) @@ -103,16 +106,13 @@ public class WorkProfileMessageControllerTest { mSharedPreferences.edit().putBoolean( WorkProfileMessageController.PREFERENCE_KEY, false).apply(); - mMessageController = new WorkProfileMessageController(mContext, mUserManager, + mMessageController = new WorkProfileMessageController(mMockContext, mUserManager, mPackageManager); } @Test public void testOnScreenshotTaken_notManaged() { - mMessageController.onScreenshotTaken(NON_WORK_USER, mMessageDisplay); - - verify(mMessageDisplay, never()) - .showWorkProfileMessage(any(), nullable(Drawable.class), any()); + assertNull(mMessageController.onScreenshotTaken(NON_WORK_USER)); } @Test @@ -120,10 +120,7 @@ public class WorkProfileMessageControllerTest { mSharedPreferences.edit().putBoolean( WorkProfileMessageController.PREFERENCE_KEY, true).apply(); - mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay); - - verify(mMessageDisplay, never()) - .showWorkProfileMessage(any(), nullable(Drawable.class), any()); + assertNull(mMessageController.onScreenshotTaken(WORK_USER)); } @Test @@ -133,28 +130,45 @@ public class WorkProfileMessageControllerTest { any(PackageManager.ComponentInfoFlags.class))).thenThrow( new PackageManager.NameNotFoundException()); - mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay); + WorkProfileMessageController.WorkProfileFirstRunData data = + mMessageController.onScreenshotTaken(WORK_USER); - verify(mMessageDisplay).showWorkProfileMessage( - eq(BADGED_DEFAULT_LABEL), eq(null), any()); + assertEquals(BADGED_DEFAULT_LABEL, data.getAppName()); + assertNull(data.getIcon()); } @Test public void testOnScreenshotTaken() { - mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay); + WorkProfileMessageController.WorkProfileFirstRunData data = + mMessageController.onScreenshotTaken(WORK_USER); - verify(mMessageDisplay).showWorkProfileMessage( - eq(BADGED_APP_LABEL), eq(mBadgedActivityIcon), mRunnableArgumentCaptor.capture()); - - // Dismiss hasn't been tapped, preference untouched. - assertFalse( - mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false)); - - mRunnableArgumentCaptor.getValue().run(); + assertEquals(BADGED_APP_LABEL, data.getAppName()); + assertEquals(mBadgedActivityIcon, data.getIcon()); + } - // After dismiss has been tapped, the setting should be updated. - assertTrue( - mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false)); + @Test + public void testPopulateView() throws InterruptedException { + ViewGroup layout = (ViewGroup) LayoutInflater.from(mContext).inflate( + R.layout.screenshot_work_profile_first_run, null); + WorkProfileMessageController.WorkProfileFirstRunData data = + new WorkProfileMessageController.WorkProfileFirstRunData(BADGED_APP_LABEL, + mBadgedActivityIcon); + final CountDownLatch countdown = new CountDownLatch(1); + mMessageController.populateView(layout, data, () -> { + countdown.countDown(); + return Unit.INSTANCE; + }); + + ImageView image = layout.findViewById(R.id.screenshot_message_icon); + assertEquals(mBadgedActivityIcon, image.getDrawable()); + TextView text = layout.findViewById(R.id.screenshot_message_content); + // The app name is used in a template, but at least validate that it was inserted. + assertTrue(text.getText().toString().contains(BADGED_APP_LABEL)); + + // Validate that clicking the dismiss button calls back properly. + assertEquals(1, countdown.getCount()); + layout.findViewById(R.id.message_dismiss_button).callOnClick(); + countdown.await(1000, TimeUnit.MILLISECONDS); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt new file mode 100644 index 000000000000..ae976a0ea703 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt @@ -0,0 +1,176 @@ +/* + * 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.settings + +import android.hardware.display.DisplayManager +import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS +import android.hardware.display.DisplayManagerGlobal +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.view.Display +import android.view.DisplayAdjustments +import android.view.DisplayInfo +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.eq +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DisplayTrackerImplTest : SysuiTestCase() { + @Mock private lateinit var displayManager: DisplayManager + @Mock private lateinit var handler: Handler + + private val executor = Executor(Runnable::run) + private lateinit var mDefaultDisplay: Display + private lateinit var mSecondaryDisplay: Display + private lateinit var tracker: DisplayTrackerImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + mDefaultDisplay = + Display( + DisplayManagerGlobal.getInstance(), + Display.DEFAULT_DISPLAY, + DisplayInfo(), + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS + ) + mSecondaryDisplay = + Display( + DisplayManagerGlobal.getInstance(), + Display.DEFAULT_DISPLAY + 1, + DisplayInfo(), + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS + ) + + `when`(displayManager.displays).thenReturn(arrayOf(mDefaultDisplay, mSecondaryDisplay)) + + tracker = DisplayTrackerImpl(displayManager, handler) + } + + @Test + fun testGetDefaultDisplay() { + assertThat(tracker.defaultDisplayId).isEqualTo(Display.DEFAULT_DISPLAY) + } + + @Test + fun testGetAllDisplays() { + assertThat(tracker.allDisplays).isEqualTo(arrayOf(mDefaultDisplay, mSecondaryDisplay)) + } + + @Test + fun registerCallback_registersDisplayListener() { + tracker.addDisplayChangeCallback(TestCallback(), executor) + verify(displayManager).registerDisplayListener(any(), any()) + } + + @Test + fun registerBrightnessCallback_registersDisplayListener() { + tracker.addBrightnessChangeCallback(TestCallback(), executor) + verify(displayManager) + .registerDisplayListener(any(), any(), eq(EVENT_FLAG_DISPLAY_BRIGHTNESS)) + } + + @Test + fun unregisterCallback_displayListenerStillRegistered() { + val callback1 = TestCallback() + tracker.addDisplayChangeCallback(callback1, executor) + tracker.addDisplayChangeCallback(TestCallback(), executor) + tracker.removeCallback(callback1) + + verify(displayManager, never()).unregisterDisplayListener(any()) + } + + @Test + fun unregisterLastCallback_unregistersDisplayListener() { + val callback = TestCallback() + tracker.addDisplayChangeCallback(callback, executor) + tracker.removeCallback(callback) + + verify(displayManager).unregisterDisplayListener(any()) + } + + @Test + fun callbackCalledOnDisplayAdd() { + val testDisplay = 2 + val callback = TestCallback() + tracker.addDisplayChangeCallback(callback, executor) + tracker.displayChangedListener.onDisplayAdded(testDisplay) + + assertThat(callback.lastDisplayAdded).isEqualTo(testDisplay) + } + + @Test + fun callbackCalledOnDisplayRemoved() { + val testDisplay = 2 + val callback = TestCallback() + tracker.addDisplayChangeCallback(callback, executor) + tracker.displayChangedListener.onDisplayRemoved(testDisplay) + + assertThat(callback.lastDisplayRemoved).isEqualTo(testDisplay) + } + + @Test + fun callbackCalledOnDisplayChanged() { + val testDisplay = 2 + val callback = TestCallback() + tracker.addDisplayChangeCallback(callback, executor) + tracker.displayChangedListener.onDisplayChanged(testDisplay) + + assertThat(callback.lastDisplayChanged).isEqualTo(testDisplay) + } + + @Test + fun callbackCalledOnBrightnessChanged() { + val testDisplay = 2 + val callback = TestCallback() + tracker.addBrightnessChangeCallback(callback, executor) + tracker.displayBrightnessChangedListener.onDisplayChanged(testDisplay) + + assertThat(callback.lastDisplayChanged).isEqualTo(testDisplay) + } + + private class TestCallback : DisplayTracker.Callback { + var lastDisplayAdded = -1 + var lastDisplayRemoved = -1 + var lastDisplayChanged = -1 + + override fun onDisplayAdded(displayId: Int) { + lastDisplayAdded = displayId + } + + override fun onDisplayRemoved(displayId: Int) { + lastDisplayRemoved = displayId + } + + override fun onDisplayChanged(displayId: Int) { + lastDisplayChanged = displayId + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt index 3710281499b3..57b6b2bd6fde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.settings +import android.app.IActivityManager import android.content.Context import android.content.Intent import android.content.pm.UserInfo @@ -51,6 +52,7 @@ class UserTrackerImplReceiveTest : SysuiTestCase() { @Mock private lateinit var context: Context @Mock private lateinit var userManager: UserManager + @Mock private lateinit var iActivityManager: IActivityManager @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager @Mock(stubOnly = true) private lateinit var handler: Handler @@ -67,7 +69,7 @@ class UserTrackerImplReceiveTest : SysuiTestCase() { `when`(context.user).thenReturn(UserHandle.SYSTEM) `when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context) - tracker = UserTrackerImpl(context, userManager, dumpManager, handler) + tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt index e65bbb1bea08..71ba21538a8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt @@ -16,11 +16,14 @@ package com.android.systemui.settings +import android.app.IActivityManager +import android.app.IUserSwitchObserver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.UserInfo import android.os.Handler +import android.os.IRemoteCallback import android.os.UserHandle import android.os.UserManager import android.testing.AndroidTestingRunner @@ -29,19 +32,20 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.util.mockito.capture import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.eq import org.mockito.ArgumentMatchers.isNull import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import java.util.concurrent.Executor @SmallTest @RunWith(AndroidTestingRunner::class) @@ -51,6 +55,10 @@ class UserTrackerImplTest : SysuiTestCase() { private lateinit var context: Context @Mock private lateinit var userManager: UserManager + @Mock + private lateinit var iActivityManager: IActivityManager + @Mock + private lateinit var userSwitchingReply: IRemoteCallback @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager @Mock(stubOnly = true) @@ -76,7 +84,7 @@ class UserTrackerImplTest : SysuiTestCase() { listOf(info) } - tracker = UserTrackerImpl(context, userManager, dumpManager, handler) + tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler) } @Test @@ -125,8 +133,7 @@ class UserTrackerImplTest : SysuiTestCase() { verify(context).registerReceiverForAllUsers( eq(tracker), capture(captor), isNull(), eq(handler)) with(captor.value) { - assertThat(countActions()).isEqualTo(7) - assertThat(hasAction(Intent.ACTION_USER_SWITCHED)).isTrue() + assertThat(countActions()).isEqualTo(6) assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue() assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue() assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue() @@ -158,8 +165,10 @@ class UserTrackerImplTest : SysuiTestCase() { tracker.initialize(0) val newID = 5 - val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID) - tracker.onReceive(context, intent) + val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) + verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onUserSwitching(newID, userSwitchingReply) + verify(userSwitchingReply).sendResult(any()) verify(userManager).getProfiles(newID) @@ -272,6 +281,24 @@ class UserTrackerImplTest : SysuiTestCase() { } @Test + fun testCallbackCalledOnUserChanging() { + tracker.initialize(0) + val callback = TestCallback() + tracker.addCallback(callback, executor) + + val newID = 5 + + val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) + verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onUserSwitching(newID, userSwitchingReply) + verify(userSwitchingReply).sendResult(any()) + + assertThat(callback.calledOnUserChanging).isEqualTo(1) + assertThat(callback.lastUser).isEqualTo(newID) + assertThat(callback.lastUserContext?.userId).isEqualTo(newID) + } + + @Test fun testCallbackCalledOnUserChanged() { tracker.initialize(0) val callback = TestCallback() @@ -279,8 +306,9 @@ class UserTrackerImplTest : SysuiTestCase() { val newID = 5 - val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID) - tracker.onReceive(context, intent) + val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) + verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onUserSwitchComplete(newID) assertThat(callback.calledOnUserChanged).isEqualTo(1) assertThat(callback.lastUser).isEqualTo(newID) @@ -330,25 +358,36 @@ class UserTrackerImplTest : SysuiTestCase() { tracker.addCallback(callback, executor) tracker.removeCallback(callback) - val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, 5) - tracker.onReceive(context, intent) + val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) + verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onUserSwitching(newID, userSwitchingReply) + verify(userSwitchingReply).sendResult(any()) + captor.value.onUserSwitchComplete(newID) val intentProfiles = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID)) tracker.onReceive(context, intentProfiles) + assertThat(callback.calledOnUserChanging).isEqualTo(0) assertThat(callback.calledOnUserChanged).isEqualTo(0) assertThat(callback.calledOnProfilesChanged).isEqualTo(0) } private class TestCallback : UserTracker.Callback { + var calledOnUserChanging = 0 var calledOnUserChanged = 0 var calledOnProfilesChanged = 0 var lastUser: Int? = null var lastUserContext: Context? = null var lastUserProfiles = emptyList<UserInfo>() + override fun onUserChanging(newUser: Int, userContext: Context) { + calledOnUserChanging++ + lastUser = newUser + lastUserContext = userContext + } + override fun onUserChanged(newUser: Int, userContext: Context) { calledOnUserChanged++ lastUser = newUser @@ -360,4 +399,4 @@ class UserTrackerImplTest : SysuiTestCase() { lastUserProfiles = profiles } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index 9d1802a686fa..58ade49648d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -28,6 +28,7 @@ import androidx.test.rule.ActivityTestRule import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat @@ -51,6 +52,7 @@ class BrightnessDialogTest : SysuiTestCase() { @Mock private lateinit var mainExecutor: Executor @Mock private lateinit var backgroundHandler: Handler @Mock private lateinit var brightnessSliderController: BrightnessSliderController + private val displayTracker = FakeDisplayTracker(mContext) @Rule @JvmField @@ -60,6 +62,7 @@ class BrightnessDialogTest : SysuiTestCase() { override fun create(intent: Intent?): TestDialog { return TestDialog( userTracker, + displayTracker, brightnessSliderControllerFactory, mainExecutor, backgroundHandler @@ -105,12 +108,14 @@ class BrightnessDialogTest : SysuiTestCase() { class TestDialog( userTracker: UserTracker, + displayTracker: FakeDisplayTracker, brightnessSliderControllerFactory: BrightnessSliderController.Factory, mainExecutor: Executor, backgroundHandler: Handler ) : BrightnessDialog( userTracker, + displayTracker, brightnessSliderControllerFactory, mainExecutor, backgroundHandler diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt index ed9baf5b1c9f..3706859a5b74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt @@ -90,7 +90,7 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { fun testEdgeElementsAlignedWithEdgeOrGuide_qs() { with(qsConstraint) { assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID) - assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f) + assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0.5f) assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID) assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0.5f) @@ -342,7 +342,6 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { R.id.clock to "clock", R.id.date to "date", R.id.privacy_container to "privacy", - R.id.carrier_group to "carriers", ) views.forEach { (id, name) -> assertWithMessage("$name has 0 height in qqs") @@ -361,7 +360,6 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { val views = mapOf( R.id.clock to "clock", R.id.privacy_container to "privacy", - R.id.carrier_group to "carriers", ) views.forEach { (id, name) -> expect.withMessage("$name changes height") 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 f580f5e00f67..91fef1daaf86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -692,6 +692,19 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header) } + @Test + fun `carrier left padding is set when clock layout changes`() { + val width = 200 + whenever(clock.width).thenReturn(width) + whenever(clock.scaleX).thenReturn(2.57f) // 2.57 comes from qs_header.xml + val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java) + + verify(clock).addOnLayoutChangeListener(capture(captor)) + captor.value.onLayoutChange(clock, 0, 0, width, 0, 0, 0, 0, 0) + + verify(carrierGroup).setPaddingRelative(514, 0, 0, 0) + } + private fun View.executeLayoutChange( left: Int, top: Int, 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 b568122d3fed..2bf2a81fe13e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.shade +import android.animation.Animator import android.animation.ValueAnimator import android.app.StatusBarManager import android.content.Context @@ -45,8 +46,8 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions -import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @@ -293,6 +294,34 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { } @Test + fun animatorListenersClearedAtEnd() { + val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + whenever(view.animate()).thenReturn(animator) + + mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L) + val listenerCaptor = argumentCaptor<Animator.AnimatorListener>() + verify(animator).setListener(capture(listenerCaptor)) + + listenerCaptor.value.onAnimationEnd(mock()) + verify(animator).setListener(null) + verify(animator).setUpdateListener(null) + } + + @Test + fun animatorListenersClearedOnCancel() { + val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + whenever(view.animate()).thenReturn(animator) + + mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L) + val listenerCaptor = argumentCaptor<Animator.AnimatorListener>() + verify(animator).setListener(capture(listenerCaptor)) + + listenerCaptor.value.onAnimationCancel(mock()) + verify(animator).setListener(null) + verify(animator).setUpdateListener(null) + } + + @Test fun demoMode_attachDemoMode() { val cb = argumentCaptor<DemoMode>() verify(demoModeController).addCallback(capture(cb)) 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 0f3d4a8ca59a..28f7edf4ffb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -97,6 +97,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.common.ui.view.LongPressHandlingView; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; @@ -105,10 +106,12 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; +import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; @@ -197,7 +200,7 @@ import kotlinx.coroutines.CoroutineDispatcher; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class NotificationPanelViewControllerTest extends SysuiTestCase { private static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400; @@ -302,6 +305,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; + @Mock private KeyguardInteractor mKeyguardInteractor; + @Mock private KeyguardLongPressViewModel mKeyuardLongPressViewModel; @Mock private CoroutineDispatcher mMainDispatcher; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock private MotionEvent mDownMotionEvent; @@ -459,6 +464,9 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mMainHandler = new Handler(Looper.getMainLooper()); + when(mView.requireViewById(R.id.keyguard_long_press)) + .thenReturn(mock(LongPressHandlingView.class)); + mNotificationPanelViewController = new NotificationPanelViewController( mView, mMainHandler, @@ -528,7 +536,9 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mLockscreenToOccludedTransitionViewModel, mMainDispatcher, mKeyguardTransitionInteractor, - mDumpManager); + mDumpManager, + mKeyuardLongPressViewModel, + mKeyguardInteractor); mNotificationPanelViewController.initDependencies( mCentralSurfaces, null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt index bdafc7df33bc..c915502ad42e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt @@ -13,8 +13,11 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags +import com.android.systemui.fragments.FragmentHostManager +import com.android.systemui.fragments.FragmentService import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener +import com.android.systemui.plugins.qs.QS import com.android.systemui.recents.OverviewProxyService import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.util.concurrency.FakeExecutor @@ -29,6 +32,7 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.RETURNS_DEEP_STUBS +import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.doNothing import org.mockito.Mockito.eq @@ -69,6 +73,10 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager @Mock private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var fragmentService: FragmentService + @Mock + private lateinit var fragmentHostManager: FragmentHostManager @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener> @Captor @@ -77,6 +85,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>> @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet> + @Captor + lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener> private lateinit var controller: NotificationsQSContainerController private lateinit var navigationModeCallback: ModeChangedListener @@ -91,8 +101,10 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { mContext.ensureTestableResources() whenever(notificationsQSContainer.context).thenReturn(mContext) whenever(notificationsQSContainer.resources).thenReturn(mContext.resources) + whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager) fakeSystemClock = FakeSystemClock() delayableExecutor = FakeExecutor(fakeSystemClock) + controller = NotificationsQSContainerController( notificationsQSContainer, navigationModeController, @@ -100,6 +112,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { largeScreenShadeHeaderController, shadeExpansionStateManager, featureFlags, + fragmentService, delayableExecutor ) @@ -114,9 +127,10 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { doNothing().`when`(notificationsQSContainer) .setInsetsChangedListener(windowInsetsCallbackCaptor.capture()) doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture()) - + doNothing().`when`(notificationsQSContainer) + .addOnAttachStateChangeListener(attachStateListenerCaptor.capture()) controller.init() - controller.onViewAttached() + attachStateListenerCaptor.value.onViewAttachedToWindow(notificationsQSContainer) navigationModeCallback = navigationModeCaptor.value taskbarVisibilityCallback = taskbarVisibilityCaptor.value @@ -385,6 +399,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { largeScreenShadeHeaderController, shadeExpansionStateManager, featureFlags, + fragmentService, delayableExecutor ) controller.updateConstraints() @@ -426,6 +441,17 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { verify(largeScreenShadeHeaderController).startCustomizingAnimation(false, 100L) } + @Test + fun testTagListenerAdded() { + verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(notificationsQSContainer)) + } + + @Test + fun testTagListenerRemoved() { + attachStateListenerCaptor.value.onViewDetachedFromWindow(notificationsQSContainer) + verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(notificationsQSContainer)) + } + private fun disableSplitShade() { setSplitShadeEnabled(false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 4c768253202a..e5d5e3b8433a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -45,13 +45,16 @@ import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController +import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat 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 import org.mockito.Mockito.anyFloat +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -102,7 +105,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory - @Mock lateinit var keyguardBouncerContainer: ViewGroup @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardHostViewController: KeyguardHostViewController @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @@ -116,6 +118,12 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) whenever(view.bottom).thenReturn(VIEW_BOTTOM) + whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container)) + .thenReturn(mock(ViewGroup::class.java)) + whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java))) + .thenReturn(keyguardBouncerComponent) + whenever(keyguardBouncerComponent.keyguardHostViewController) + .thenReturn(keyguardHostViewController) underTest = NotificationShadeWindowViewController( lockscreenShadeTransitionController, FalsingCollectorFake(), @@ -275,6 +283,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Test fun testGetBouncerContainer() { + Mockito.clearInvocations(view) underTest.bouncerContainer verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index d43562443d6e..5cc3ef1def9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -29,9 +29,11 @@ import android.os.SystemClock; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; +import android.view.ViewGroup; import androidx.test.filters.SmallTest; +import com.android.keyguard.KeyguardHostViewController; import com.android.keyguard.LockIconViewController; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; @@ -94,6 +96,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel; @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; + @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent; + @Mock private KeyguardHostViewController mKeyguardHostViewController; @Mock private NotificationInsetsController mNotificationInsetsController; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -110,6 +114,12 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { when(mView.findViewById(R.id.notification_stack_scroller)) .thenReturn(mNotificationStackScrollLayout); + when(mView.findViewById(R.id.keyguard_bouncer_container)).thenReturn(mock(ViewGroup.class)); + when(mKeyguardBouncerComponentFactory.create(any(ViewGroup.class))).thenReturn( + mKeyguardBouncerComponent); + when(mKeyguardBouncerComponent.getKeyguardHostViewController()).thenReturn( + mKeyguardHostViewController); + when(mStatusBarStateController.isDozing()).thenReturn(false); mDependency.injectTestDependency(ShadeController.class, mShadeController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt index 5a62cc18cae3..ae1c8cbe2a65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt @@ -1,21 +1,17 @@ package com.android.systemui.shared.regionsampling -import android.graphics.Rect +import android.app.WallpaperManager import android.testing.AndroidTestingRunner import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.shared.navigationbar.RegionSamplingHelper import java.io.PrintWriter import java.util.concurrent.Executor -import org.junit.Assert import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @@ -28,9 +24,8 @@ class RegionSamplerTest : SysuiTestCase() { @Mock private lateinit var sampledView: View @Mock private lateinit var mainExecutor: Executor @Mock private lateinit var bgExecutor: Executor - @Mock private lateinit var regionSampler: RegionSamplingHelper @Mock private lateinit var pw: PrintWriter - @Mock private lateinit var callback: RegionSamplingHelper.SamplingCallback + @Mock private lateinit var wallpaperManager: WallpaperManager private lateinit var mRegionSampler: RegionSampler private var updateFun: UpdateColorCallback = {} @@ -38,65 +33,18 @@ class RegionSamplerTest : SysuiTestCase() { @Before fun setUp() { whenever(sampledView.isAttachedToWindow).thenReturn(true) - whenever(regionSampler.callback).thenReturn(this@RegionSamplerTest.callback) mRegionSampler = - object : RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun) { - override fun createRegionSamplingHelper( - sampledView: View, - callback: RegionSamplingHelper.SamplingCallback, - mainExecutor: Executor?, - bgExecutor: Executor? - ): RegionSamplingHelper { - return this@RegionSamplerTest.regionSampler - } - } + RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun, wallpaperManager) } @Test fun testStartRegionSampler() { mRegionSampler.startRegionSampler() - - verify(regionSampler).start(Rect(0, 0, 0, 0)) - } - - @Test - fun testStopRegionSampler() { - mRegionSampler.stopRegionSampler() - - verify(regionSampler).stop() } @Test fun testDump() { mRegionSampler.dump(pw) - - verify(regionSampler).dump(pw) - } - - @Test - fun testUpdateColorCallback() { - regionSampler.callback.onRegionDarknessChanged(false) - verify(regionSampler.callback).onRegionDarknessChanged(false) - clearInvocations(regionSampler.callback) - regionSampler.callback.onRegionDarknessChanged(true) - verify(regionSampler.callback).onRegionDarknessChanged(true) - } - - @Test - fun testFlagFalse() { - mRegionSampler = - object : RegionSampler(sampledView, mainExecutor, bgExecutor, false, updateFun) { - override fun createRegionSamplingHelper( - sampledView: View, - callback: RegionSamplingHelper.SamplingCallback, - mainExecutor: Executor?, - bgExecutor: Executor? - ): RegionSamplingHelper { - return this@RegionSamplerTest.regionSampler - } - } - - Assert.assertEquals(mRegionSampler.regionSampler, null) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt index d29e9a66a331..fa7d869b6b95 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt @@ -20,8 +20,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.smartspace.preconditions.LockscreenPrecondition import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.util.concurrency.Execution @@ -41,9 +39,6 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper class LockscreenPreconditionTest : SysuiTestCase() { @Mock - private lateinit var featureFlags: FeatureFlags - - @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock @@ -64,10 +59,7 @@ class LockscreenPreconditionTest : SysuiTestCase() { fun testFullyEnabled() { `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) - `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE)) - .thenReturn(true) - val precondition = LockscreenPrecondition(featureFlags, deviceProvisionedController, - execution) + val precondition = LockscreenPrecondition(deviceProvisionedController, execution) precondition.addListener(listener) `verify`(listener).onCriteriaChanged() @@ -81,10 +73,8 @@ class LockscreenPreconditionTest : SysuiTestCase() { fun testProvisioning() { `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(false) - `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE)) - .thenReturn(true) val precondition = - LockscreenPrecondition(featureFlags, deviceProvisionedController, execution) + LockscreenPrecondition(deviceProvisionedController, execution) precondition.addListener(listener) verify(listener).onCriteriaChanged() @@ -109,10 +99,8 @@ class LockscreenPreconditionTest : SysuiTestCase() { fun testUserSetup() { `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false) `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) - `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE)) - .thenReturn(true) val precondition = - LockscreenPrecondition(featureFlags, deviceProvisionedController, execution) + LockscreenPrecondition(deviceProvisionedController, execution) precondition.addListener(listener) verify(listener).onCriteriaChanged() @@ -129,4 +117,4 @@ class LockscreenPreconditionTest : SysuiTestCase() { verify(listener).onCriteriaChanged() assertThat(precondition.conditionsMet()).isTrue() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 8aaa18129834..e68d3b465a07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -45,6 +45,7 @@ import com.android.internal.statusbar.LetterboxDetails; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.view.AppearanceRegion; import com.android.systemui.SysuiTestCase; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.CommandQueue.Callbacks; import org.junit.After; @@ -62,12 +63,14 @@ public class CommandQueueTest extends SysuiTestCase { }; private CommandQueue mCommandQueue; + + private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); private Callbacks mCallbacks; private static final int SECONDARY_DISPLAY = 1; @Before public void setup() { - mCommandQueue = new CommandQueue(mContext); + mCommandQueue = new CommandQueue(mContext, mDisplayTracker); mCallbacks = mock(Callbacks.class); mCommandQueue.addCallback(mCallbacks); verify(mCallbacks).disable(anyInt(), eq(0), eq(0), eq(false)); @@ -415,7 +418,7 @@ public class CommandQueueTest extends SysuiTestCase { @Test public void testOnDisplayRemoved() { - mCommandQueue.onDisplayRemoved(SECONDARY_DISPLAY); + mDisplayTracker.triggerOnDisplayRemoved(SECONDARY_DISPLAY); waitForIdleSync(); verify(mCallbacks).onDisplayRemoved(eq(SECONDARY_DISPLAY)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 610bb13c6016..dffa566c97c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -58,6 +58,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.app.AlarmManager; import android.app.Instrumentation; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyResourcesManager; @@ -183,6 +184,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { private ScreenLifecycle mScreenLifecycle; @Mock private AuthController mAuthController; + @Mock + private AlarmManager mAlarmManager; @Captor private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener; @Captor @@ -277,7 +280,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mAuthController, mLockPatternUtils, mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager, mFaceHelpMessageDeferral, mock(KeyguardLogger.class), - mAlternateBouncerInteractor); + mAlternateBouncerInteractor, + mAlarmManager + ); mController.init(); mController.setIndicationArea(mIndicationArea); verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); 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 5124eb992dc0..e6f272b3ad70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -37,6 +37,7 @@ import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -152,4 +153,18 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { // and cause us to drop a frame during the LOCKSCREEN_TRANSITION_FROM_AOD CUJ. assertEquals(0.99f, controller.dozeAmount, 0.009f) } + + @Test + fun testSetDreamState_invokesCallback() { + val listener = mock(StatusBarStateController.StateListener::class.java) + controller.addCallback(listener) + + controller.setIsDreaming(true) + verify(listener).onDreamingChanged(true) + + Mockito.clearInvocations(listener) + + controller.setIsDreaming(false) + verify(listener).onDreamingChanged(false) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt index ea066471a802..a9c3d5d5eeb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt @@ -6,6 +6,7 @@ import android.view.InputEvent import android.view.MotionEvent import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.FakeDisplayTracker import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -17,6 +18,7 @@ import org.junit.runner.RunWith class GenericGestureDetectorTest : SysuiTestCase() { private lateinit var gestureDetector: TestGestureDetector + private val displayTracker = FakeDisplayTracker(mContext) @Before fun setUp() { @@ -101,12 +103,21 @@ class GenericGestureDetectorTest : SysuiTestCase() { gestureDetector.addOnGestureDetectedCallback("tag2"){} gestureDetector.removeOnGestureDetectedCallback("tag") - gestureDetector.onInputEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CORRECT_X, 0f, 0)) + gestureDetector.onInputEvent( + MotionEvent.obtain( + 0, + 0, + MotionEvent.ACTION_DOWN, + CORRECT_X, + 0f, + 0 + ) + ) assertThat(oldCallbackNotified).isFalse() } - inner class TestGestureDetector : GenericGestureDetector("fakeTag") { + inner class TestGestureDetector : GenericGestureDetector("fakeTag", displayTracker) { var isGestureListening = false override fun onInputEvent(ev: InputEvent) { 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 4bcb54ddbbc0..2423f13e6494 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 @@ -113,6 +113,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { private lateinit var handler: Handler @Mock + private lateinit var weatherPlugin: BcSmartspaceDataPlugin + + @Mock private lateinit var plugin: BcSmartspaceDataPlugin @Mock @@ -152,6 +155,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { KeyguardBypassController.OnBypassStateChangedListener private lateinit var deviceProvisionedListener: DeviceProvisionedListener + private lateinit var weatherSmartspaceView: SmartspaceView private lateinit var smartspaceView: SmartspaceView private val clock = FakeSystemClock() @@ -177,18 +181,22 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - `when`(featureFlags.isEnabled(Flags.SMARTSPACE)).thenReturn(true) + // Todo(b/261760571): flip the flag value here when feature is launched, and update relevant + // tests. + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING)) .thenReturn(fakePrivateLockscreenSettingUri) `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING)) .thenReturn(fakeNotifOnLockscreenSettingUri) `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession) + `when`(weatherPlugin.getView(any())).thenReturn( + createWeatherSmartspaceView(), createWeatherSmartspaceView()) `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView()) `when`(userTracker.userProfiles).thenReturn(userList) `when`(statusBarStateController.dozeAmount).thenReturn(0.5f) - `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true) - `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true) + `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) setActiveUser(userHandlePrimary) setAllowPrivateNotifications(userHandlePrimary, true) @@ -213,6 +221,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { executor, bgExecutor, handler, + Optional.of(weatherPlugin), Optional.of(plugin), Optional.of(configPlugin), ) @@ -222,21 +231,21 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test(expected = RuntimeException::class) - fun testThrowsIfFlagIsDisabled() { + fun testBuildAndConnectWeatherView_throwsIfDecouplingDisabled() { // GIVEN the feature flag is disabled - `when`(featureFlags.isEnabled(Flags.SMARTSPACE)).thenReturn(false) + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) // WHEN we try to build the view - controller.buildAndConnectView(fakeParent) + controller.buildAndConnectWeatherView(fakeParent) // THEN an exception is thrown } @Test - fun connectOnlyAfterDeviceIsProvisioned() { + fun testBuildAndConnectView_connectsOnlyAfterDeviceIsProvisioned() { // GIVEN an unprovisioned device and an attempt to connect - `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false) - `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false) + `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(false) + `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false) // WHEN a connection attempt is made and view is attached val view = controller.buildAndConnectView(fakeParent) @@ -246,8 +255,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { verify(smartspaceManager, never()).createSmartspaceSession(any()) // WHEN it does become provisioned - `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true) - `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true) + `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) deviceProvisionedListener.onUserSetupChanged() // THEN the session is created @@ -257,7 +266,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testListenersAreRegistered() { + fun testAddListener_registersListenersForPlugin() { // GIVEN a listener is added after a session is created connectSession() @@ -266,10 +275,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { // THEN the listener is registered to the underlying plugin verify(plugin).registerListener(controllerListener) + // The listener is registered only for the plugin, not the weather plugin. + verify(weatherPlugin, never()).registerListener(any()) } @Test - fun testEarlyRegisteredListenersAreAttachedAfterConnected() { + fun testAddListener_earlyRegisteredListenersAreAttachedAfterConnected() { // GIVEN a listener that is registered before the session is created controller.addListener(controllerListener) @@ -278,10 +289,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { // THEN the listener is subsequently registered verify(plugin).registerListener(controllerListener) + // The listener is registered only for the plugin, not the weather plugin. + verify(weatherPlugin, never()).registerListener(any()) } @Test - fun testEmptyListIsEmittedAndNotifierRemovedAfterDisconnect() { + fun testDisconnect_emitsEmptyListAndRemovesNotifier() { // GIVEN a registered listener on an active session connectSession() clearInvocations(plugin) @@ -293,10 +306,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { // THEN the listener receives an empty list of targets and unregisters the notifier verify(plugin).onTargetsAvailable(emptyList()) verify(plugin).registerSmartspaceEventNotifier(null) + verify(weatherPlugin).onTargetsAvailable(emptyList()) + verify(weatherPlugin).registerSmartspaceEventNotifier(null) } @Test - fun testUserChangeReloadsSmartspace() { + fun testUserChange_reloadsSmartspace() { // GIVEN a connected smartspace session connectSession() @@ -308,7 +323,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testSettingsChangeReloadsSmartspace() { + fun testSettingsChange_reloadsSmartspace() { // GIVEN a connected smartspace session connectSession() @@ -320,7 +335,21 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testThemeChangeUpdatesTextColor() { + fun testThemeChange_updatesTextColor() { + // GIVEN a connected smartspace session + connectSession() + + // WHEN the theme changes + configChangeListener.onThemeChanged() + + // We update the new text color to match the wallpaper color + verify(smartspaceView).setPrimaryTextColor(anyInt()) + } + + @Test + fun testThemeChange_ifDecouplingEnabled_updatesTextColor() { + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + // GIVEN a connected smartspace session connectSession() @@ -328,11 +357,26 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { configChangeListener.onThemeChanged() // We update the new text color to match the wallpaper color + verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) verify(smartspaceView).setPrimaryTextColor(anyInt()) } @Test - fun testDozeAmountChangeUpdatesView() { + fun testDozeAmountChange_updatesView() { + // GIVEN a connected smartspace session + connectSession() + + // WHEN the doze amount changes + statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f) + + // We pass that along to the view + verify(smartspaceView).setDozeAmount(0.7f) + } + + @Test + fun testDozeAmountChange_ifDecouplingEnabled_updatesViews() { + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + // GIVEN a connected smartspace session connectSession() @@ -340,11 +384,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f) // We pass that along to the view + verify(weatherSmartspaceView).setDozeAmount(0.7f) verify(smartspaceView).setDozeAmount(0.7f) } @Test - fun testKeyguardBypassEnabledUpdatesView() { + fun testKeyguardBypassEnabled_updatesView() { // GIVEN a connected smartspace session connectSession() `when`(keyguardBypassController.bypassEnabled).thenReturn(true) @@ -439,6 +484,27 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test + fun testSessionListener_ifDecouplingEnabled_weatherTargetIsFilteredOut() { + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + connectSession() + + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeTarget(2, userHandlePrimary), + makeTarget(3, userHandleManaged), + makeTarget(4, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER) + ) + + sessionListener.onTargetsAvailable(targets) + + // THEN all non-sensitive content is still shown + verify(plugin).onTargetsAvailable(eq(listOf(targets[0], targets[1], targets[2]))) + // No filtering is applied for the weather plugin + verify(weatherPlugin).onTargetsAvailable(eq(targets)) + } + + @Test fun testSettingsAreReloaded() { // GIVEN a connected session where the privacy settings later flip to false connectSession() @@ -529,6 +595,16 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test + fun testWeatherViewUsesSameSession() { + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + // GIVEN a connected session + connectSession() + + // No checks is needed here, since connectSession() already checks internally that + // createSmartspaceSession is invoked only once. + } + + @Test fun testViewGetInitializedWithBypassEnabledState() { // GIVEN keyguard bypass is enabled. `when`(keyguardBypassController.bypassEnabled).thenReturn(true) @@ -545,8 +621,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { fun testConnectAttemptBeforeInitializationShouldNotCreateSession() { // GIVEN an uninitalized smartspaceView // WHEN the device is provisioned - `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true) - `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true) + `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) deviceProvisionedListener.onDeviceProvisionedChanged() // THEN no calls to createSmartspaceSession should occur @@ -556,9 +632,23 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } private fun connectSession() { + if (controller.isDateWeatherDecoupled()) { + val weatherView = controller.buildAndConnectWeatherView(fakeParent) + weatherSmartspaceView = weatherView as SmartspaceView + fakeParent.addView(weatherView) + controller.stateChangeListener.onViewAttachedToWindow(weatherView) + + verify(weatherSmartspaceView).setUiSurface( + BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) + verify(weatherSmartspaceView).registerDataProvider(weatherPlugin) + + verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) + verify(weatherSmartspaceView).setDozeAmount(0.5f) + } + val view = controller.buildAndConnectView(fakeParent) smartspaceView = view as SmartspaceView - + fakeParent.addView(view) controller.stateChangeListener.onViewAttachedToWindow(view) verify(smartspaceView).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) @@ -568,6 +658,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { .addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor)) sessionListener = sessionListenerCaptor.value + verify(smartspaceManager).createSmartspaceSession(any()) + verify(userTracker).addCallback(capture(userTrackerCaptor), any()) userListener = userTrackerCaptor.value @@ -592,9 +684,11 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { verify(smartspaceView).setPrimaryTextColor(anyInt()) verify(smartspaceView).setDozeAmount(0.5f) - clearInvocations(view) - fakeParent.addView(view) + if (controller.isDateWeatherDecoupled()) { + clearInvocations(weatherSmartspaceView) + } + clearInvocations(smartspaceView) } private fun setActiveUser(userHandle: UserHandle) { @@ -640,6 +734,31 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { ).thenReturn(if (value) 1 else 0) } + // Separate function for the weather view, which doesn't implement all functions in interface. + private fun createWeatherSmartspaceView(): SmartspaceView { + return spy(object : View(context), SmartspaceView { + override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) { + } + + override fun setPrimaryTextColor(color: Int) { + } + + override fun setIsDreaming(isDreaming: Boolean) { + } + + override fun setUiSurface(uiSurface: String) { + } + + override fun setDozeAmount(amount: Float) { + } + + override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) { + } + + override fun setFalsingManager(falsingManager: FalsingManager?) { + } + }) + } private fun createSmartspaceView(): SmartspaceView { return spy(object : View(context), SmartspaceView { override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) { 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 4559a23a4f5c..9e23d548e5b5 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 @@ -82,19 +82,14 @@ import org.mockito.junit.MockitoRule; import java.util.Arrays; import java.util.List; +import java.util.function.Consumer; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper public class ExpandableNotificationRowTest extends SysuiTestCase { - private ExpandableNotificationRow mGroupRow; - private ExpandableNotificationRow mNotifRow; - private ExpandableNotificationRow mPublicRow; - private NotificationTestHelper mNotificationTestHelper; - boolean mHeadsUpAnimatingAway = false; - @Rule public MockitoRule mockito = MockitoJUnit.rule(); @Before @@ -105,112 +100,108 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { mDependency, TestableLooper.get(this)); mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL); + FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags(); fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true); mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags); - // create a standard private notification row - Notification normalNotif = mNotificationTestHelper.createNotification(); - normalNotif.publicVersion = null; - mNotifRow = mNotificationTestHelper.createRow(normalNotif); - // create a notification row whose public version is identical - Notification publicNotif = mNotificationTestHelper.createNotification(); - publicNotif.publicVersion = mNotificationTestHelper.createNotification(); - mPublicRow = mNotificationTestHelper.createRow(publicNotif); - // create a group row - mGroupRow = mNotificationTestHelper.createGroup(); - mGroupRow.setHeadsUpAnimatingAwayListener( - animatingAway -> mHeadsUpAnimatingAway = animatingAway); - } @Test - public void testUpdateBackgroundColors_isRecursive() { - mGroupRow.setTintColor(Color.RED); - mGroupRow.getChildNotificationAt(0).setTintColor(Color.GREEN); - mGroupRow.getChildNotificationAt(1).setTintColor(Color.BLUE); + public void testUpdateBackgroundColors_isRecursive() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + group.setTintColor(Color.RED); + group.getChildNotificationAt(0).setTintColor(Color.GREEN); + group.getChildNotificationAt(1).setTintColor(Color.BLUE); - assertThat(mGroupRow.getCurrentBackgroundTint()).isEqualTo(Color.RED); - assertThat(mGroupRow.getChildNotificationAt(0).getCurrentBackgroundTint()) + assertThat(group.getCurrentBackgroundTint()).isEqualTo(Color.RED); + assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint()) .isEqualTo(Color.GREEN); - assertThat(mGroupRow.getChildNotificationAt(1).getCurrentBackgroundTint()) + assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint()) .isEqualTo(Color.BLUE); - mGroupRow.updateBackgroundColors(); + group.updateBackgroundColors(); - int resetTint = mGroupRow.getCurrentBackgroundTint(); + int resetTint = group.getCurrentBackgroundTint(); assertThat(resetTint).isNotEqualTo(Color.RED); - assertThat(mGroupRow.getChildNotificationAt(0).getCurrentBackgroundTint()) + assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint()) .isEqualTo(resetTint); - assertThat(mGroupRow.getChildNotificationAt(1).getCurrentBackgroundTint()) + assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint()) .isEqualTo(resetTint); } @Test - public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws InterruptedException { + public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception { // GIVEN a sensitive notification row that's currently redacted - measureAndLayout(mNotifRow); - mNotifRow.setHideSensitiveForIntrinsicHeight(true); - mNotifRow.setSensitive(true, true); - assertThat(mNotifRow.getShowingLayout()).isSameInstanceAs(mNotifRow.getPublicLayout()); - assertThat(mNotifRow.getIntrinsicHeight()).isGreaterThan(0); + ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + measureAndLayout(row); + row.setHideSensitiveForIntrinsicHeight(true); + row.setSensitive(true, true); + assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPublicLayout()); + assertThat(row.getIntrinsicHeight()).isGreaterThan(0); // GIVEN that the row has a height change listener OnHeightChangedListener listener = mock(OnHeightChangedListener.class); - mNotifRow.setOnHeightChangedListener(listener); + row.setOnHeightChangedListener(listener); // WHEN the row is set to no longer be sensitive - mNotifRow.setSensitive(false, true); + row.setSensitive(false, true); // VERIFY that the height change listener is invoked - assertThat(mNotifRow.getShowingLayout()).isSameInstanceAs(mNotifRow.getPrivateLayout()); - assertThat(mNotifRow.getIntrinsicHeight()).isGreaterThan(0); - verify(listener).onHeightChanged(eq(mNotifRow), eq(false)); + assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout()); + assertThat(row.getIntrinsicHeight()).isGreaterThan(0); + verify(listener).onHeightChanged(eq(row), eq(false)); } @Test - public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() { + public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() throws Exception { // GIVEN a sensitive group row that's currently redacted - measureAndLayout(mGroupRow); - mGroupRow.setHideSensitiveForIntrinsicHeight(true); - mGroupRow.setSensitive(true, true); - assertThat(mGroupRow.getShowingLayout()).isSameInstanceAs(mGroupRow.getPublicLayout()); - assertThat(mGroupRow.getIntrinsicHeight()).isGreaterThan(0); + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + measureAndLayout(group); + group.setHideSensitiveForIntrinsicHeight(true); + group.setSensitive(true, true); + assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPublicLayout()); + assertThat(group.getIntrinsicHeight()).isGreaterThan(0); // GIVEN that the row has a height change listener OnHeightChangedListener listener = mock(OnHeightChangedListener.class); - mGroupRow.setOnHeightChangedListener(listener); + group.setOnHeightChangedListener(listener); // WHEN the row is set to no longer be sensitive - mGroupRow.setSensitive(false, true); + group.setSensitive(false, true); // VERIFY that the height change listener is invoked - assertThat(mGroupRow.getShowingLayout()).isSameInstanceAs(mGroupRow.getPrivateLayout()); - assertThat(mGroupRow.getIntrinsicHeight()).isGreaterThan(0); - verify(listener).onHeightChanged(eq(mGroupRow), eq(false)); + assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout()); + assertThat(group.getIntrinsicHeight()).isGreaterThan(0); + verify(listener).onHeightChanged(eq(group), eq(false)); } @Test - public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() { + public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() throws Exception { + // create a notification row whose public version is identical + Notification publicNotif = mNotificationTestHelper.createNotification(); + publicNotif.publicVersion = mNotificationTestHelper.createNotification(); + ExpandableNotificationRow publicRow = mNotificationTestHelper.createRow(publicNotif); + // GIVEN a sensitive public row that's currently redacted - measureAndLayout(mPublicRow); - mPublicRow.setHideSensitiveForIntrinsicHeight(true); - mPublicRow.setSensitive(true, true); - assertThat(mPublicRow.getShowingLayout()).isSameInstanceAs(mPublicRow.getPublicLayout()); - assertThat(mPublicRow.getIntrinsicHeight()).isGreaterThan(0); + measureAndLayout(publicRow); + publicRow.setHideSensitiveForIntrinsicHeight(true); + publicRow.setSensitive(true, true); + assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPublicLayout()); + assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0); // GIVEN that the row has a height change listener OnHeightChangedListener listener = mock(OnHeightChangedListener.class); - mPublicRow.setOnHeightChangedListener(listener); + publicRow.setOnHeightChangedListener(listener); // WHEN the row is set to no longer be sensitive - mPublicRow.setSensitive(false, true); + publicRow.setSensitive(false, true); // VERIFY that the height change listener is not invoked, because the height didn't change - assertThat(mPublicRow.getShowingLayout()).isSameInstanceAs(mPublicRow.getPrivateLayout()); - assertThat(mPublicRow.getIntrinsicHeight()).isGreaterThan(0); - assertThat(mPublicRow.getPrivateLayout().getMinHeight()) - .isEqualTo(mPublicRow.getPublicLayout().getMinHeight()); - verify(listener, never()).onHeightChanged(eq(mPublicRow), eq(false)); + assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPrivateLayout()); + assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0); + assertThat(publicRow.getPrivateLayout().getMinHeight()) + .isEqualTo(publicRow.getPublicLayout().getMinHeight()); + verify(listener, never()).onHeightChanged(eq(publicRow), eq(false)); } private void measureAndLayout(ExpandableNotificationRow row) { @@ -227,36 +218,43 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void testGroupSummaryNotShowingIconWhenPublic() { - mGroupRow.setSensitive(true, true); - mGroupRow.setHideSensitiveForIntrinsicHeight(true); - assertTrue(mGroupRow.isSummaryWithChildren()); - assertFalse(mGroupRow.isShowingIcon()); + public void testGroupSummaryNotShowingIconWhenPublic() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + + group.setSensitive(true, true); + group.setHideSensitiveForIntrinsicHeight(true); + assertTrue(group.isSummaryWithChildren()); + assertFalse(group.isShowingIcon()); } @Test - public void testNotificationHeaderVisibleWhenAnimating() { - mGroupRow.setSensitive(true, true); - mGroupRow.setHideSensitive(true, false, 0, 0); - mGroupRow.setHideSensitive(false, true, 0, 0); - assertEquals(View.VISIBLE, mGroupRow.getChildrenContainer().getVisibleWrapper() + public void testNotificationHeaderVisibleWhenAnimating() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + + group.setSensitive(true, true); + group.setHideSensitive(true, false, 0, 0); + group.setHideSensitive(false, true, 0, 0); + assertEquals(View.VISIBLE, group.getChildrenContainer().getVisibleWrapper() .getNotificationHeader().getVisibility()); } @Test - public void testUserLockedResetEvenWhenNoChildren() { - mGroupRow.setUserLocked(true); - mGroupRow.setUserLocked(false); + public void testUserLockedResetEvenWhenNoChildren() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + + group.setUserLocked(true); + group.setUserLocked(false); assertFalse("The childrencontainer should not be userlocked but is, the state " - + "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked()); + + "seems out of sync.", group.getChildrenContainer().isUserLocked()); } @Test - public void testReinflatedOnDensityChange() { + public void testReinflatedOnDensityChange() throws Exception { + ExpandableNotificationRow row = mNotificationTestHelper.createRow(); NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class); - mNotifRow.setChildrenContainer(mockContainer); + row.setChildrenContainer(mockContainer); - mNotifRow.onDensityOrFontScaleChanged(); + row.onDensityOrFontScaleChanged(); verify(mockContainer).reInflateViews(any(), any()); } @@ -299,64 +297,73 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testAboveShelfChangedListenerCalledWhenGoingBelow() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - row.setHeadsUp(true); AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class); row.setAboveShelfChangedListener(listener); + Mockito.reset(listener); + row.setHeadsUp(true); row.setAboveShelf(false); verify(listener).onAboveShelfStateChanged(false); } @Test public void testClickSound() throws Exception { - assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled()); + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + + assertTrue("Should play sounds by default.", group.isSoundEffectsEnabled()); StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController(); when(mock.isDozing()).thenReturn(true); - mGroupRow.setSecureStateProvider(()-> false); + group.setSecureStateProvider(()-> false); assertFalse("Shouldn't play sounds when dark and trusted.", - mGroupRow.isSoundEffectsEnabled()); - mGroupRow.setSecureStateProvider(()-> true); + group.isSoundEffectsEnabled()); + group.setSecureStateProvider(()-> true); assertTrue("Should always play sounds when not trusted.", - mGroupRow.isSoundEffectsEnabled()); + group.isSoundEffectsEnabled()); } @Test - public void testSetDismissed_longPressListenerRemoved() { + public void testSetDismissed_longPressListenerRemoved() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + ExpandableNotificationRow.LongPressListener listener = mock(ExpandableNotificationRow.LongPressListener.class); - mGroupRow.setLongPressListener(listener); - mGroupRow.doLongClickCallback(0,0); - verify(listener, times(1)).onLongPress(eq(mGroupRow), eq(0), eq(0), + group.setLongPressListener(listener); + group.doLongClickCallback(0, 0); + verify(listener, times(1)).onLongPress(eq(group), eq(0), eq(0), any(NotificationMenuRowPlugin.MenuItem.class)); reset(listener); - mGroupRow.dismiss(true); - mGroupRow.doLongClickCallback(0,0); - verify(listener, times(0)).onLongPress(eq(mGroupRow), eq(0), eq(0), + group.dismiss(true); + group.doLongClickCallback(0, 0); + verify(listener, times(0)).onLongPress(eq(group), eq(0), eq(0), any(NotificationMenuRowPlugin.MenuItem.class)); } @Test - public void testFeedback_noHeader() { + public void testFeedback_noHeader() throws Exception { + ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup(); + // public notification is custom layout - no header - mGroupRow.setSensitive(true, true); - mGroupRow.setOnFeedbackClickListener(null); - mGroupRow.setFeedbackIcon(null); + groupRow.setSensitive(true, true); + groupRow.setOnFeedbackClickListener(null); + groupRow.setFeedbackIcon(null); } @Test - public void testFeedback_header() { + public void testFeedback_header() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + NotificationContentView publicLayout = mock(NotificationContentView.class); - mGroupRow.setPublicLayout(publicLayout); + group.setPublicLayout(publicLayout); NotificationContentView privateLayout = mock(NotificationContentView.class); - mGroupRow.setPrivateLayout(privateLayout); + group.setPrivateLayout(privateLayout); NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class); when(mockContainer.getNotificationChildCount()).thenReturn(1); - mGroupRow.setChildrenContainer(mockContainer); + group.setChildrenContainer(mockContainer); final boolean show = true; final FeedbackIcon icon = new FeedbackIcon( R.drawable.ic_feedback_alerted, R.string.notification_feedback_indicator_alerted); - mGroupRow.setFeedbackIcon(icon); + group.setFeedbackIcon(icon); verify(mockContainer, times(1)).setFeedbackIcon(icon); verify(privateLayout, times(1)).setFeedbackIcon(icon); @@ -364,43 +371,60 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void testFeedbackOnClick() { + public void testFeedbackOnClick() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + ExpandableNotificationRow.CoordinateOnClickListener l = mock( ExpandableNotificationRow.CoordinateOnClickListener.class); View view = mock(View.class); - mGroupRow.setOnFeedbackClickListener(l); + group.setOnFeedbackClickListener(l); - mGroupRow.getFeedbackOnClickListener().onClick(view); + group.getFeedbackOnClickListener().onClick(view); verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any()); } @Test - public void testHeadsUpAnimatingAwayListener() { - mGroupRow.setHeadsUpAnimatingAway(true); - Assert.assertEquals(true, mHeadsUpAnimatingAway); - mGroupRow.setHeadsUpAnimatingAway(false); - Assert.assertEquals(false, mHeadsUpAnimatingAway); + public void testHeadsUpAnimatingAwayListener() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + Consumer<Boolean> headsUpListener = mock(Consumer.class); + AboveShelfChangedListener aboveShelfChangedListener = mock(AboveShelfChangedListener.class); + group.setHeadsUpAnimatingAwayListener(headsUpListener); + group.setAboveShelfChangedListener(aboveShelfChangedListener); + + group.setHeadsUpAnimatingAway(true); + verify(headsUpListener).accept(true); + verify(aboveShelfChangedListener).onAboveShelfStateChanged(true); + + group.setHeadsUpAnimatingAway(false); + verify(headsUpListener).accept(false); + verify(aboveShelfChangedListener).onAboveShelfStateChanged(false); } @Test - public void testIsBlockingHelperShowing_isCorrectlyUpdated() { - mGroupRow.setBlockingHelperShowing(true); - assertTrue(mGroupRow.isBlockingHelperShowing()); + public void testIsBlockingHelperShowing_isCorrectlyUpdated() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); - mGroupRow.setBlockingHelperShowing(false); - assertFalse(mGroupRow.isBlockingHelperShowing()); + group.setBlockingHelperShowing(true); + assertTrue(group.isBlockingHelperShowing()); + + group.setBlockingHelperShowing(false); + assertFalse(group.isBlockingHelperShowing()); } @Test - public void testGetNumUniqueChildren_defaultChannel() { - assertEquals(1, mGroupRow.getNumUniqueChannels()); + public void testGetNumUniqueChildren_defaultChannel() throws Exception { + ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup(); + + assertEquals(1, groupRow.getNumUniqueChannels()); } @Test - public void testGetNumUniqueChildren_multiChannel() { + public void testGetNumUniqueChildren_multiChannel() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + List<ExpandableNotificationRow> childRows = - mGroupRow.getChildrenContainer().getAttachedChildren(); + group.getChildrenContainer().getAttachedChildren(); // Give each child a unique channel id/name. int i = 0; for (ExpandableNotificationRow childRow : childRows) { @@ -412,25 +436,29 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { i++; } - assertEquals(3, mGroupRow.getNumUniqueChannels()); + assertEquals(3, group.getNumUniqueChannels()); } @Test public void testIconScrollXAfterTranslationAndReset() throws Exception { - mGroupRow.setDismissUsingRowTranslationX(false); - mGroupRow.setTranslation(50); - assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX()); + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + + group.setDismissUsingRowTranslationX(false); + group.setTranslation(50); + assertEquals(50, -group.getEntry().getIcons().getShelfIcon().getScrollX()); - mGroupRow.resetTranslation(); - assertEquals(0, mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX()); + group.resetTranslation(); + assertEquals(0, group.getEntry().getIcons().getShelfIcon().getScrollX()); } @Test - public void testIsExpanded_userExpanded() { - mGroupRow.setExpandable(true); - Assert.assertFalse(mGroupRow.isExpanded()); - mGroupRow.setUserExpanded(true); - Assert.assertTrue(mGroupRow.isExpanded()); + public void testIsExpanded_userExpanded() throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + + group.setExpandable(true); + Assert.assertFalse(group.isExpanded()); + group.setUserExpanded(true); + Assert.assertTrue(group.isExpanded()); } @Test @@ -549,72 +577,80 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void applyRoundnessAndInv_should_be_immediately_applied_on_childrenContainer_legacy() { - mGroupRow.useRoundnessSourceTypes(false); - Assert.assertEquals(0f, mGroupRow.getBottomRoundness(), 0.001f); - Assert.assertEquals(0f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f); + public void applyRoundnessAndInv_should_be_immediately_applied_on_childrenContainer_legacy() + throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + group.useRoundnessSourceTypes(false); + Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f); + Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f); - mGroupRow.requestBottomRoundness(1f, SourceType.from(""), false); + group.requestBottomRoundness(1f, SourceType.from(""), false); - Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f); - Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f); + Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f); + Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f); } @Test - public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer() { - mGroupRow.useRoundnessSourceTypes(true); - Assert.assertEquals(0f, mGroupRow.getBottomRoundness(), 0.001f); - Assert.assertEquals(0f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f); + public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer() + throws Exception { + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + group.useRoundnessSourceTypes(true); + Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f); + Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f); - mGroupRow.requestBottomRoundness(1f, SourceType.from(""), false); + group.requestBottomRoundness(1f, SourceType.from(""), false); - Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f); - Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f); + Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f); + Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f); } @Test public void testSetContentAnimationRunning_Run() throws Exception { // Create views for the notification row. + ExpandableNotificationRow row = mNotificationTestHelper.createRow(); NotificationContentView publicLayout = mock(NotificationContentView.class); - mNotifRow.setPublicLayout(publicLayout); + row.setPublicLayout(publicLayout); NotificationContentView privateLayout = mock(NotificationContentView.class); - mNotifRow.setPrivateLayout(privateLayout); + row.setPrivateLayout(privateLayout); - mNotifRow.setAnimationRunning(true); + row.setAnimationRunning(true); verify(publicLayout, times(1)).setContentAnimationRunning(true); verify(privateLayout, times(1)).setContentAnimationRunning(true); } @Test - public void testSetContentAnimationRunning_Stop() { + public void testSetContentAnimationRunning_Stop() throws Exception { // Create views for the notification row. + ExpandableNotificationRow row = mNotificationTestHelper.createRow(); NotificationContentView publicLayout = mock(NotificationContentView.class); - mNotifRow.setPublicLayout(publicLayout); + row.setPublicLayout(publicLayout); NotificationContentView privateLayout = mock(NotificationContentView.class); - mNotifRow.setPrivateLayout(privateLayout); + row.setPrivateLayout(privateLayout); - mNotifRow.setAnimationRunning(false); + row.setAnimationRunning(false); verify(publicLayout, times(1)).setContentAnimationRunning(false); verify(privateLayout, times(1)).setContentAnimationRunning(false); } @Test - public void testSetContentAnimationRunningInGroupChild_Run() { - // Creates parent views on mGroupRow. + public void testSetContentAnimationRunningInGroupChild_Run() throws Exception { + // Creates parent views on groupRow. + ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup(); NotificationContentView publicParentLayout = mock(NotificationContentView.class); - mGroupRow.setPublicLayout(publicParentLayout); + groupRow.setPublicLayout(publicParentLayout); NotificationContentView privateParentLayout = mock(NotificationContentView.class); - mGroupRow.setPrivateLayout(privateParentLayout); + groupRow.setPrivateLayout(privateParentLayout); - // Create child views on mNotifRow. + // Create child views on row. + ExpandableNotificationRow row = mNotificationTestHelper.createRow(); NotificationContentView publicChildLayout = mock(NotificationContentView.class); - mNotifRow.setPublicLayout(publicChildLayout); + row.setPublicLayout(publicChildLayout); NotificationContentView privateChildLayout = mock(NotificationContentView.class); - mNotifRow.setPrivateLayout(privateChildLayout); - when(mNotifRow.isGroupExpanded()).thenReturn(true); - setMockChildrenContainer(mGroupRow, mNotifRow); + row.setPrivateLayout(privateChildLayout); + when(row.isGroupExpanded()).thenReturn(true); + setMockChildrenContainer(groupRow, row); - mGroupRow.setAnimationRunning(true); + groupRow.setAnimationRunning(true); verify(publicParentLayout, times(1)).setContentAnimationRunning(true); verify(privateParentLayout, times(1)).setContentAnimationRunning(true); // The child layouts should be started too. @@ -624,23 +660,25 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test - public void testSetIconAnimationRunningGroup_Run() { + public void testSetIconAnimationRunningGroup_Run() throws Exception { // Create views for a group row. + ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); + ExpandableNotificationRow child = mNotificationTestHelper.createRow(); NotificationContentView publicParentLayout = mock(NotificationContentView.class); - mGroupRow.setPublicLayout(publicParentLayout); + group.setPublicLayout(publicParentLayout); NotificationContentView privateParentLayout = mock(NotificationContentView.class); - mGroupRow.setPrivateLayout(privateParentLayout); - when(mGroupRow.isGroupExpanded()).thenReturn(true); + group.setPrivateLayout(privateParentLayout); + when(group.isGroupExpanded()).thenReturn(true); - // Sets up mNotifRow as a child ExpandableNotificationRow. + // Add the child to the group. NotificationContentView publicChildLayout = mock(NotificationContentView.class); - mNotifRow.setPublicLayout(publicChildLayout); + child.setPublicLayout(publicChildLayout); NotificationContentView privateChildLayout = mock(NotificationContentView.class); - mNotifRow.setPrivateLayout(privateChildLayout); - when(mNotifRow.isGroupExpanded()).thenReturn(true); + child.setPrivateLayout(privateChildLayout); + when(child.isGroupExpanded()).thenReturn(true); NotificationChildrenContainer mockContainer = - setMockChildrenContainer(mGroupRow, mNotifRow); + setMockChildrenContainer(group, child); // Mock the children view wrappers, and give them each an icon. NotificationViewWrapper mockViewWrapper = mock(NotificationViewWrapper.class); @@ -663,7 +701,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { AnimatedVectorDrawable lowPriVectorDrawable = mock(AnimatedVectorDrawable.class); setDrawableIconsInImageView(mockLowPriorityIcon, lowPriDrawable, lowPriVectorDrawable); - mGroupRow.setAnimationRunning(true); + group.setAnimationRunning(true); verify(drawable, times(1)).start(); verify(vectorDrawable, times(1)).start(); verify(lowPriDrawable, times(1)).start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index b6a1bb3b27d6..d7ac6b41ee78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -66,9 +66,9 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.SysuiTestCase; -import com.android.systemui.dump.DumpManager; import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -133,18 +133,13 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private ShadeController mShadeController; @Mock private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; @Mock private AssistantFeedbackController mAssistantFeedbackController; + @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager; + @Mock private StatusBarStateController mStatusBarStateController; @Before public void setUp() { mTestableLooper = TestableLooper.get(this); allowTestableLooperAsMainThread(); - mDependency.injectTestDependency(DeviceProvisionedController.class, - mDeviceProvisionedController); - mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); - mDependency.injectTestDependency( - OnUserInteractionCallback.class, - mOnUserInteractionCallback); - mDependency.injectMockDependency(NotificationLockscreenUserManager.class); mHandler = Handler.createAsync(mTestableLooper.getLooper()); mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); @@ -155,7 +150,11 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager, mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController, Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback, - mShadeController); + mShadeController, + mNotificationLockscreenUserManager, + mStatusBarStateController, + mDeviceProvisionedController, + mMetricsLogger); mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer, mOnSettingsClickListener); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); @@ -372,7 +371,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false), eq(true), /* wasShownHighPriority */ - eq(mAssistantFeedbackController)); + eq(mAssistantFeedbackController), + any(MetricsLogger.class)); } @Test @@ -406,7 +406,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(true), eq(false), eq(false), /* wasShownHighPriority */ - eq(mAssistantFeedbackController)); + eq(mAssistantFeedbackController), + any(MetricsLogger.class)); } @Test @@ -438,7 +439,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false), eq(false), /* wasShownHighPriority */ - eq(mAssistantFeedbackController)); + eq(mAssistantFeedbackController), + any(MetricsLogger.class)); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index 80a81a592049..8dd0488c924e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -130,7 +130,6 @@ public class NotificationInfoTest extends SysuiTestCase { mContext.addMockSystemService(TelecomManager.class, mTelecomManager); mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); - mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); // Inflate the layout final LayoutInflater layoutInflater = LayoutInflater.from(mContext); mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info, @@ -194,7 +193,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name); assertTrue(textView.getText().toString().contains("App Name")); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); @@ -220,7 +220,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final ImageView iconView = mNotificationInfo.findViewById(R.id.pkg_icon); assertEquals(iconDrawable, iconView.getDrawable()); } @@ -242,7 +243,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); } @@ -273,7 +275,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); assertTrue(nameView.getText().toString().contains("Proxied")); @@ -296,7 +299,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(GONE, groupNameView.getVisibility()); } @@ -324,7 +328,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.VISIBLE, groupNameView.getVisibility()); assertEquals("Test Group Name", groupNameView.getText()); @@ -347,7 +352,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(TEST_CHANNEL_NAME, textView.getText()); } @@ -369,7 +375,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, textView.getVisibility()); } @@ -395,7 +402,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -417,7 +425,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, true, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -443,7 +452,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final View settingsButton = mNotificationInfo.findViewById(R.id.info); settingsButton.performClick(); @@ -468,7 +478,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -493,7 +504,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -515,7 +527,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.bindNotification( mMockPackageManager, mMockINotificationManager, @@ -531,7 +544,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertEquals(View.VISIBLE, settingsButton.getVisibility()); } @@ -556,7 +570,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, true, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.info).performClick(); // Verify that listener was triggered. @@ -582,7 +597,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView channelNameView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, channelNameView.getVisibility()); @@ -606,7 +622,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertEquals(GONE, mNotificationInfo.findViewById( R.id.interruptiveness_settings).getVisibility()); assertEquals(VISIBLE, mNotificationInfo.findViewById( @@ -630,7 +647,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, true, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_text); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_desc), @@ -673,7 +691,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_call_text); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_call_desc), @@ -716,7 +735,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertEquals(GONE, mNotificationInfo.findViewById(R.id.non_configurable_call_text).getVisibility()); assertEquals(VISIBLE, @@ -743,7 +763,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic).getVisibility()); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility()); } @@ -765,7 +786,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic).getVisibility()); assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility()); } @@ -789,7 +811,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertTrue(mNotificationInfo.findViewById(R.id.automatic).isSelected()); } @@ -810,7 +833,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertTrue(mNotificationInfo.findViewById(R.id.alert).isSelected()); } @@ -831,7 +855,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertTrue(mNotificationInfo.findViewById(R.id.silence).isSelected()); } @@ -852,7 +877,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mTestableLooper.processAllMessages(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), any()); @@ -875,7 +901,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertEquals(1, mUiEventLogger.numLogs()); assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(), mUiEventLogger.eventId(0)); @@ -899,7 +926,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.alert).performClick(); mTestableLooper.processAllMessages(); @@ -926,7 +954,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.silence).performClick(); mTestableLooper.processAllMessages(); @@ -953,7 +982,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.automatic).performClick(); mTestableLooper.processAllMessages(); @@ -981,7 +1011,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -1008,7 +1039,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -1043,7 +1075,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.handleCloseControls(true, false); @@ -1071,7 +1104,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1112,7 +1146,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1149,7 +1184,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.automatic).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1181,7 +1217,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1217,7 +1254,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertEquals(mContext.getString(R.string.inline_done_button), ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); @@ -1255,7 +1293,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.handleCloseControls(false, false); @@ -1286,7 +1325,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertEquals(mContext.getString(R.string.inline_done_button), ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); @@ -1324,7 +1364,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, true, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1353,7 +1394,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertEquals(mContext.getString(R.string.inline_done_button), ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); @@ -1384,7 +1426,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1419,7 +1462,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1452,7 +1496,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1485,7 +1530,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); mNotificationInfo.findViewById(R.id.alert).performClick(); @@ -1511,7 +1557,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, false, false, - mAssistantFeedbackController); + mAssistantFeedbackController, + mMetricsLogger); assertFalse(mNotificationInfo.willBeRemoved()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index e4fc4d5d54ba..aca9c563de04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -40,7 +40,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.graphics.drawable.Icon; -import android.os.Handler; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.testing.TestableLooper; @@ -49,7 +48,6 @@ import android.view.LayoutInflater; import android.widget.RemoteViews; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; @@ -57,7 +55,6 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.controls.util.MediaFeatureFlag; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -68,7 +65,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.icon.IconBuilder; @@ -77,11 +73,8 @@ import com.android.systemui.statusbar.notification.people.PeopleNotificationIden import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; -import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; -import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; import com.android.systemui.statusbar.policy.InflatedSmartReplyState; import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder; import com.android.systemui.statusbar.policy.SmartReplyConstants; @@ -121,12 +114,12 @@ public class NotificationTestHelper { private final GroupMembershipManager mGroupMembershipManager; private final GroupExpansionManager mGroupExpansionManager; private ExpandableNotificationRow mRow; - private HeadsUpManagerPhone mHeadsUpManager; + private final HeadsUpManagerPhone mHeadsUpManager; private final NotifBindPipeline mBindPipeline; private final NotifCollectionListener mBindPipelineEntryListener; private final RowContentBindStage mBindStage; private final IconManager mIconManager; - private StatusBarStateController mStatusBarStateController; + private final StatusBarStateController mStatusBarStateController; private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; public final OnUserInteractionCallback mOnUserInteractionCallback; public final Runnable mFutureDismissalRunnable; @@ -146,21 +139,7 @@ public class NotificationTestHelper { mStatusBarStateController = mock(StatusBarStateController.class); mGroupMembershipManager = mock(GroupMembershipManager.class); mGroupExpansionManager = mock(GroupExpansionManager.class); - mHeadsUpManager = new HeadsUpManagerPhone( - mContext, - mock(HeadsUpManagerLogger.class), - mStatusBarStateController, - mock(KeyguardBypassController.class), - mock(GroupMembershipManager.class), - mock(VisualStabilityProvider.class), - mock(ConfigurationControllerImpl.class), - new Handler(mTestLooper.getLooper()), - mock(AccessibilityManagerWrapper.class), - mock(UiEventLogger.class), - mock(ShadeExpansionStateManager.class) - ); - mHeadsUpManager.mHandler.removeCallbacksAndMessages(null); - mHeadsUpManager.mHandler = new Handler(mTestLooper.getLooper()); + mHeadsUpManager = mock(HeadsUpManagerPhone.class); mIconManager = new IconManager( mock(CommonNotifCollection.class), mock(LauncherApps.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 091bb5455d93..8cfcc075bfaf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -510,6 +510,7 @@ public class AutoTileManagerTest extends SysuiTestCase { @Test public void managedProfileAdded_tileAdded() { when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false); + when(mAutoAddTracker.getRestoredTilePosition(eq("work"))).thenReturn(2); mAutoTileManager = createAutoTileManager(mContext); Mockito.doAnswer((Answer<Object>) invocation -> { mManagedProfileCallback = invocation.getArgument(0); @@ -520,7 +521,7 @@ public class AutoTileManagerTest extends SysuiTestCase { mManagedProfileCallback.onManagedProfileChanged(); - verify(mQsTileHost, times(1)).addTile(eq("work")); + verify(mQsTileHost, times(1)).addTile(eq("work"), eq(2)); verify(mAutoAddTracker, times(1)).setTileAdded(eq("work")); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index b1363a047307..06053987202c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -1010,6 +1010,18 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + public void testSetDozingNotUnlocking_transitionToAOD_cancelKeyguardFadingAway() { + setDozing(true); + when(mKeyguardStateController.isShowing()).thenReturn(false); + when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true); + + mCentralSurfaces.updateScrimController(); + + verify(mScrimController, times(2)).transitionTo(eq(ScrimState.AOD)); + verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway(); + } + + @Test public void testShowKeyguardImplementation_setsState() { when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java deleted file mode 100644 index 2f495355df40..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.phone; - -import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; -import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyFloat; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.eq; -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; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.hardware.biometrics.BiometricSourceType; -import android.os.Handler; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import androidx.test.filters.SmallTest; - -import com.android.keyguard.KeyguardHostViewController; -import com.android.keyguard.KeyguardSecurityModel; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.ViewMediatorCallback; -import com.android.keyguard.dagger.KeyguardBouncerComponent; -import com.android.systemui.DejankUtils; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.classifier.FalsingCollector; -import com.android.systemui.keyguard.DismissCallbackRegistry; -import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; -import com.android.systemui.plugins.ActivityStarter.OnDismissAction; -import com.android.systemui.statusbar.policy.KeyguardStateController; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class KeyguardBouncerTest extends SysuiTestCase { - - @Mock - private FalsingCollector mFalsingCollector; - @Mock - private ViewMediatorCallback mViewMediatorCallback; - @Mock - private DismissCallbackRegistry mDismissCallbackRegistry; - @Mock - private KeyguardHostViewController mKeyguardHostViewController; - @Mock - private PrimaryBouncerExpansionCallback mExpansionCallback; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - private KeyguardStateController mKeyguardStateController; - @Mock - private KeyguardBypassController mKeyguardBypassController; - @Mock - private Handler mHandler; - @Mock - private KeyguardSecurityModel mKeyguardSecurityModel; - @Mock - private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; - @Mock - private KeyguardBouncerComponent mKeyguardBouncerComponent; - private ViewGroup mContainer; - @Rule - public MockitoRule mRule = MockitoJUnit.rule(); - private Integer mRootVisibility = View.INVISIBLE; - private KeyguardBouncer mBouncer; - - @Before - public void setup() { - allowTestableLooperAsMainThread(); - when(mKeyguardSecurityModel.getSecurityMode(anyInt())) - .thenReturn(KeyguardSecurityModel.SecurityMode.None); - DejankUtils.setImmediate(true); - - mContainer = spy(new FrameLayout(getContext())); - when(mKeyguardBouncerComponentFactory.create(mContainer)).thenReturn( - mKeyguardBouncerComponent); - when(mKeyguardBouncerComponent.getKeyguardHostViewController()) - .thenReturn(mKeyguardHostViewController); - - mBouncer = new KeyguardBouncer.Factory(getContext(), mViewMediatorCallback, - mDismissCallbackRegistry, mFalsingCollector, - mKeyguardStateController, mKeyguardUpdateMonitor, - mKeyguardBypassController, mHandler, mKeyguardSecurityModel, - mKeyguardBouncerComponentFactory) - .create(mContainer, mExpansionCallback); - } - - @Test - public void testInflateView_doesntCrash() { - mBouncer.inflateView(); - } - - @Test - public void testShow_notifiesFalsingManager() { - mBouncer.show(true); - verify(mFalsingCollector).onBouncerShown(); - - mBouncer.show(true, false); - verifyNoMoreInteractions(mFalsingCollector); - } - - /** - * Regression test: Invisible bouncer when occluded. - */ - @Test - public void testShow_bouncerIsVisible() { - // Expand notification panel as if we were in the keyguard. - mBouncer.ensureView(); - mBouncer.setExpansion(1); - - reset(mKeyguardHostViewController); - - mBouncer.show(true); - verify(mKeyguardHostViewController).setExpansion(0); - } - - @Test - public void testShow_notifiesVisibility() { - mBouncer.show(true); - verify(mKeyguardStateController).notifyBouncerShowing(eq(true)); - verify(mExpansionCallback).onStartingToShow(); - - // Not called again when visible - reset(mViewMediatorCallback); - mBouncer.show(true); - verifyNoMoreInteractions(mViewMediatorCallback); - } - - @Test - public void testShow_triesToDismissKeyguard() { - mBouncer.show(true); - verify(mKeyguardHostViewController).dismiss(anyInt()); - } - - @Test - public void testShow_resetsSecuritySelection() { - mBouncer.show(false); - verify(mKeyguardHostViewController, never()).showPrimarySecurityScreen(); - - mBouncer.hide(false); - mBouncer.show(true); - verify(mKeyguardHostViewController).showPrimarySecurityScreen(); - } - - @Test - public void testShow_animatesKeyguardView() { - mBouncer.show(true); - verify(mKeyguardHostViewController).appear(anyInt()); - } - - @Test - public void testShow_showsErrorMessage() { - final String errorMessage = "an error message"; - when(mViewMediatorCallback.consumeCustomMessage()).thenReturn(errorMessage); - mBouncer.show(true); - verify(mKeyguardHostViewController).showErrorMessage(eq(errorMessage)); - } - - @Test - public void testSetExpansion_notifiesFalsingManager() { - mBouncer.ensureView(); - mBouncer.setExpansion(0.5f); - - mBouncer.setExpansion(EXPANSION_HIDDEN); - verify(mFalsingCollector).onBouncerHidden(); - verify(mExpansionCallback).onFullyHidden(); - - mBouncer.setExpansion(EXPANSION_VISIBLE); - verify(mFalsingCollector).onBouncerShown(); - verify(mExpansionCallback).onFullyShown(); - - verify(mExpansionCallback, never()).onStartingToHide(); - verify(mKeyguardHostViewController, never()).onStartingToHide(); - mBouncer.setExpansion(0.9f); - verify(mExpansionCallback).onStartingToHide(); - verify(mKeyguardHostViewController).onStartingToHide(); - } - - @Test - public void testSetExpansion_notifiesKeyguardView() { - mBouncer.ensureView(); - mBouncer.setExpansion(0.1f); - - mBouncer.setExpansion(0); - verify(mKeyguardHostViewController).onResume(); - verify(mContainer).announceForAccessibility(any()); - } - - @Test - public void show_notifiesKeyguardViewController() { - mBouncer.ensureView(); - - mBouncer.show(/* resetSecuritySelection= */ false); - - verify(mKeyguardHostViewController).onBouncerVisibilityChanged(View.VISIBLE); - } - - @Test - public void testHide_notifiesFalsingManager() { - mBouncer.hide(false); - verify(mFalsingCollector).onBouncerHidden(); - } - - @Test - public void testHide_notifiesVisibility() { - mBouncer.hide(false); - verify(mKeyguardStateController).notifyBouncerShowing(eq(false)); - } - - @Test - public void testHide_notifiesDismissCallbackIfVisible() { - mBouncer.hide(false); - verifyZeroInteractions(mDismissCallbackRegistry); - mBouncer.show(false); - mBouncer.hide(false); - verify(mDismissCallbackRegistry).notifyDismissCancelled(); - } - - @Test - public void testHide_notShowingAnymore() { - mBouncer.ensureView(); - mBouncer.show(false /* resetSecuritySelection */); - mBouncer.hide(false /* destroyViews */); - Assert.assertFalse("Not showing", mBouncer.isShowing()); - } - - @Test - public void testShowPromptReason_propagates() { - mBouncer.ensureView(); - mBouncer.showPromptReason(1); - verify(mKeyguardHostViewController).showPromptReason(eq(1)); - } - - @Test - public void testShowMessage_propagates() { - final String message = "a message"; - mBouncer.ensureView(); - mBouncer.showMessage(message, ColorStateList.valueOf(Color.GREEN)); - verify(mKeyguardHostViewController).showMessage( - eq(message), eq(ColorStateList.valueOf(Color.GREEN))); - } - - @Test - public void testShowOnDismissAction_showsBouncer() { - final OnDismissAction dismissAction = () -> false; - final Runnable cancelAction = () -> {}; - mBouncer.showWithDismissAction(dismissAction, cancelAction); - verify(mKeyguardHostViewController).setOnDismissAction(dismissAction, cancelAction); - Assert.assertTrue("Should be showing", mBouncer.isShowing()); - } - - @Test - public void testStartPreHideAnimation_notifiesView() { - final boolean[] ran = {false}; - final Runnable r = () -> ran[0] = true; - mBouncer.startPreHideAnimation(r); - Assert.assertTrue("Callback should have been invoked", ran[0]); - - ran[0] = false; - mBouncer.ensureView(); - mBouncer.startPreHideAnimation(r); - verify(mKeyguardHostViewController).startDisappearAnimation(r); - Assert.assertFalse("Callback should have been deferred", ran[0]); - } - - @Test - public void testIsShowing_animated() { - Assert.assertFalse("Show wasn't invoked yet", mBouncer.isShowing()); - mBouncer.show(true /* reset */); - Assert.assertTrue("Should be showing", mBouncer.isShowing()); - } - - @Test - public void testIsShowing_forSwipeUp() { - mBouncer.setExpansion(1f); - mBouncer.show(true /* reset */, false /* animated */); - Assert.assertFalse("Should only be showing after collapsing notification panel", - mBouncer.isShowing()); - mBouncer.setExpansion(0f); - Assert.assertTrue("Should be showing", mBouncer.isShowing()); - } - - @Test - public void testSetExpansion() { - mBouncer.ensureView(); - mBouncer.setExpansion(0.5f); - verify(mKeyguardHostViewController).setExpansion(0.5f); - } - - @Test - public void testIsFullscreenBouncer_asksKeyguardView() { - mBouncer.ensureView(); - mBouncer.isFullscreenBouncer(); - verify(mKeyguardHostViewController).getCurrentSecurityMode(); - } - - @Test - public void testIsHiding_preHideOrHide() { - Assert.assertFalse("Should not be hiding on initial state", mBouncer.isAnimatingAway()); - mBouncer.startPreHideAnimation(null /* runnable */); - Assert.assertTrue("Should be hiding during pre-hide", mBouncer.isAnimatingAway()); - mBouncer.hide(false /* destroyView */); - Assert.assertFalse("Should be hidden after hide()", mBouncer.isAnimatingAway()); - } - - @Test - public void testIsHiding_skipsTranslation() { - mBouncer.show(false /* reset */); - reset(mKeyguardHostViewController); - mBouncer.startPreHideAnimation(null /* runnable */); - mBouncer.setExpansion(0.5f); - verify(mKeyguardHostViewController, never()).setExpansion(anyFloat()); - } - - @Test - public void testIsSecure() { - mBouncer.ensureView(); - for (KeyguardSecurityModel.SecurityMode mode : KeyguardSecurityModel.SecurityMode.values()){ - reset(mKeyguardSecurityModel); - when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(mode); - Assert.assertEquals("Security doesn't match for mode: " + mode, - mBouncer.isSecure(), mode != KeyguardSecurityModel.SecurityMode.None); - } - } - - @Test - public void testIsShowingScrimmed_true() { - doAnswer(invocation -> { - assertThat(mBouncer.isScrimmed()).isTrue(); - return null; - }).when(mExpansionCallback).onFullyShown(); - mBouncer.show(false /* resetSecuritySelection */, true /* animate */); - assertThat(mBouncer.isScrimmed()).isTrue(); - mBouncer.hide(false /* destroyView */); - assertThat(mBouncer.isScrimmed()).isFalse(); - } - - @Test - public void testIsShowingScrimmed_false() { - doAnswer(invocation -> { - assertThat(mBouncer.isScrimmed()).isFalse(); - return null; - }).when(mExpansionCallback).onFullyShown(); - mBouncer.show(false /* resetSecuritySelection */, false /* animate */); - assertThat(mBouncer.isScrimmed()).isFalse(); - } - - @Test - public void testWillDismissWithAction() { - mBouncer.ensureView(); - Assert.assertFalse("Action not set yet", mBouncer.willDismissWithAction()); - when(mKeyguardHostViewController.hasDismissActions()).thenReturn(true); - Assert.assertTrue("Action should exist", mBouncer.willDismissWithAction()); - } - - @Test - public void testShow_delaysIfFaceAuthIsRunning() { - when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)) - .thenReturn(true); - when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true); - mBouncer.show(true /* reset */); - - ArgumentCaptor<Runnable> showRunnable = ArgumentCaptor.forClass(Runnable.class); - verify(mHandler).postDelayed(showRunnable.capture(), - eq(KeyguardBouncer.BOUNCER_FACE_DELAY)); - - mBouncer.hide(false /* destroyView */); - verify(mHandler).removeCallbacks(eq(showRunnable.getValue())); - } - - @Test - public void testShow_doesNotDelaysIfFaceAuthIsNotAllowed() { - when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true); - when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)) - .thenReturn(false); - mBouncer.show(true /* reset */); - - verify(mHandler, never()).postDelayed(any(), anyLong()); - } - - @Test - public void testShow_delaysIfFaceAuthIsRunning_unlessBypassEnabled() { - when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true); - when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true); - mBouncer.show(true /* reset */); - - verify(mHandler, never()).postDelayed(any(), anyLong()); - } - - @Test - public void testShow_delaysIfFaceAuthIsRunning_unlessFingerprintEnrolled() { - when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true); - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)) - .thenReturn(true); - mBouncer.show(true /* reset */); - - verify(mHandler, never()).postDelayed(any(), anyLong()); - } - - @Test - public void testRegisterUpdateMonitorCallback() { - verify(mKeyguardUpdateMonitor).registerCallback(any()); - } - - @Test - public void testInTransit_whenTranslation() { - mBouncer.show(true); - mBouncer.setExpansion(EXPANSION_HIDDEN); - assertThat(mBouncer.inTransit()).isFalse(); - mBouncer.setExpansion(0.5f); - assertThat(mBouncer.inTransit()).isTrue(); - mBouncer.setExpansion(EXPANSION_VISIBLE); - assertThat(mBouncer.inTransit()).isFalse(); - } - - @Test - public void testUpdateResources_delegatesToRootView() { - mBouncer.ensureView(); - mBouncer.updateResources(); - - // This is mocked, so won't pick up on the call to updateResources via - // mKeyguardViewController.init(), only updateResources above. - verify(mKeyguardHostViewController).updateResources(); - } - - @Test - public void testUpdateKeyguardPosition_delegatesToRootView() { - mBouncer.ensureView(); - mBouncer.updateKeyguardPosition(1.0f); - - verify(mKeyguardHostViewController).updateKeyguardPosition(1.0f); - } - - @Test - public void testExpansion_notifiesCallback() { - mBouncer.ensureView(); - mBouncer.setExpansion(0.5f); - - final PrimaryBouncerExpansionCallback callback = - mock(PrimaryBouncerExpansionCallback.class); - mBouncer.addBouncerExpansionCallback(callback); - - mBouncer.setExpansion(EXPANSION_HIDDEN); - verify(callback).onFullyHidden(); - verify(callback).onExpansionChanged(EXPANSION_HIDDEN); - - Mockito.clearInvocations(callback); - mBouncer.setExpansion(EXPANSION_VISIBLE); - verify(callback).onFullyShown(); - verify(callback).onExpansionChanged(EXPANSION_VISIBLE); - - Mockito.clearInvocations(callback); - float bouncerHideAmount = 0.9f; - // Ensure the callback only triggers once despite multiple calls to setExpansion - // with the same value. - mBouncer.setExpansion(bouncerHideAmount); - mBouncer.setExpansion(bouncerHideAmount); - verify(callback, times(1)).onStartingToHide(); - verify(callback, times(1)).onExpansionChanged(bouncerHideAmount); - - Mockito.clearInvocations(callback); - mBouncer.removeBouncerExpansionCallback(callback); - bouncerHideAmount = 0.5f; - mBouncer.setExpansion(bouncerHideAmount); - verify(callback, never()).onExpansionChanged(bouncerHideAmount); - } - - @Test - public void testOnResumeCalledForFullscreenBouncerOnSecondShow() { - // GIVEN a security mode which requires fullscreen bouncer - when(mKeyguardSecurityModel.getSecurityMode(anyInt())) - .thenReturn(KeyguardSecurityModel.SecurityMode.SimPin); - mBouncer.show(true); - - // WHEN a second call to show occurs, the bouncer will already by visible - reset(mKeyguardHostViewController); - mBouncer.show(true); - - // THEN ensure the ViewController is told to resume - verify(mKeyguardHostViewController).onResume(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index cc4abfcaa42f..529519a6246e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -38,6 +38,7 @@ import com.android.internal.view.AppearanceRegion; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.policy.BatteryController; import org.junit.Before; @@ -67,7 +68,8 @@ public class LightBarControllerTest extends SysuiTestCase { mStatusBarIconController, mock(BatteryController.class), mock(NavigationModeController.class), - mock(DumpManager.class)); + mock(DumpManager.class), + new FakeDisplayTracker(mContext)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java index 189aa0f99b0f..0a68406882d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java @@ -28,6 +28,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.LightBarTransitionsController.DarkIntensityApplier; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -56,7 +57,8 @@ public class LightBarTransitionsControllerTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); mLightBarTransitionsController = new LightBarTransitionsController(mContext, mApplier, - new CommandQueue(mContext), mKeyguardStateController, mStatusBarStateController); + new CommandQueue(mContext, new FakeDisplayTracker(mContext)), + mKeyguardStateController, mStatusBarStateController); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 1ba0a36fb05d..d8446f4721b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.flags.Flags.MODERN_BOUNCER; import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; @@ -109,7 +108,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private View mNotificationContainer; @Mock private KeyguardBypassController mBypassController; - @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory; @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory; @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController; @Mock private KeyguardMessageArea mKeyguardMessageArea; @@ -153,8 +151,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM)) .thenReturn(true); - when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(true); - mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager( getContext(), @@ -169,7 +165,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mock(NotificationShadeWindowController.class), mKeyguardStateController, mock(NotificationMediaManager.class), - mKeyguardBouncerFactory, mKeyguardMessageAreaFactory, Optional.of(mSysUiUnfoldComponent), () -> mShadeController, @@ -660,7 +655,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mock(NotificationShadeWindowController.class), mKeyguardStateController, mock(NotificationMediaManager.class), - mKeyguardBouncerFactory, mKeyguardMessageAreaFactory, Optional.of(mSysUiUnfoldComponent), () -> mShadeController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java deleted file mode 100644 index 55ab6812da91..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java +++ /dev/null @@ -1,581 +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.statusbar.phone; - -import static com.android.systemui.flags.Flags.MODERN_BOUNCER; -import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; -import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; - -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyFloat; -import static org.mockito.ArgumentMatchers.anyInt; -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.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewRootImpl; -import android.window.OnBackInvokedCallback; -import android.window.OnBackInvokedDispatcher; -import android.window.WindowOnBackInvokedDispatcher; - -import androidx.test.filters.SmallTest; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardMessageArea; -import com.android.keyguard.KeyguardMessageAreaController; -import com.android.keyguard.KeyguardSecurityModel; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.dock.DockManager; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.keyguard.data.BouncerView; -import com.android.systemui.keyguard.data.BouncerViewDelegate; -import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; -import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor; -import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; -import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; -import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.plugins.ActivityStarter.OnDismissAction; -import com.android.systemui.shade.NotificationPanelViewController; -import com.android.systemui.shade.ShadeController; -import com.android.systemui.shade.ShadeExpansionChangeEvent; -import com.android.systemui.shade.ShadeExpansionStateManager; -import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.unfold.SysUIUnfoldComponent; - -import com.google.common.truth.Truth; - -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.Mockito; -import org.mockito.MockitoAnnotations; - -import java.util.Optional; - -/** - * StatusBarKeyguardViewManager Test with deprecated KeyguardBouncer.java. - * TODO: Delete when deleting {@link KeyguardBouncer} - */ -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { - private static final ShadeExpansionChangeEvent EXPANSION_EVENT = - expansionEvent(/* fraction= */ 0.5f, /* expanded= */ false, /* tracking= */ true); - - @Mock private ViewMediatorCallback mViewMediatorCallback; - @Mock private LockPatternUtils mLockPatternUtils; - @Mock private CentralSurfaces mCentralSurfaces; - @Mock private ViewGroup mContainer; - @Mock private NotificationPanelViewController mNotificationPanelView; - @Mock private BiometricUnlockController mBiometricUnlockController; - @Mock private SysuiStatusBarStateController mStatusBarStateController; - @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock private View mNotificationContainer; - @Mock private KeyguardBypassController mBypassController; - @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory; - @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory; - @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController; - @Mock private KeyguardBouncer mPrimaryBouncer; - @Mock private KeyguardMessageArea mKeyguardMessageArea; - @Mock private ShadeController mShadeController; - @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent; - @Mock private DreamOverlayStateController mDreamOverlayStateController; - @Mock private LatencyTracker mLatencyTracker; - @Mock private FeatureFlags mFeatureFlags; - @Mock private KeyguardSecurityModel mKeyguardSecurityModel; - @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; - @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; - @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; - @Mock private BouncerView mBouncerView; - @Mock private BouncerViewDelegate mBouncerViewDelegate; - - private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback - mBouncerExpansionCallback; - private FakeKeyguardStateController mKeyguardStateController = - spy(new FakeKeyguardStateController()); - - @Mock private ViewRootImpl mViewRootImpl; - @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; - @Captor - private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback; - - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mKeyguardBouncerFactory.create( - any(ViewGroup.class), - any(PrimaryBouncerExpansionCallback.class))) - .thenReturn(mPrimaryBouncer); - when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer); - when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea); - when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class))) - .thenReturn(mKeyguardMessageAreaController); - when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate); - - mStatusBarKeyguardViewManager = - new StatusBarKeyguardViewManager( - getContext(), - mViewMediatorCallback, - mLockPatternUtils, - mStatusBarStateController, - mock(ConfigurationController.class), - mKeyguardUpdateMonitor, - mDreamOverlayStateController, - mock(NavigationModeController.class), - mock(DockManager.class), - mock(NotificationShadeWindowController.class), - mKeyguardStateController, - mock(NotificationMediaManager.class), - mKeyguardBouncerFactory, - mKeyguardMessageAreaFactory, - Optional.of(mSysUiUnfoldComponent), - () -> mShadeController, - mLatencyTracker, - mKeyguardSecurityModel, - mFeatureFlags, - mPrimaryBouncerCallbackInteractor, - mPrimaryBouncerInteractor, - mBouncerView, - mAlternateBouncerInteractor) { - @Override - public ViewRootImpl getViewRootImpl() { - return mViewRootImpl; - } - }; - when(mViewRootImpl.getOnBackInvokedDispatcher()) - .thenReturn(mOnBackInvokedDispatcher); - mStatusBarKeyguardViewManager.registerCentralSurfaces( - mCentralSurfaces, - mNotificationPanelView, - new ShadeExpansionStateManager(), - mBiometricUnlockController, - mNotificationContainer, - mBypassController); - mStatusBarKeyguardViewManager.show(null); - ArgumentCaptor<PrimaryBouncerExpansionCallback> callbackArgumentCaptor = - ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class); - verify(mKeyguardBouncerFactory).create(any(ViewGroup.class), - callbackArgumentCaptor.capture()); - mBouncerExpansionCallback = callbackArgumentCaptor.getValue(); - } - - @Test - public void dismissWithAction_AfterKeyguardGoneSetToFalse() { - OnDismissAction action = () -> false; - Runnable cancelAction = () -> {}; - mStatusBarKeyguardViewManager.dismissWithAction( - action, cancelAction, false /* afterKeyguardGone */); - verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction)); - } - - @Test - public void showBouncer_onlyWhenShowing() { - mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */); - mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */); - verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean()); - verify(mPrimaryBouncer, never()).show(anyBoolean()); - } - - @Test - public void showBouncer_notWhenBouncerAlreadyShowing() { - mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */); - when(mPrimaryBouncer.isSecure()).thenReturn(true); - mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */); - verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean()); - verify(mPrimaryBouncer, never()).show(anyBoolean()); - } - - @Test - public void showBouncer_showsTheBouncer() { - mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */); - verify(mPrimaryBouncer).show(anyBoolean(), eq(true)); - } - - @Test - public void onPanelExpansionChanged_neverShowsDuringHintAnimation() { - when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); - } - - @Test - public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() { - mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f)); - - when(mPrimaryBouncer.isShowing()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged( - expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true)); - verify(mPrimaryBouncer).setExpansion(eq(0.6f)); - } - - @Test - public void onPanelExpansionChanged_duplicateEventsAreIgnored() { - when(mPrimaryBouncer.isShowing()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncer).setExpansion(eq(0.5f)); - - reset(mPrimaryBouncer); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f)); - } - - @Test - public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() { - mStatusBarKeyguardViewManager.hide(0, 0); - when(mPrimaryBouncer.inTransit()).thenReturn(true); - - mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncer).setExpansion(eq(EXPANSION_HIDDEN)); - } - - @Test - public void onPanelExpansionChanged_showsBouncerWhenSwiping() { - mKeyguardStateController.setCanDismissLockScreen(false); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncer).show(eq(false), eq(false)); - - // But not when it's already visible - reset(mPrimaryBouncer); - when(mPrimaryBouncer.isShowing()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncer, never()).show(eq(false), eq(false)); - - // Or animating away - reset(mPrimaryBouncer); - when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncer, never()).show(eq(false), eq(false)); - } - - @Test - public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() { - when(mBiometricUnlockController.getMode()) - .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK); - mStatusBarKeyguardViewManager.onPanelExpansionChanged( - expansionEvent( - /* fraction= */ EXPANSION_VISIBLE, - /* expanded= */ true, - /* tracking= */ false)); - verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); - } - - @Test - public void onPanelExpansionChanged_neverTranslatesBouncerWhenDismissBouncer() { - // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing - // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel - // which would mistakenly cause the bouncer to show briefly before its visibility - // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the - // bouncer if the bouncer is dismissing as a result of a biometric unlock. - when(mBiometricUnlockController.getMode()) - .thenReturn(BiometricUnlockController.MODE_DISMISS_BOUNCER); - mStatusBarKeyguardViewManager.onPanelExpansionChanged( - expansionEvent( - /* fraction= */ EXPANSION_VISIBLE, - /* expanded= */ true, - /* tracking= */ false)); - verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); - } - - @Test - public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() { - when(mKeyguardStateController.isOccluded()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged( - expansionEvent( - /* fraction= */ EXPANSION_VISIBLE, - /* expanded= */ true, - /* tracking= */ false)); - verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); - } - - @Test - public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() { - // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing - // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel - // which would mistakenly cause the bouncer to show briefly before its visibility - // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the - // bouncer if the bouncer is dismissing as a result of a biometric unlock. - when(mBiometricUnlockController.getMode()) - .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER); - mStatusBarKeyguardViewManager.onPanelExpansionChanged( - expansionEvent( - /* fraction= */ EXPANSION_VISIBLE, - /* expanded= */ true, - /* tracking= */ false)); - verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); - } - - @Test - public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() { - when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); - mStatusBarKeyguardViewManager.onPanelExpansionChanged( - expansionEvent( - /* fraction= */ EXPANSION_VISIBLE, - /* expanded= */ true, - /* tracking= */ false)); - verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); - } - - @Test - public void setOccluded_animatesPanelExpansion_onlyIfBouncerHidden() { - mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */); - verify(mCentralSurfaces).animateKeyguardUnoccluding(); - - when(mPrimaryBouncer.isShowing()).thenReturn(true); - clearInvocations(mCentralSurfaces); - mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */); - verify(mCentralSurfaces, never()).animateKeyguardUnoccluding(); - } - - @Test - public void setOccluded_onKeyguardOccludedChangedCalled() { - clearInvocations(mKeyguardStateController); - clearInvocations(mKeyguardUpdateMonitor); - - mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */); - verify(mKeyguardStateController).notifyKeyguardState(true, false); - - clearInvocations(mKeyguardUpdateMonitor); - clearInvocations(mKeyguardStateController); - - mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */); - verify(mKeyguardStateController).notifyKeyguardState(true, true); - - clearInvocations(mKeyguardUpdateMonitor); - clearInvocations(mKeyguardStateController); - - mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */); - verify(mKeyguardStateController).notifyKeyguardState(true, false); - } - - @Test - public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() { - mStatusBarKeyguardViewManager.show(null); - - mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */); - verify(mKeyguardStateController).notifyKeyguardState(true, true); - } - - @Test - public void setOccluded_isLaunchingActivityOverLockscreen_onKeyguardOccludedChangedCalled() { - when(mCentralSurfaces.isLaunchingActivityOverLockscreen()).thenReturn(true); - mStatusBarKeyguardViewManager.show(null); - - mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */); - verify(mKeyguardStateController).notifyKeyguardState(true, true); - } - - @Test - public void testHiding_cancelsGoneRunnable() { - OnDismissAction action = mock(OnDismissAction.class); - Runnable cancelAction = mock(Runnable.class); - mStatusBarKeyguardViewManager.dismissWithAction( - action, cancelAction, true /* afterKeyguardGone */); - - when(mPrimaryBouncer.isShowing()).thenReturn(false); - mStatusBarKeyguardViewManager.hideBouncer(true); - mStatusBarKeyguardViewManager.hide(0, 30); - verify(action, never()).onDismiss(); - verify(cancelAction).run(); - } - - @Test - public void testHidingBouncer_cancelsGoneRunnable() { - OnDismissAction action = mock(OnDismissAction.class); - Runnable cancelAction = mock(Runnable.class); - mStatusBarKeyguardViewManager.dismissWithAction( - action, cancelAction, true /* afterKeyguardGone */); - - when(mPrimaryBouncer.isShowing()).thenReturn(false); - mStatusBarKeyguardViewManager.hideBouncer(true); - - verify(action, never()).onDismiss(); - verify(cancelAction).run(); - } - - @Test - public void testHiding_doesntCancelWhenShowing() { - OnDismissAction action = mock(OnDismissAction.class); - Runnable cancelAction = mock(Runnable.class); - mStatusBarKeyguardViewManager.dismissWithAction( - action, cancelAction, true /* afterKeyguardGone */); - - mStatusBarKeyguardViewManager.hide(0, 30); - verify(action).onDismiss(); - verify(cancelAction, never()).run(); - } - - @Test - public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() { - when(mPrimaryBouncer.isShowing()).thenReturn(false); - when(mPrimaryBouncer.inTransit()).thenReturn(true); - - assertTrue( - "Is or will be showing should be true when bouncer is in transit", - mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()); - } - - @Test - public void testUpdateResources_delegatesToBouncer() { - mStatusBarKeyguardViewManager.updateResources(); - - verify(mPrimaryBouncer).updateResources(); - } - - @Test - public void updateKeyguardPosition_delegatesToBouncer() { - mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f); - - verify(mPrimaryBouncer).updateKeyguardPosition(1.0f); - } - - @Test - public void testIsBouncerInTransit() { - when(mPrimaryBouncer.inTransit()).thenReturn(true); - Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue(); - when(mPrimaryBouncer.inTransit()).thenReturn(false); - Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse(); - mPrimaryBouncer = null; - Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse(); - } - - private static ShadeExpansionChangeEvent expansionEvent( - float fraction, boolean expanded, boolean tracking) { - return new ShadeExpansionChangeEvent( - fraction, expanded, tracking, /* dragDownPxAmount= */ 0f); - } - - @Test - public void testPredictiveBackCallback_registration() { - /* verify that a predictive back callback is registered when the bouncer becomes visible */ - mBouncerExpansionCallback.onVisibilityChanged(true); - verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY), - mOnBackInvokedCallback.capture()); - - /* verify that the same callback is unregistered when the bouncer becomes invisible */ - mBouncerExpansionCallback.onVisibilityChanged(false); - verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback( - eq(mOnBackInvokedCallback.getValue())); - } - - @Test - public void testPredictiveBackCallback_invocationHidesBouncer() { - mBouncerExpansionCallback.onVisibilityChanged(true); - /* capture the predictive back callback during registration */ - verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY), - mOnBackInvokedCallback.capture()); - - when(mPrimaryBouncer.isShowing()).thenReturn(true); - when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true); - /* invoke the back callback directly */ - mOnBackInvokedCallback.getValue().onBackInvoked(); - - /* verify that the bouncer will be hidden as a result of the invocation */ - verify(mCentralSurfaces).setBouncerShowing(eq(false)); - } - - @Test - public void testReportBouncerOnDreamWhenVisible() { - mBouncerExpansionCallback.onVisibilityChanged(true); - verify(mCentralSurfaces).setBouncerShowingOverDream(false); - Mockito.clearInvocations(mCentralSurfaces); - when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true); - mBouncerExpansionCallback.onVisibilityChanged(true); - verify(mCentralSurfaces).setBouncerShowingOverDream(true); - } - - @Test - public void testReportBouncerOnDreamWhenNotVisible() { - mBouncerExpansionCallback.onVisibilityChanged(false); - verify(mCentralSurfaces).setBouncerShowingOverDream(false); - Mockito.clearInvocations(mCentralSurfaces); - when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true); - mBouncerExpansionCallback.onVisibilityChanged(false); - verify(mCentralSurfaces).setBouncerShowingOverDream(false); - } - - @Test - public void flag_off_DoesNotCallBouncerInteractor() { - when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false); - mStatusBarKeyguardViewManager.hideBouncer(false); - verify(mPrimaryBouncerInteractor, never()).hide(); - } - - @Test - public void hideAlternateBouncer_beforeCentralSurfacesRegistered() { - mStatusBarKeyguardViewManager = - new StatusBarKeyguardViewManager( - getContext(), - mViewMediatorCallback, - mLockPatternUtils, - mStatusBarStateController, - mock(ConfigurationController.class), - mKeyguardUpdateMonitor, - mDreamOverlayStateController, - mock(NavigationModeController.class), - mock(DockManager.class), - mock(NotificationShadeWindowController.class), - mKeyguardStateController, - mock(NotificationMediaManager.class), - mKeyguardBouncerFactory, - mKeyguardMessageAreaFactory, - Optional.of(mSysUiUnfoldComponent), - () -> mShadeController, - mLatencyTracker, - mKeyguardSecurityModel, - mFeatureFlags, - mPrimaryBouncerCallbackInteractor, - mPrimaryBouncerInteractor, - mBouncerView, - mAlternateBouncerInteractor) { - @Override - public ViewRootImpl getViewRootImpl() { - return mViewRootImpl; - } - }; - - // the following call before registering centralSurfaces should NOT throw a NPE: - mStatusBarKeyguardViewManager.hideAlternateBouncer(true); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index eef43bde1a23..8841521c19f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -40,6 +40,7 @@ import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.ShadeController; @@ -92,7 +93,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mMetricsLogger = new FakeMetricsLogger(); LockscreenGestureLogger lockscreenGestureLogger = new LockscreenGestureLogger( mMetricsLogger); - mCommandQueue = new CommandQueue(mContext); + mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext)); mDependency.injectTestDependency(StatusBarStateController.class, mock(SysuiStatusBarStateController.class)); mDependency.injectTestDependency(ShadeController.class, mShadeController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java index 613238f7752e..929099a8f1f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java @@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; @@ -78,7 +79,8 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext, mock(GroupExpansionManager.class), mNotificationLockscreenUserManager, mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager, - mActivityStarter, mShadeController, new CommandQueue(mContext), + mActivityStarter, mShadeController, + new CommandQueue(mContext, new FakeDisplayTracker(mContext)), mock(ActionClickLogger.class), mFakeExecutor)); mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver(); } 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 438271c489e6..f230b876f05e 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 @@ -58,7 +58,6 @@ import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; import com.android.systemui.statusbar.OperatorNameViewController; -import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; @@ -92,7 +91,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private StatusBarLocationPublisher mLocationPublisher; // Set in instantiate() private StatusBarIconController mStatusBarIconController; - private NetworkController mNetworkController; private KeyguardStateController mKeyguardStateController; private final CommandQueue mCommandQueue = mock(CommandQueue.class); @@ -424,7 +422,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mAnimationScheduler = mock(SystemStatusAnimationScheduler.class); mLocationPublisher = mock(StatusBarLocationPublisher.class); mStatusBarIconController = mock(StatusBarIconController.class); - mNetworkController = mock(NetworkController.class); mStatusBarStateController = mock(StatusBarStateController.class); mKeyguardStateController = mock(KeyguardStateController.class); mOperatorNameViewController = mock(OperatorNameViewController.class); @@ -448,7 +445,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mStatusBarHideIconsForBouncerManager, mKeyguardStateController, mNotificationPanelViewController, - mNetworkController, mStatusBarStateController, mCommandQueue, mCarrierConfigTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index ae390a0e2959..db8172a5cacf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -22,6 +22,7 @@ import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.os.ParcelUuid import android.provider.Settings import android.telephony.CarrierConfigManager import android.telephony.SubscriptionInfo @@ -50,6 +51,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat +import java.util.UUID import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -104,6 +106,17 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mock<TableLogBuffer>() } + // For convenience, set up the subscription info callbacks + whenever(subscriptionManager.getActiveSubscriptionInfo(anyInt())).thenAnswer { invocation -> + when (invocation.getArgument(0) as Int) { + 1 -> SUB_1 + 2 -> SUB_2 + 3 -> SUB_3 + 4 -> SUB_4 + else -> null + } + } + wifiRepository = FakeWifiRepository() connectionFactory = @@ -686,6 +699,38 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun `active data change - in same group - emits unit`() = + runBlocking(IMMEDIATE) { + var latest: Unit? = null + val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this) + + getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() + .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED) + getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() + .onActiveDataSubscriptionIdChanged(SUB_4_ID_GROUPED) + + assertThat(latest).isEqualTo(Unit) + + job.cancel() + } + + @Test + fun `active data change - not in same group - does not emit`() = + runBlocking(IMMEDIATE) { + var latest: Unit? = null + val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this) + + getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() + .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED) + getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>() + .onActiveDataSubscriptionIdChanged(SUB_1_ID) + + assertThat(latest).isEqualTo(null) + + job.cancel() + } + private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected) @@ -719,19 +764,50 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { companion object { private val IMMEDIATE = Dispatchers.Main.immediate + + // Subscription 1 private const val SUB_1_ID = 1 private val SUB_1 = - mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) } + mock<SubscriptionInfo>().also { + whenever(it.subscriptionId).thenReturn(SUB_1_ID) + whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID())) + } private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID) + // Subscription 2 private const val SUB_2_ID = 2 private val SUB_2 = - mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } + mock<SubscriptionInfo>().also { + whenever(it.subscriptionId).thenReturn(SUB_2_ID) + whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID())) + } private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID) + // Subs 3 and 4 are considered to be in the same group ------------------------------------ + private val GROUP_ID_3_4 = ParcelUuid(UUID.randomUUID()) + + // Subscription 3 + private const val SUB_3_ID_GROUPED = 3 + private val SUB_3 = + mock<SubscriptionInfo>().also { + whenever(it.subscriptionId).thenReturn(SUB_3_ID_GROUPED) + whenever(it.groupUuid).thenReturn(GROUP_ID_3_4) + } + + // Subscription 4 + private const val SUB_4_ID_GROUPED = 4 + private val SUB_4 = + mock<SubscriptionInfo>().also { + whenever(it.subscriptionId).thenReturn(SUB_4_ID_GROUPED) + whenever(it.groupUuid).thenReturn(GROUP_ID_3_4) + } + + // Subs 3 and 4 are considered to be in the same group ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + private const val NET_ID = 123 private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) } + // Carrier merged subscription private const val SUB_CM_ID = 5 private val SUB_CM = mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt index b32058fca109..3dccbbf26575 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt @@ -45,7 +45,7 @@ class ConnectivityPipelineLoggerTest : SysuiTestCase() { @Test fun testLogNetworkCapsChange_bufferHasInfo() { - logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS) + logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS, isDefaultNetworkCallback = true) val stringWriter = StringWriter() buffer.dump(PrintWriter(stringWriter), tailLength = 0) @@ -54,6 +54,7 @@ class ConnectivityPipelineLoggerTest : SysuiTestCase() { val expectedNetId = NET_1_ID.toString() val expectedCaps = NET_1_CAPS.toString() + assertThat(actualString).contains("true") assertThat(actualString).contains(expectedNetId) assertThat(actualString).contains(expectedCaps) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt index 3fe69837a761..e4c8fd0cd8a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.view +import android.graphics.Rect import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest @@ -118,6 +119,22 @@ class ModernStatusBarViewTest : SysuiTestCase() { assertThat(view.isIconVisible).isEqualTo(false) } + @Test + fun getDrawingRect_takesTranslationIntoAccount() { + val view = createAndInitView() + + view.translationX = 50f + view.translationY = 60f + + val drawingRect = Rect() + view.getDrawingRect(drawingRect) + + assertThat(drawingRect.left).isEqualTo(view.left + 50) + assertThat(drawingRect.right).isEqualTo(view.right + 50) + assertThat(drawingRect.top).isEqualTo(view.top + 60) + assertThat(drawingRect.bottom).isEqualTo(view.bottom + 60) + } + private fun createAndInitView(): ModernStatusBarView { val view = ModernStatusBarView(context, null) binding = TestBinding() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index 87ce8faff5a5..7099f1f0af2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -21,7 +21,10 @@ import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_VPN import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.TransportInfo +import android.net.VpnTransportInfo import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.net.wifi.WifiManager @@ -243,6 +246,54 @@ class WifiRepositoryImplTest : SysuiTestCase() { job.cancel() } + /** Regression test for b/266628069. */ + @Test + fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() = + runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val transportInfo = VpnTransportInfo( + /* type= */ 0, + /* sessionId= */ "sessionId", + ) + val networkCapabilities = mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) + whenever(it.transportInfo).thenReturn(transportInfo) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities) + + assertThat(underTest.isWifiDefault.value).isFalse() + + job.cancel() + } + + /** Regression test for b/266628069. */ + @Test + fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() = + runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val transportInfo = VpnTransportInfo( + /* type= */ 0, + /* sessionId= */ "sessionId", + ) + val networkCapabilities = mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) + whenever(it.transportInfo).thenReturn(transportInfo) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities) + + assertThat(underTest.isWifiDefault.value).isTrue() + + job.cancel() + } + @Test fun isWifiDefault_cellularVcnNetwork_isTrue() = runBlocking(IMMEDIATE) { val job = underTest.isWifiDefault.launchIn(this) @@ -260,6 +311,24 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() = + runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(underTest.isWifiDefault.value).isTrue() + + job.cancel() + } + + @Test fun isWifiDefault_cellularNotVcnNetwork_isFalse() = runBlocking(IMMEDIATE) { val job = underTest.isWifiDefault.launchIn(this) @@ -467,6 +536,28 @@ class WifiRepositoryImplTest : SysuiTestCase() { job.cancel() } + /** Regression test for b/266628069. */ + @Test + fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val transportInfo = VpnTransportInfo( + /* type= */ 0, + /* sessionId= */ "sessionId", + ) + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo)) + + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + @Test fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) { var latest: WifiNetworkModel? = null @@ -535,6 +626,31 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun wifiNetwork_cellularAndWifiTransports_usesCellular() = + 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.hasTransport(TRANSPORT_WIFI)).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_newPrimaryWifiNetwork_flowHasNewNetwork() = runBlocking(IMMEDIATE) { var latest: WifiNetworkModel? = null val job = underTest @@ -870,12 +986,12 @@ class WifiRepositoryImplTest : SysuiTestCase() { } private fun createWifiNetworkCapabilities( - wifiInfo: WifiInfo, + transportInfo: TransportInfo, isValidated: Boolean = true, ): NetworkCapabilities { return mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.transportInfo).thenReturn(wifiInfo) + whenever(it.transportInfo).thenReturn(transportInfo) whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(isValidated) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt index 1cccd65c8dbc..cc6be5e09fbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt @@ -92,6 +92,20 @@ class StylusUsiPowerStartableTest : SysuiTestCase() { } @Test + fun onStylusAdded_internal_updatesNotificationSuppression() { + startable.onStylusAdded(STYLUS_DEVICE_ID) + + verify(stylusUsiPowerUi, times(1)).updateSuppression(false) + } + + @Test + fun onStylusAdded_external_noop() { + startable.onStylusAdded(EXTERNAL_DEVICE_ID) + + verifyZeroInteractions(stylusUsiPowerUi) + } + + @Test fun onStylusBluetoothConnected_refreshesNotification() { startable.onStylusBluetoothConnected(STYLUS_DEVICE_ID, "ANY") diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index 45f7df3c3f5b..c7c6b945f09a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -1172,7 +1172,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { inner class Listener : TemporaryViewDisplayController.Listener { val permanentlyRemovedIds = mutableListOf<String>() - override fun onInfoPermanentlyRemoved(id: String) { + override fun onInfoPermanentlyRemoved(id: String, reason: String) { permanentlyRemovedIds.add(id) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt index a87a95060a7e..c539246d4902 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt @@ -22,6 +22,7 @@ import android.view.View import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -36,7 +37,7 @@ class SwipeChipbarAwayGestureHandlerTest : SysuiTestCase() { @Before fun setUp() { - underTest = SwipeChipbarAwayGestureHandler(context, mock()) + underTest = SwipeChipbarAwayGestureHandler(context, FakeDisplayTracker(mContext), mock()) } @Test 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 68ccc301755d..bc3343993737 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -91,6 +91,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; import com.android.systemui.shade.NotificationShadeWindowView; @@ -287,6 +288,8 @@ public class BubblesTest extends SysuiTestCase { private TestableLooper mTestableLooper; + private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -333,7 +336,7 @@ public class BubblesTest extends SysuiTestCase { mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); - mSysUiState = new SysUiState(); + mSysUiState = new SysUiState(mDisplayTracker); mSysUiState.addCallback(sysUiFlags -> { mSysUiStateBubblesManageMenuExpanded = (sysUiFlags 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 7ae47b41d5ae..45489d974195 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.notetask.NoteTaskInitializer; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -63,6 +64,7 @@ import java.util.concurrent.Executor; @RunWith(AndroidJUnit4.class) public class WMShellTest extends SysuiTestCase { WMShell mWMShell; + FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); @Mock ShellInterface mShellInterface; @Mock CommandQueue mCommandQueue; @@ -100,6 +102,7 @@ public class WMShellTest extends SysuiTestCase { mProtoTracer, mWakefulnessLifecycle, mUserTracker, + mDisplayTracker, mNoteTaskInitializer, mSysUiMainExecutor ); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt index 990db77463f6..f723a9e57e72 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt @@ -23,14 +23,20 @@ fun fakeDialogLaunchAnimator( isUnlocked: Boolean = true, isShowingAlternateAuthOnUnlock: Boolean = false, interactionJankMonitor: InteractionJankMonitor = mock(InteractionJankMonitor::class.java), + isPredictiveBackQsDialogAnim: Boolean = false, ): DialogLaunchAnimator { return DialogLaunchAnimator( - FakeCallback( - isUnlocked = isUnlocked, - isShowingAlternateAuthOnUnlock = isShowingAlternateAuthOnUnlock, - ), - interactionJankMonitor, - fakeLaunchAnimator(), + callback = + FakeCallback( + isUnlocked = isUnlocked, + isShowingAlternateAuthOnUnlock = isShowingAlternateAuthOnUnlock, + ), + interactionJankMonitor = interactionJankMonitor, + featureFlags = + object : AnimationFeatureFlags { + override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim + }, + launchAnimator = fakeLaunchAnimator(), isForTesting = true, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt index 6c82cef22ddb..b94f816e1ca4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt @@ -38,12 +38,6 @@ class FakeFeatureFlags : FeatureFlags { } } - fun set(flag: DeviceConfigBooleanFlag, value: Boolean) { - if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { - notifyFlagChanged(flag) - } - } - fun set(flag: ResourceBooleanFlag, value: Boolean) { if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { notifyFlagChanged(flag) @@ -73,7 +67,7 @@ class FakeFeatureFlags : FeatureFlags { listeners.forEach { listener -> listener.onFlagChanged( object : FlagListenable.FlagEvent { - override val flagId = flag.id + override val flagName = flag.name override fun requestNoRestart() {} } ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt new file mode 100644 index 000000000000..5641832b6ae2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.repository + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository { + private val _isLockedOut = MutableStateFlow<Boolean>(false) + override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow() + + fun setLockedOut(lockedOut: Boolean) { + _isLockedOut.value = lockedOut + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 15b473640de7..065fe8903e40 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.WakefulnessState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow /** Fake implementation of [KeyguardRepository] */ class FakeKeyguardRepository : KeyguardRepository { @@ -101,6 +102,13 @@ class FakeKeyguardRepository : KeyguardRepository { private val _biometricUnlockSource = MutableStateFlow<BiometricUnlockSource?>(null) override val biometricUnlockSource: Flow<BiometricUnlockSource?> = _biometricUnlockSource + private val _isQuickSettingsVisible = MutableStateFlow(false) + override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow() + + override fun setQuickSettingsVisible(isVisible: Boolean) { + _isQuickSettingsVisible.value = isVisible + } + override fun isKeyguardShowing(): Boolean { return _isKeyguardShowing.value } @@ -169,6 +177,10 @@ class FakeKeyguardRepository : KeyguardRepository { _dozeTransitionModel.value = model } + fun setStatusBarState(state: StatusBarState) { + _statusBarState.value = state + } + override fun isUdfpsSupported(): Boolean { return _isUdfpsSupported.value } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index 6c4424470c1c..eac1bd145033 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -22,13 +22,15 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import java.util.UUID +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow /** Fake implementation of [KeyguardTransitionRepository] */ class FakeKeyguardTransitionRepository : KeyguardTransitionRepository { - private val _transitions = MutableSharedFlow<TransitionStep>() + private val _transitions = + MutableSharedFlow<TransitionStep>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) override val transitions: SharedFlow<TransitionStep> = _transitions suspend fun sendTransitionStep(step: TransitionStep) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt new file mode 100644 index 000000000000..6ae7c34e13f2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings + +import android.content.Context +import android.hardware.display.DisplayManager +import android.view.Display +import java.util.concurrent.Executor + +class FakeDisplayTracker internal constructor(val context: Context) : DisplayTracker { + val displayManager: DisplayManager = context.getSystemService(DisplayManager::class.java)!! + override var defaultDisplayId: Int = Display.DEFAULT_DISPLAY + override var allDisplays: Array<Display> = displayManager.displays + + private val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList() + private val brightnessCallbacks: MutableList<DisplayTracker.Callback> = ArrayList() + override fun addDisplayChangeCallback(callback: DisplayTracker.Callback, executor: Executor) { + displayCallbacks.add(callback) + } + override fun addBrightnessChangeCallback( + callback: DisplayTracker.Callback, + executor: Executor + ) { + brightnessCallbacks.add(callback) + } + + override fun removeCallback(callback: DisplayTracker.Callback) { + displayCallbacks.remove(callback) + brightnessCallbacks.remove(callback) + } + + fun setDefaultDisplay(displayId: Int) { + defaultDisplayId = displayId + } + + fun setDisplays(displays: Array<Display>) { + allDisplays = displays + } + + fun triggerOnDisplayAdded(displayId: Int) { + notifyCallbacks({ onDisplayAdded(displayId) }, displayCallbacks) + } + + fun triggerOnDisplayRemoved(displayId: Int) { + notifyCallbacks({ onDisplayRemoved(displayId) }, displayCallbacks) + } + + fun triggerOnDisplayChanged(displayId: Int) { + notifyCallbacks({ onDisplayChanged(displayId) }, displayCallbacks) + } + + fun triggerOnDisplayBrightnessChanged(displayId: Int) { + notifyCallbacks({ onDisplayChanged(displayId) }, brightnessCallbacks) + } + + private inline fun notifyCallbacks( + crossinline action: DisplayTracker.Callback.() -> Unit, + list: List<DisplayTracker.Callback> + ) { + list.forEach { it.action() } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt index 0dd1fc758a27..251014fc50b3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt @@ -67,7 +67,10 @@ class FakeUserTracker( _userHandle = UserHandle.of(_userId) val copy = callbacks.toList() - copy.forEach { it.onUserChanged(_userId, userContext) } + copy.forEach { + it.onUserChanging(_userId, userContext) + it.onUserChanged(_userId, userContext) + } } fun onProfileChanged() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt index 4a881a7ce898..fd1b8e9ebb7a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt @@ -41,7 +41,7 @@ class FakeSharedPreferences : SharedPreferences { } override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? { - return data.getOrDefault(key, defValues) as? MutableSet<String>? + return (data.getOrDefault(key, defValues) as? Set<String>?)?.toMutableSet() } override fun getInt(key: String, defValue: Int): Int { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java index 33ef9cf7a9c5..1baac84d5ca6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java @@ -46,11 +46,6 @@ public class FakeNetworkController extends BaseLeakChecker<SignalCallback> } @Override - public boolean hasEmergencyCryptKeeperText() { - return false; - } - - @Override public boolean isRadioOn() { return false; } diff --git a/packages/SystemUI/tools/lint/baseline.xml b/packages/SystemUI/tools/lint/baseline.xml index 9a2e3207a6f4..301c9b831c3b 100644 --- a/packages/SystemUI/tools/lint/baseline.xml +++ b/packages/SystemUI/tools/lint/baseline.xml @@ -337,17 +337,6 @@ <issue id="Deprecated" message="`android:singleLine` is deprecated: Use `maxLines="1"` instead" - errorLine1=" android:singleLine="true"" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> - <location - file="res/layout/emergency_cryptkeeper_text.xml" - line="25" - column="9"/> - </issue> - - <issue - id="Deprecated" - message="`android:singleLine` is deprecated: Use `maxLines="1"` instead" errorLine1=" android:singleLine="true"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> <location @@ -88997,17 +88986,6 @@ errorLine1=" android:paddingStart="6dp"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> <location - file="res/layout/emergency_cryptkeeper_text.xml" - line="24" - column="9"/> - </issue> - - <issue - id="RtlSymmetry" - message="When you define `paddingStart` you should probably also define `paddingEnd` for right-to-left symmetry" - errorLine1=" android:paddingStart="6dp"" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> - <location file="res/layout/heads_up_status_bar_layout.xml" line="43" column="9"/> diff --git a/packages/VpnDialogs/res/values-ky/strings.xml b/packages/VpnDialogs/res/values-ky/strings.xml index 31f9e2da11c7..ea33e3427b0d 100644 --- a/packages/VpnDialogs/res/values-ky/strings.xml +++ b/packages/VpnDialogs/res/values-ky/strings.xml @@ -29,7 +29,7 @@ <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> тармагына ар дайым туташып турсун деп жөндөлгөн, бирок учурда телефонуңуз ага туташа албай жатат. <xliff:g id="VPN_APP_1">%1$s</xliff:g> тармагына кайра туташканга чейин телефонуңуз жалпыга ачык тармакты пайдаланып турат."</string> <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> тармагына ар дайым туташып турсун деп жөндөлгөн, бирок учурда телефонуңуз ага туташа албай жатат. VPN тармагына кайра туташмайынча, Интернет жок болот."</string> <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string> - <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN жөндөөлөрүн өзгөртүү"</string> + <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN параметрлерин өзгөртүү"</string> <string name="configure" msgid="4905518375574791375">"Конфигурациялоо"</string> <string name="disconnect" msgid="971412338304200056">"Ажыратуу"</string> <string name="open_app" msgid="3717639178595958667">"Колдонмону ачуу"</string> diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 19b5cc93932b..5d4dc39341a1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -8196,15 +8196,13 @@ public class ActivityManagerService extends IActivityManager.Stub t.traceEnd(); } + boolean isBootingSystemUser = currentUserId == UserHandle.USER_SYSTEM; + // Some systems - like automotive - will explicitly unlock system user then switch - // to a secondary user. Hence, we don't want to send duplicate broadcasts for - // the system user here. + // to a secondary user. // TODO(b/242195409): this workaround shouldn't be necessary once we move // the headless-user start logic to UserManager-land. - final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM) - && !UserManager.isHeadlessSystemUserMode(); - - if (isBootingSystemUser) { + if (isBootingSystemUser && !UserManager.isHeadlessSystemUserMode()) { t.traceBegin("startHomeOnAllDisplays"); mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady"); t.traceEnd(); @@ -8216,6 +8214,10 @@ public class ActivityManagerService extends IActivityManager.Stub if (isBootingSystemUser) { + // Need to send the broadcasts for the system user here because + // UserController#startUserInternal will not send them for the system user starting, + // It checks if the user state already exists, which is always the case for the + // system user. t.traceBegin("sendUserStartBroadcast"); final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 3584f16c6de1..ac782289606a 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -51,6 +51,7 @@ import android.app.GameManager.GameMode; import android.app.GameModeInfo; import android.app.GameState; import android.app.IGameManagerService; +import android.app.IUidObserver; import android.app.compat.PackageOverride; import android.content.BroadcastReceiver; import android.content.Context; @@ -118,6 +119,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * Service to manage game related features. @@ -171,40 +173,19 @@ public final class GameManagerService extends IGameManagerService.Stub { private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>(); @Nullable private final GameServiceController mGameServiceController; + private final Object mUidObserverLock = new Object(); + @VisibleForTesting + @Nullable + final UidObserver mUidObserver; + @GuardedBy("mUidObserverLock") + private final Set<Integer> mForegroundGameUids = new HashSet<>(); public GameManagerService(Context context) { this(context, createServiceThread().getLooper()); } GameManagerService(Context context, Looper looper) { - mContext = context; - mHandler = new SettingsHandler(looper); - mPackageManager = mContext.getPackageManager(); - mUserManager = mContext.getSystemService(UserManager.class); - mPlatformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); - mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); - mSystemDir = new File(Environment.getDataDirectory(), "system"); - mSystemDir.mkdirs(); - FileUtils.setPermissions(mSystemDir.toString(), - FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, - -1, -1); - mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir, - GAME_MODE_INTERVENTION_LIST_FILE_NAME)); - FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(), - FileUtils.S_IRUSR | FileUtils.S_IWUSR - | FileUtils.S_IRGRP | FileUtils.S_IWGRP, - -1, -1); - if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) { - mGameServiceController = new GameServiceController( - context, BackgroundThread.getExecutor(), - new GameServiceProviderSelectorImpl( - context.getResources(), - context.getPackageManager()), - new GameServiceProviderInstanceFactoryImpl(context)); - } else { - mGameServiceController = null; - } + this(context, looper, Environment.getDataDirectory()); } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @@ -237,6 +218,14 @@ public final class GameManagerService extends IGameManagerService.Stub { } else { mGameServiceController = null; } + mUidObserver = new UidObserver(); + try { + ActivityManager.getService().registerUidObserver(mUidObserver, + ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, + ActivityManager.PROCESS_STATE_UNKNOWN, null); + } catch (RemoteException e) { + Slog.w(TAG, "Could not register UidObserver"); + } } @Override @@ -1874,4 +1863,66 @@ public final class GameManagerService extends IGameManagerService.Stub { * load dynamic library for frame rate overriding JNI calls */ private static native void nativeSetOverrideFrameRate(int uid, float frameRate); + + final class UidObserver extends IUidObserver.Stub { + @Override + public void onUidIdle(int uid, boolean disabled) {} + + @Override + public void onUidGone(int uid, boolean disabled) { + synchronized (mUidObserverLock) { + disableGameMode(uid); + } + } + + @Override + public void onUidActive(int uid) {} + + @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { + synchronized (mUidObserverLock) { + if (ActivityManager.isProcStateBackground(procState)) { + disableGameMode(uid); + return; + } + + final String[] packages = mContext.getPackageManager().getPackagesForUid(uid); + if (packages == null || packages.length == 0) { + return; + } + + final int userId = mContext.getUserId(); + if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) { + return; + } + + if (mForegroundGameUids.isEmpty()) { + Slog.v(TAG, "Game power mode ON (process state was changed to foreground)"); + mPowerManagerInternal.setPowerMode(Mode.GAME, true); + } + mForegroundGameUids.add(uid); + } + } + + private void disableGameMode(int uid) { + synchronized (mUidObserverLock) { + if (!mForegroundGameUids.contains(uid)) { + return; + } + mForegroundGameUids.remove(uid); + if (!mForegroundGameUids.isEmpty()) { + return; + } + Slog.v(TAG, + "Game power mode OFF (process remomved or state changed to background)"); + mPowerManagerInternal.setPowerMode(Mode.GAME, false); + } + } + + @Override + public void onUidCachedChanged(int uid, boolean cached) {} + + @Override + public void onUidProcAdjChanged(int uid) {} + } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 278c98f45c44..0589cfc0967b 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -56,6 +56,7 @@ import android.util.PrintWriterPrinter; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; @@ -434,6 +435,48 @@ import java.util.concurrent.atomic.AtomicBoolean; return device; } + private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = { + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, + AudioDeviceInfo.TYPE_BLUETOOTH_SCO, + AudioDeviceInfo.TYPE_WIRED_HEADSET, + AudioDeviceInfo.TYPE_USB_HEADSET, + AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, + AudioDeviceInfo.TYPE_WIRED_HEADPHONES, + AudioDeviceInfo.TYPE_HEARING_AID, + AudioDeviceInfo.TYPE_BLE_HEADSET, + AudioDeviceInfo.TYPE_USB_DEVICE, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + AudioDeviceInfo.TYPE_LINE_ANALOG, + AudioDeviceInfo.TYPE_HDMI, + AudioDeviceInfo.TYPE_AUX_LINE + }; + + /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) { + for (int type : VALID_COMMUNICATION_DEVICE_TYPES) { + if (device.getType() == type) { + return true; + } + } + return false; + } + + /* package */ static List<AudioDeviceInfo> getAvailableCommunicationDevices() { + ArrayList<AudioDeviceInfo> commDevices = new ArrayList<>(); + AudioDeviceInfo[] allDevices = + AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo device : allDevices) { + if (isValidCommunicationDevice(device)) { + commDevices.add(device); + } + } + return commDevices; + } + + private @Nullable AudioDeviceInfo getCommunicationDeviceOfType(int type) { + return getAvailableCommunicationDevices().stream().filter(d -> d.getType() == type) + .findFirst().orElse(null); + } + /** * Returns the device currently requested for communication use case. * @return AudioDeviceInfo the requested device for communication. @@ -441,7 +484,29 @@ import java.util.concurrent.atomic.AtomicBoolean; /* package */ AudioDeviceInfo getCommunicationDevice() { synchronized (mDeviceStateLock) { updateActiveCommunicationDevice(); - return mActiveCommunicationDevice; + AudioDeviceInfo device = mActiveCommunicationDevice; + // make sure we return a valid communication device (i.e. a device that is allowed by + // setCommunicationDevice()) for consistency. + if (device != null) { + // a digital dock is used instead of the speaker in speakerphone mode and should + // be reflected as such + if (device.getType() == AudioDeviceInfo.TYPE_DOCK) { + device = getCommunicationDeviceOfType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER); + } + } + // Try to default to earpiece when current communication device is not valid. This can + // happen for instance if no call is active. If no earpiece device is available take the + // first valid communication device + if (device == null || !AudioDeviceBroker.isValidCommunicationDevice(device)) { + device = getCommunicationDeviceOfType(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE); + if (device == null) { + List<AudioDeviceInfo> commDevices = getAvailableCommunicationDevices(); + if (!commDevices.isEmpty()) { + device = commDevices.get(0); + } + } + } + return device; } } @@ -918,8 +983,8 @@ import java.util.concurrent.atomic.AtomicBoolean; @GuardedBy("mDeviceStateLock") private void dispatchCommunicationDevice() { - int portId = (mActiveCommunicationDevice == null) ? 0 - : mActiveCommunicationDevice.getId(); + AudioDeviceInfo device = getCommunicationDevice(); + int portId = device != null ? device.getId() : 0; if (portId == mCurCommunicationPortId) { return; } @@ -936,6 +1001,7 @@ import java.util.concurrent.atomic.AtomicBoolean; mCommDevDispatchers.finishBroadcast(); } + //--------------------------------------------------------------------- // Communication with (to) AudioService //TODO check whether the AudioService methods are candidates to move here diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1bd8f1ea1c18..c7c905e4066b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1253,6 +1253,20 @@ public class AudioService extends IAudioService.Stub 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); } + private void initVolumeStreamStates() { + int numStreamTypes = AudioSystem.getNumStreamTypes(); + synchronized (VolumeStreamState.class) { + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { + VolumeStreamState streamState = mStreamStates[streamType]; + int groupId = getVolumeGroupForStreamType(streamType); + if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP + && sVolumeGroupStates.indexOfKey(groupId) >= 0) { + streamState.setVolumeGroupState(sVolumeGroupStates.get(groupId)); + } + } + } + } + /** * Separating notification volume from ring is NOT of aliasing the corresponding streams * @param properties @@ -1282,6 +1296,8 @@ public class AudioService extends IAudioService.Stub // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it // relies on audio policy having correct ranges for volume indexes. mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex(); + // Link VGS on VSS + initVolumeStreamStates(); // Call setRingerModeInt() to apply correct mute // state on streams affected by ringer mode. @@ -3340,15 +3356,7 @@ public class AudioService extends IAudioService.Stub } else { state = direction == AudioManager.ADJUST_MUTE; } - for (int stream = 0; stream < mStreamStates.length; stream++) { - if (streamTypeAlias == mStreamVolumeAlias[stream]) { - if (!(readCameraSoundForced() - && (mStreamStates[stream].getStreamType() - == AudioSystem.STREAM_SYSTEM_ENFORCED))) { - mStreamStates[stream].mute(state); - } - } - } + muteAliasStreams(streamTypeAlias, state); } else if ((direction == AudioManager.ADJUST_RAISE) && !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex); @@ -3363,7 +3371,7 @@ public class AudioService extends IAudioService.Stub // Unmute the stream if it was previously muted if (direction == AudioManager.ADJUST_RAISE) { // unmute immediately for volume up - streamState.mute(false); + muteAliasStreams(streamTypeAlias, false); } else if (direction == AudioManager.ADJUST_LOWER) { if (mIsSingleVolume) { sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE, @@ -3489,6 +3497,42 @@ public class AudioService extends IAudioService.Stub sendVolumeUpdate(streamType, oldIndex, newIndex, flags, device); } + /** + * Loops on aliasted stream, update the mute cache attribute of each + * {@see AudioService#VolumeStreamState}, and then apply the change. + * It prevents to unnecessary {@see AudioSystem#setStreamVolume} done for each stream + * and aliases before mute change changed and after. + */ + private void muteAliasStreams(int streamAlias, boolean state) { + synchronized (VolumeStreamState.class) { + List<Integer> streamsToMute = new ArrayList<>(); + for (int stream = 0; stream < mStreamStates.length; stream++) { + if (streamAlias == mStreamVolumeAlias[stream]) { + if (!(readCameraSoundForced() + && (mStreamStates[stream].getStreamType() + == AudioSystem.STREAM_SYSTEM_ENFORCED))) { + boolean changed = mStreamStates[stream].mute(state, /* apply= */ false); + if (changed) { + streamsToMute.add(stream); + } + } + } + } + streamsToMute.forEach(streamToMute -> { + mStreamStates[streamToMute].doMute(); + broadcastMuteSetting(streamToMute, state); + }); + } + } + + private void broadcastMuteSetting(int streamType, boolean isMuted) { + // Stream mute changed, fire the intent. + Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); + intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType); + intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, isMuted); + sendBroadcastToAll(intent); + } + // Called after a delay when volume down is pressed while muted private void onUnmuteStream(int stream, int flags) { boolean wasMuted; @@ -3593,8 +3637,19 @@ public class AudioService extends IAudioService.Stub return false; } + /** + * Update stream volume, ringer mode and mute status after a volume index change + * @param streamType + * @param index + * @param flags + * @param device the device for which the volume is changed + * @param caller + * @param hasModifyAudioSettings + * @param canChangeMute true if the origin of this event is one where the mute state should be + * updated following the change in volume index + */ private void onSetStreamVolume(int streamType, int index, int flags, int device, - String caller, boolean hasModifyAudioSettings) { + String caller, boolean hasModifyAudioSettings, boolean canChangeMute) { final int stream = mStreamVolumeAlias[streamType]; setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings); // setting volume on ui sounds stream type also controls silent mode @@ -3604,11 +3659,10 @@ public class AudioService extends IAudioService.Stub TAG + ".onSetStreamVolume", false /*external*/); } // setting non-zero volume for a muted stream unmutes the stream and vice versa - // (only when changing volume for the current device), // except for BT SCO stream where only explicit mute is allowed to comply to BT requirements - if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO) - && (getDeviceForStream(stream) == device)) { - mStreamStates[stream].mute(index == 0); + if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO) && canChangeMute) { + // As adjustStreamVolume with muteAdjust flags mute/unmutes stream and aliased streams. + muteAliasStreams(stream, index == 0); } } @@ -3664,29 +3718,27 @@ public class AudioService extends IAudioService.Stub } - /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */ - public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags, + /** @see AudioManager#setVolumeGroupVolumeIndex(int, int, int) */ + public void setVolumeGroupVolumeIndex(int groupId, int index, int flags, String callingPackage, String attributionTag) { enforceModifyAudioRoutingPermission(); - Objects.requireNonNull(attr, "attr must not be null"); - final int volumeGroup = getVolumeGroupIdForAttributes(attr); - if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { - Log.e(TAG, ": no volume group found for attributes " + attr.toString()); + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + Log.e(TAG, ": no volume group found for id " + groupId); return; } - final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); - sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(), - index/*val1*/, flags/*val2*/, callingPackage)); + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, vgs.name(), + index, flags, callingPackage + ", user " + getCurrentUserId())); vgs.setVolumeIndex(index, flags); // For legacy reason, propagate to all streams associated to this volume group - for (final int groupedStream : vgs.getLegacyStreamTypes()) { + for (int groupedStream : vgs.getLegacyStreamTypes()) { try { ensureValidStreamType(groupedStream); } catch (IllegalArgumentException e) { - Log.d(TAG, "volume group " + volumeGroup + " has internal streams (" + groupedStream + Log.d(TAG, "volume group " + groupId + " has internal streams (" + groupedStream + "), do not change associated stream volume"); continue; } @@ -3698,7 +3750,7 @@ public class AudioService extends IAudioService.Stub @Nullable private AudioVolumeGroup getAudioVolumeGroupById(int volumeGroupId) { - for (final AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) { + for (AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) { if (avg.getId() == volumeGroupId) { return avg; } @@ -3708,30 +3760,42 @@ public class AudioService extends IAudioService.Stub return null; } - /** @see AudioManager#getVolumeIndexForAttributes(attr) */ - public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) { + /** @see AudioManager#getVolumeGroupVolumeIndex(int) */ + public int getVolumeGroupVolumeIndex(int groupId) { enforceModifyAudioRoutingPermission(); - Objects.requireNonNull(attr, "attr must not be null"); - final int volumeGroup = getVolumeGroupIdForAttributes(attr); - if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { - throw new IllegalArgumentException("No volume group for attributes " + attr); + synchronized (VolumeStreamState.class) { + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + throw new IllegalArgumentException("No volume group for id " + groupId); + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + // Return 0 when muted, not min index since for e.g. Voice Call, it has a non zero + // min but it mutable on permission condition. + return vgs.isMuted() ? 0 : vgs.getVolumeIndex(); } - final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); - return vgs.getVolumeIndex(); } - /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */ - public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) { + /** @see AudioManager#getVolumeGroupMaxVolumeIndex(int) */ + public int getVolumeGroupMaxVolumeIndex(int groupId) { enforceModifyAudioRoutingPermission(); - Objects.requireNonNull(attr, "attr must not be null"); - return AudioSystem.getMaxVolumeIndexForAttributes(attr); + synchronized (VolumeStreamState.class) { + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + throw new IllegalArgumentException("No volume group for id " + groupId); + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + return vgs.getMaxIndex(); + } } - /** @see AudioManager#getMinVolumeIndexForAttributes(attr) */ - public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) { + /** @see AudioManager#getVolumeGroupMinVolumeIndex(int) */ + public int getVolumeGroupMinVolumeIndex(int groupId) { enforceModifyAudioRoutingPermission(); - Objects.requireNonNull(attr, "attr must not be null"); - return AudioSystem.getMinVolumeIndexForAttributes(attr); + synchronized (VolumeStreamState.class) { + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + throw new IllegalArgumentException("No volume group for id " + groupId); + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + return vgs.getMinIndex(); + } } /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes) @@ -3808,6 +3872,70 @@ public class AudioService extends IAudioService.Stub callingPackage, /*attributionTag*/ null); } + /** @see AudioManager#adjustVolumeGroupVolume(int, int, int) */ + public void adjustVolumeGroupVolume(int groupId, int direction, int flags, + String callingPackage) { + ensureValidDirection(direction); + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + Log.e(TAG, ": no volume group found for id " + groupId); + return; + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + // For compatibility reason, use stream API if group linked to a valid stream + boolean fallbackOnStream = false; + for (int stream : vgs.getLegacyStreamTypes()) { + try { + ensureValidStreamType(stream); + } catch (IllegalArgumentException e) { + Log.d(TAG, "volume group " + groupId + " has internal streams (" + stream + + "), do not change associated stream volume"); + continue; + } + // Note: Group and Stream does not share same convention, 0 is mute for stream, + // min index is acting as mute for Groups + if (vgs.isVssMuteBijective(stream)) { + adjustStreamVolume(stream, direction, flags, callingPackage); + if (isMuteAdjust(direction)) { + // will be propagated to all aliased streams + return; + } + fallbackOnStream = true; + } + } + if (fallbackOnStream) { + // Handled by at least one stream, will be propagated to group, bailing out. + return; + } + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_GROUP_VOL, vgs.name(), + direction, flags, callingPackage)); + vgs.adjustVolume(direction, flags); + } + + /** @see AudioManager#getLastAudibleVolumeGroupVolume(int) */ + public int getLastAudibleVolumeGroupVolume(int groupId) { + enforceQueryStatePermission(); + synchronized (VolumeStreamState.class) { + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + Log.e(TAG, ": no volume group found for id " + groupId); + return 0; + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + return vgs.getVolumeIndex(); + } + } + + /** @see AudioManager#isVolumeGroupMuted(int) */ + public boolean isVolumeGroupMuted(int groupId) { + synchronized (VolumeStreamState.class) { + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + Log.e(TAG, ": no volume group found for id " + groupId); + return false; + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + return vgs.isMuted(); + } + } + /** @see AudioManager#setStreamVolume(int, int, int) * Part of service interface, check permissions here */ public void setStreamVolumeWithAttribution(int streamType, int index, int flags, @@ -4247,7 +4375,10 @@ public class AudioService extends IAudioService.Stub mPendingVolumeCommand = new StreamVolumeCommand( streamType, index, flags, device); } else { - onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings); + onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings, + // ada is non-null when called from setDeviceVolume, + // which shouldn't update the mute state + ada == null /*canChangeMute*/); index = mStreamStates[streamType].getIndex(device); } } @@ -4264,31 +4395,6 @@ public class AudioService extends IAudioService.Stub } } - - - private int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) { - Objects.requireNonNull(attributes, "attributes must not be null"); - int volumeGroupId = getVolumeGroupIdForAttributesInt(attributes); - if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { - return volumeGroupId; - } - // The default volume group is the one hosted by default product strategy, i.e. - // supporting Default Attributes - return getVolumeGroupIdForAttributesInt(AudioProductStrategy.getDefaultAttributes()); - } - - private int getVolumeGroupIdForAttributesInt(@NonNull AudioAttributes attributes) { - Objects.requireNonNull(attributes, "attributes must not be null"); - for (final AudioProductStrategy productStrategy : - AudioProductStrategy.getAudioProductStrategies()) { - int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes); - if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { - return volumeGroupId; - } - } - return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; - } - private void dispatchAbsoluteVolumeChanged(int streamType, AbsoluteVolumeDeviceInfo deviceInfo, int index) { VolumeInfo volumeInfo = deviceInfo.getMatchingVolumeInfoForStream(streamType); @@ -4321,7 +4427,6 @@ public class AudioService extends IAudioService.Stub } } - // No ringer or zen muted stream volumes can be changed unless it'll exit dnd private boolean volumeAdjustmentAllowedByDnd(int streamTypeAlias, int flags) { switch (mNm.getZenMode()) { @@ -5013,7 +5118,7 @@ public class AudioService extends IAudioService.Stub } private void setRingerMode(int ringerMode, String caller, boolean external) { - if (mUseFixedVolume || mIsSingleVolume) { + if (mUseFixedVolume || mIsSingleVolume || mUseVolumeGroupAliases) { return; } if (caller == null || caller.length() == 0) { @@ -5838,7 +5943,7 @@ public class AudioService extends IAudioService.Stub } } - readVolumeGroupsSettings(); + readVolumeGroupsSettings(userSwitch); if (DEBUG_VOL) { Log.d(TAG, "Restoring device volume behavior"); @@ -5846,46 +5951,16 @@ public class AudioService extends IAudioService.Stub restoreDeviceVolumeBehavior(); } - private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = { - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, - AudioDeviceInfo.TYPE_BLUETOOTH_SCO, - AudioDeviceInfo.TYPE_WIRED_HEADSET, - AudioDeviceInfo.TYPE_USB_HEADSET, - AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, - AudioDeviceInfo.TYPE_WIRED_HEADPHONES, - AudioDeviceInfo.TYPE_HEARING_AID, - AudioDeviceInfo.TYPE_BLE_HEADSET, - AudioDeviceInfo.TYPE_USB_DEVICE, - AudioDeviceInfo.TYPE_BLE_SPEAKER, - AudioDeviceInfo.TYPE_LINE_ANALOG, - AudioDeviceInfo.TYPE_HDMI, - AudioDeviceInfo.TYPE_AUX_LINE - }; - - private boolean isValidCommunicationDevice(AudioDeviceInfo device) { - for (int type : VALID_COMMUNICATION_DEVICE_TYPES) { - if (device.getType() == type) { - return true; - } - } - return false; - } - /** @see AudioManager#getAvailableCommunicationDevices(int) */ public int[] getAvailableCommunicationDeviceIds() { - ArrayList<Integer> deviceIds = new ArrayList<>(); - AudioDeviceInfo[] devices = AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS); - for (AudioDeviceInfo device : devices) { - if (isValidCommunicationDevice(device)) { - deviceIds.add(device.getId()); - } - } - return deviceIds.stream().mapToInt(Integer::intValue).toArray(); + List<AudioDeviceInfo> commDevices = AudioDeviceBroker.getAvailableCommunicationDevices(); + return commDevices.stream().mapToInt(AudioDeviceInfo::getId).toArray(); } - /** - * @see AudioManager#setCommunicationDevice(int) - * @see AudioManager#clearCommunicationDevice() - */ + + /** + * @see AudioManager#setCommunicationDevice(int) + * @see AudioManager#clearCommunicationDevice() + */ public boolean setCommunicationDevice(IBinder cb, int portId) { final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); @@ -5894,9 +5969,10 @@ public class AudioService extends IAudioService.Stub if (portId != 0) { device = AudioManager.getDeviceForPortId(portId, AudioManager.GET_DEVICES_OUTPUTS); if (device == null) { - throw new IllegalArgumentException("invalid portID " + portId); + Log.w(TAG, "setCommunicationDevice: invalid portID " + portId); + return false; } - if (!isValidCommunicationDevice(device)) { + if (!AudioDeviceBroker.isValidCommunicationDevice(device)) { throw new IllegalArgumentException("invalid device type " + device.getType()); } } @@ -5939,13 +6015,15 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getCommunicationDevice() */ public int getCommunicationDevice() { + int deviceId = 0; final long ident = Binder.clearCallingIdentity(); - AudioDeviceInfo device = mDeviceBroker.getCommunicationDevice(); - Binder.restoreCallingIdentity(ident); - if (device == null) { - return 0; + try { + AudioDeviceInfo device = mDeviceBroker.getCommunicationDevice(); + deviceId = device != null ? device.getId() : 0; + } finally { + Binder.restoreCallingIdentity(ident); } - return device.getId(); + return deviceId; } /** @see AudioManager#addOnCommunicationDeviceChangedListener( @@ -7313,6 +7391,7 @@ public class AudioService extends IAudioService.Stub try { // if no valid attributes, this volume group is not controllable, throw exception ensureValidAttributes(avg); + sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg)); } catch (IllegalArgumentException e) { // Volume Groups without attributes are not controllable through set/get volume // using attributes. Do not append them. @@ -7321,11 +7400,10 @@ public class AudioService extends IAudioService.Stub } continue; } - sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg)); } for (int i = 0; i < sVolumeGroupStates.size(); i++) { final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i); - vgs.applyAllVolumes(); + vgs.applyAllVolumes(/* userSwitch= */ false); } } @@ -7338,14 +7416,22 @@ public class AudioService extends IAudioService.Stub } } - private void readVolumeGroupsSettings() { - if (DEBUG_VOL) { - Log.v(TAG, "readVolumeGroupsSettings"); - } - for (int i = 0; i < sVolumeGroupStates.size(); i++) { - final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i); - vgs.readSettings(); - vgs.applyAllVolumes(); + private void readVolumeGroupsSettings(boolean userSwitch) { + synchronized (mSettingsLock) { + synchronized (VolumeStreamState.class) { + if (DEBUG_VOL) { + Log.d(TAG, "readVolumeGroupsSettings userSwitch=" + userSwitch); + } + for (int i = 0; i < sVolumeGroupStates.size(); i++) { + VolumeGroupState vgs = sVolumeGroupStates.valueAt(i); + // as for STREAM_MUSIC, preserve volume from one user to the next. + if (!(userSwitch && vgs.isMusic())) { + vgs.clearIndexCache(); + vgs.readSettings(); + } + vgs.applyAllVolumes(userSwitch); + } + } } } @@ -7356,7 +7442,7 @@ public class AudioService extends IAudioService.Stub } for (int i = 0; i < sVolumeGroupStates.size(); i++) { final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i); - vgs.applyAllVolumes(); + vgs.applyAllVolumes(false/*userSwitch*/); } } @@ -7369,17 +7455,34 @@ public class AudioService extends IAudioService.Stub } } + private static boolean isCallStream(int stream) { + return stream == AudioSystem.STREAM_VOICE_CALL + || stream == AudioSystem.STREAM_BLUETOOTH_SCO; + } + + private static int getVolumeGroupForStreamType(int stream) { + AudioAttributes attributes = + AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(stream); + if (attributes.equals(new AudioAttributes.Builder().build())) { + return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; + } + return AudioProductStrategy.getVolumeGroupIdForAudioAttributes( + attributes, /* fallbackOnDefault= */ false); + } + // NOTE: Locking order for synchronized objects related to volume management: // 1 mSettingsLock - // 2 VolumeGroupState.class + // 2 VolumeStreamState.class private class VolumeGroupState { private final AudioVolumeGroup mAudioVolumeGroup; private final SparseIntArray mIndexMap = new SparseIntArray(8); private int mIndexMin; private int mIndexMax; - private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT; + private boolean mHasValidStreamType = false; private int mPublicStreamType = AudioSystem.STREAM_MUSIC; private AudioAttributes mAudioAttributes = AudioProductStrategy.getDefaultAttributes(); + private boolean mIsMuted = false; + private final String mSettingName; // No API in AudioSystem to get a device from strategy or from attributes. // Need a valid public stream type to use current API getDeviceForStream @@ -7393,20 +7496,22 @@ public class AudioService extends IAudioService.Stub Log.v(TAG, "VolumeGroupState for " + avg.toString()); } // mAudioAttributes is the default at this point - for (final AudioAttributes aa : avg.getAudioAttributes()) { + for (AudioAttributes aa : avg.getAudioAttributes()) { if (!aa.equals(mAudioAttributes)) { mAudioAttributes = aa; break; } } - final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes(); + int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes(); + String streamSettingName = ""; if (streamTypes.length != 0) { // Uses already initialized MIN / MAX if a stream type is attached to group - mLegacyStreamType = streamTypes[0]; - for (final int streamType : streamTypes) { + for (int streamType : streamTypes) { if (streamType != AudioSystem.STREAM_DEFAULT && streamType < AudioSystem.getNumStreamTypes()) { mPublicStreamType = streamType; + mHasValidStreamType = true; + streamSettingName = System.VOLUME_SETTINGS_INT[mPublicStreamType]; break; } } @@ -7416,10 +7521,10 @@ public class AudioService extends IAudioService.Stub mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes); mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes); } else { - Log.e(TAG, "volume group: " + mAudioVolumeGroup.name() + throw new IllegalArgumentException("volume group: " + mAudioVolumeGroup.name() + " has neither valid attributes nor valid stream types assigned"); - return; } + mSettingName = !streamSettingName.isEmpty() ? streamSettingName : ("volume_" + name()); // Load volume indexes from data base readSettings(); } @@ -7432,40 +7537,149 @@ public class AudioService extends IAudioService.Stub return mAudioVolumeGroup.name(); } + /** + * Volume group with non null minimum index are considered as non mutable, thus + * bijectivity is broken with potential associated stream type. + * VOICE_CALL stream has minVolumeIndex > 0 but can be muted directly by an + * app that has MODIFY_PHONE_STATE permission. + */ + private boolean isVssMuteBijective(int stream) { + return isStreamAffectedByMute(stream) + && (getMinIndex() == (mStreamStates[stream].mIndexMin + 5) / 10) + && (getMinIndex() == 0 || isCallStream(stream)); + } + + private boolean isMutable() { + return mIndexMin == 0 || (mHasValidStreamType && isVssMuteBijective(mPublicStreamType)); + } + /** + * Mute/unmute the volume group + * @param muted the new mute state + */ + @GuardedBy("AudioService.VolumeStreamState.class") + public boolean mute(boolean muted) { + if (!isMutable()) { + // Non mutable volume group + if (DEBUG_VOL) { + Log.d(TAG, "invalid mute on unmutable volume group " + name()); + } + return false; + } + boolean changed = (mIsMuted != muted); + // As for VSS, mute shall apply minIndex to all devices found in IndexMap and default. + if (changed) { + mIsMuted = muted; + applyAllVolumes(false /*userSwitch*/); + } + return changed; + } + + public boolean isMuted() { + return mIsMuted; + } + + public void adjustVolume(int direction, int flags) { + synchronized (VolumeStreamState.class) { + int device = getDeviceForVolume(); + int previousIndex = getIndex(device); + if (isMuteAdjust(direction) && !isMutable()) { + // Non mutable volume group + if (DEBUG_VOL) { + Log.d(TAG, "invalid mute on unmutable volume group " + name()); + } + return; + } + switch (direction) { + case AudioManager.ADJUST_TOGGLE_MUTE: { + // Note: If muted by volume 0, unmute will restore volume 0. + mute(!mIsMuted); + break; + } + case AudioManager.ADJUST_UNMUTE: + // Note: If muted by volume 0, unmute will restore volume 0. + mute(false); + break; + case AudioManager.ADJUST_MUTE: + // May be already muted by setvolume 0, prevent from setting same value + if (previousIndex != 0) { + // bypass persist + mute(true); + } + mIsMuted = true; + break; + case AudioManager.ADJUST_RAISE: + // As for stream, RAISE during mute will increment the index + setVolumeIndex(Math.min(previousIndex + 1, mIndexMax), device, flags); + break; + case AudioManager.ADJUST_LOWER: + // For stream, ADJUST_LOWER on a muted VSS is a no-op + // If we decide to unmute on ADJUST_LOWER, cannot fallback on + // adjustStreamVolume for group associated to legacy stream type + if (isMuted() && previousIndex != 0) { + mute(false); + } else { + int newIndex = Math.max(previousIndex - 1, mIndexMin); + setVolumeIndex(newIndex, device, flags); + } + break; + } + } + } + public int getVolumeIndex() { - return getIndex(getDeviceForVolume()); + synchronized (VolumeStreamState.class) { + return getIndex(getDeviceForVolume()); + } } public void setVolumeIndex(int index, int flags) { - if (mUseFixedVolume) { - return; + synchronized (VolumeStreamState.class) { + if (mUseFixedVolume) { + return; + } + setVolumeIndex(index, getDeviceForVolume(), flags); } - setVolumeIndex(index, getDeviceForVolume(), flags); } + @GuardedBy("AudioService.VolumeStreamState.class") private void setVolumeIndex(int index, int device, int flags) { - // Set the volume index - setVolumeIndexInt(index, device, flags); + // Update cache & persist (muted by volume 0 shall be persisted) + updateVolumeIndex(index, device); + // setting non-zero volume for a muted stream unmutes the stream and vice versa, + boolean changed = mute(index == 0); + if (!changed) { + // Set the volume index only if mute operation is a no-op + index = getValidIndex(index); + setVolumeIndexInt(index, device, flags); + } + } - // Update local cache - mIndexMap.put(device, index); + @GuardedBy("AudioService.VolumeStreamState.class") + public void updateVolumeIndex(int index, int device) { + // Filter persistency if already exist and the index has not changed + if (mIndexMap.indexOfKey(device) < 0 || mIndexMap.get(device) != index) { + // Update local cache + mIndexMap.put(device, getValidIndex(index)); - // update data base - post a persist volume group msg - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME_GROUP, - SENDMSG_QUEUE, - device, - 0, - this, - PERSIST_DELAY); + // update data base - post a persist volume group msg + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME_GROUP, + SENDMSG_QUEUE, + device, + 0, + this, + PERSIST_DELAY); + } } + @GuardedBy("AudioService.VolumeStreamState.class") private void setVolumeIndexInt(int index, int device, int flags) { // Reflect mute state of corresponding stream by forcing index to 0 if muted // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted. // This allows RX path muting by the audio HAL only when explicitly muted but not when // index is just set to 0 to repect BT requirements - if (mStreamStates[mPublicStreamType].isFullyMuted()) { + if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType) + && mStreamStates[mPublicStreamType].isFullyMuted()) { index = 0; } else if (mPublicStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0) { index = 1; @@ -7474,18 +7688,16 @@ public class AudioService extends IAudioService.Stub AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); } - public int getIndex(int device) { - synchronized (VolumeGroupState.class) { - int index = mIndexMap.get(device, -1); - // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT - return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT); - } + @GuardedBy("AudioService.VolumeStreamState.class") + private int getIndex(int device) { + int index = mIndexMap.get(device, -1); + // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT + return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT); } - public boolean hasIndexForDevice(int device) { - synchronized (VolumeGroupState.class) { - return (mIndexMap.get(device, -1) != -1); - } + @GuardedBy("AudioService.VolumeStreamState.class") + private boolean hasIndexForDevice(int device) { + return (mIndexMap.get(device, -1) != -1); } public int getMaxIndex() { @@ -7496,55 +7708,108 @@ public class AudioService extends IAudioService.Stub return mIndexMin; } - private boolean isValidLegacyStreamType() { - return (mLegacyStreamType != AudioSystem.STREAM_DEFAULT) - && (mLegacyStreamType < mStreamStates.length); + private boolean isValidStream(int stream) { + return (stream != AudioSystem.STREAM_DEFAULT) && (stream < mStreamStates.length); } - public void applyAllVolumes() { - synchronized (VolumeGroupState.class) { - int deviceForStream = AudioSystem.DEVICE_NONE; - int volumeIndexForStream = 0; - if (isValidLegacyStreamType()) { - // Prevent to apply settings twice when group is associated to public stream - deviceForStream = getDeviceForStream(mLegacyStreamType); - volumeIndexForStream = getStreamVolume(mLegacyStreamType); - } + public boolean isMusic() { + return mHasValidStreamType && mPublicStreamType == AudioSystem.STREAM_MUSIC; + } + + public void applyAllVolumes(boolean userSwitch) { + String caller = "from vgs"; + synchronized (VolumeStreamState.class) { // apply device specific volumes first - int index; for (int i = 0; i < mIndexMap.size(); i++) { - final int device = mIndexMap.keyAt(i); + int device = mIndexMap.keyAt(i); + int index = mIndexMap.valueAt(i); + boolean synced = false; if (device != AudioSystem.DEVICE_OUT_DEFAULT) { - index = mIndexMap.valueAt(i); - if (device == deviceForStream && volumeIndexForStream == index) { - continue; + for (int stream : getLegacyStreamTypes()) { + if (isValidStream(stream)) { + boolean streamMuted = mStreamStates[stream].mIsMuted; + int deviceForStream = getDeviceForStream(stream); + int indexForStream = + (mStreamStates[stream].getIndex(deviceForStream) + 5) / 10; + if (device == deviceForStream) { + if (indexForStream == index && (isMuted() == streamMuted) + && isVssMuteBijective(stream)) { + synced = true; + continue; + } + if (indexForStream != index) { + mStreamStates[stream].setIndex(index * 10, device, caller, + true /*hasModifyAudioSettings*/); + } + if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) { + mStreamStates[stream].mute(isMuted()); + } + } + } } - if (DEBUG_VOL) { - Log.v(TAG, "applyAllVolumes: restore index " + index + " for group " - + mAudioVolumeGroup.name() + " and device " - + AudioSystem.getOutputDeviceName(device)); + if (!synced) { + if (DEBUG_VOL) { + Log.d(TAG, "applyAllVolumes: apply index " + index + ", group " + + mAudioVolumeGroup.name() + " and device " + + AudioSystem.getOutputDeviceName(device)); + } + setVolumeIndexInt(isMuted() ? 0 : index, device, 0 /*flags*/); } - setVolumeIndexInt(index, device, 0 /*flags*/); } } // apply default volume last: by convention , default device volume will be used // by audio policy manager if no explicit volume is present for a given device type - index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT); - if (DEBUG_VOL) { - Log.v(TAG, "applyAllVolumes: restore default device index " + index - + " for group " + mAudioVolumeGroup.name()); + int index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT); + boolean synced = false; + int deviceForVolume = getDeviceForVolume(); + boolean forceDeviceSync = userSwitch && (mIndexMap.indexOfKey(deviceForVolume) < 0); + for (int stream : getLegacyStreamTypes()) { + if (isValidStream(stream)) { + boolean streamMuted = mStreamStates[stream].mIsMuted; + int defaultStreamIndex = (mStreamStates[stream].getIndex( + AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10; + if (forceDeviceSync) { + mStreamStates[stream].setIndex(index * 10, deviceForVolume, caller, + true /*hasModifyAudioSettings*/); + } + if (defaultStreamIndex == index && (isMuted() == streamMuted) + && isVssMuteBijective(stream)) { + synced = true; + continue; + } + if (defaultStreamIndex != index) { + mStreamStates[stream].setIndex( + index * 10, AudioSystem.DEVICE_OUT_DEFAULT, caller, + true /*hasModifyAudioSettings*/); + } + if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) { + mStreamStates[stream].mute(isMuted()); + } + } } - if (isValidLegacyStreamType()) { - int defaultStreamIndex = (mStreamStates[mLegacyStreamType] - .getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10; - if (defaultStreamIndex == index) { - return; + if (!synced) { + if (DEBUG_VOL) { + Log.d(TAG, "applyAllVolumes: apply default device index " + index + + ", group " + mAudioVolumeGroup.name()); + } + setVolumeIndexInt( + isMuted() ? 0 : index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/); + } + if (forceDeviceSync) { + if (DEBUG_VOL) { + Log.d(TAG, "applyAllVolumes: forceDeviceSync index " + index + + ", device " + AudioSystem.getOutputDeviceName(deviceForVolume) + + ", group " + mAudioVolumeGroup.name()); } + setVolumeIndexInt(isMuted() ? 0 : index, deviceForVolume, 0); } - setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/); } } + public void clearIndexCache() { + mIndexMap.clear(); + } + private void persistVolumeGroup(int device) { if (mUseFixedVolume) { return; @@ -7553,21 +7818,19 @@ public class AudioService extends IAudioService.Stub Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group " + mAudioVolumeGroup.name() + ", device " + AudioSystem.getOutputDeviceName(device) - + " and User=" + ActivityManager.getCurrentUser()); + + " and User=" + getCurrentUserId()); } boolean success = mSettings.putSystemIntForUser(mContentResolver, getSettingNameForDevice(device), getIndex(device), - UserHandle.USER_CURRENT); + isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT); if (!success) { Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name()); } } public void readSettings() { - synchronized (VolumeGroupState.class) { - // First clear previously loaded (previous user?) settings - mIndexMap.clear(); + synchronized (VolumeStreamState.class) { // force maximum volume on all streams if fixed volume property is set if (mUseFixedVolume) { mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); @@ -7582,7 +7845,8 @@ public class AudioService extends IAudioService.Stub int index; String name = getSettingNameForDevice(device); index = mSettings.getSystemIntForUser( - mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); + mContentResolver, name, defaultIndex, + isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT); if (index == -1) { continue; } @@ -7593,13 +7857,14 @@ public class AudioService extends IAudioService.Stub if (DEBUG_VOL) { Log.v(TAG, "readSettings: found stored index " + getValidIndex(index) + " for group " + mAudioVolumeGroup.name() + ", device: " + name - + ", User=" + ActivityManager.getCurrentUser()); + + ", User=" + getCurrentUserId()); } mIndexMap.put(device, getValidIndex(index)); } } } + @GuardedBy("AudioService.VolumeStreamState.class") private int getValidIndex(int index) { if (index < mIndexMin) { return mIndexMin; @@ -7610,15 +7875,17 @@ public class AudioService extends IAudioService.Stub } public @NonNull String getSettingNameForDevice(int device) { - final String suffix = AudioSystem.getOutputDeviceName(device); + String suffix = AudioSystem.getOutputDeviceName(device); if (suffix.isEmpty()) { - return mAudioVolumeGroup.name(); + return mSettingName; } - return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device); + return mSettingName + "_" + AudioSystem.getOutputDeviceName(device); } private void dump(PrintWriter pw) { pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":"); + pw.print(" Muted: "); + pw.println(mIsMuted); pw.print(" Min: "); pw.println(mIndexMin); pw.print(" Max: "); @@ -7628,9 +7895,9 @@ public class AudioService extends IAudioService.Stub if (i > 0) { pw.print(", "); } - final int device = mIndexMap.keyAt(i); + int device = mIndexMap.keyAt(i); pw.print(Integer.toHexString(device)); - final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default" + String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default" : AudioSystem.getOutputDeviceName(device); if (!deviceName.isEmpty()) { pw.print(" ("); @@ -7643,7 +7910,7 @@ public class AudioService extends IAudioService.Stub pw.println(); pw.print(" Devices: "); int n = 0; - final int devices = getDeviceForVolume(); + int devices = getDeviceForVolume(); for (int device : AudioSystem.DEVICE_OUT_ALL_SET) { if ((devices & device) == device) { if (n++ > 0) { @@ -7652,6 +7919,10 @@ public class AudioService extends IAudioService.Stub pw.print(AudioSystem.getOutputDeviceName(device)); } } + pw.println(); + pw.print(" Streams: "); + Arrays.stream(getLegacyStreamTypes()) + .forEach(stream -> pw.print(AudioSystem.streamToString(stream) + " ")); } } @@ -7663,13 +7934,14 @@ public class AudioService extends IAudioService.Stub // 4 VolumeStreamState.class private class VolumeStreamState { private final int mStreamType; + private VolumeGroupState mVolumeGroupState = null; private int mIndexMin; // min index when user doesn't have permission to change audio settings private int mIndexMinNoPerm; private int mIndexMax; - private boolean mIsMuted; - private boolean mIsMutedInternally; + private boolean mIsMuted = false; + private boolean mIsMutedInternally = false; private String mVolumeIndexSettingName; @NonNull private Set<Integer> mObservedDeviceSet = new TreeSet<>(); @@ -7727,6 +7999,15 @@ public class AudioService extends IAudioService.Stub } /** + * Associate a {@link volumeGroupState} on the {@link VolumeStreamState}. + * <p> It helps to synchronize the index, mute attributes on the maching + * {@link volumeGroupState} + * @param volumeGroupState matching the {@link VolumeStreamState} + */ + public void setVolumeGroupState(VolumeGroupState volumeGroupState) { + mVolumeGroupState = volumeGroupState; + } + /** * Update the minimum index that can be used without MODIFY_AUDIO_SETTINGS permission * @param index minimum index expressed in "UI units", i.e. no 10x factor */ @@ -7984,6 +8265,9 @@ public class AudioService extends IAudioService.Stub } } if (changed) { + // If associated to volume group, update group cache + updateVolumeGroupIndex(device, /* forceMuteState= */ false); + oldIndex = (oldIndex + 5) / 10; index = (index + 5) / 10; // log base stream changes to the event log @@ -8086,6 +8370,28 @@ public class AudioService extends IAudioService.Stub } } + // If associated to volume group, update group cache + private void updateVolumeGroupIndex(int device, boolean forceMuteState) { + synchronized (VolumeStreamState.class) { + if (mVolumeGroupState != null) { + int groupIndex = (getIndex(device) + 5) / 10; + if (DEBUG_VOL) { + Log.d(TAG, "updateVolumeGroupIndex for stream " + mStreamType + + ", muted=" + mIsMuted + ", device=" + device + ", index=" + + getIndex(device) + ", group " + mVolumeGroupState.name() + + " Muted=" + mVolumeGroupState.isMuted() + ", Index=" + groupIndex + + ", forceMuteState=" + forceMuteState); + } + mVolumeGroupState.updateVolumeIndex(groupIndex, device); + // Only propage mute of stream when applicable + if (mIndexMin == 0 || isCallStream(mStreamType)) { + // For call stream, align mute only when muted, not when index is set to 0 + mVolumeGroupState.mute(forceMuteState ? mIsMuted : groupIndex == 0); + } + } + } + } + /** * Mute/unmute the stream * @param state the new mute state @@ -8094,27 +8400,10 @@ public class AudioService extends IAudioService.Stub public boolean mute(boolean state) { boolean changed = false; synchronized (VolumeStreamState.class) { - if (state != mIsMuted) { - changed = true; - mIsMuted = state; - - // Set the new mute volume. This propagates the values to - // the audio system, otherwise the volume won't be changed - // at the lower level. - sendMsg(mAudioHandler, - MSG_SET_ALL_VOLUMES, - SENDMSG_QUEUE, - 0, - 0, - this, 0); - } + changed = mute(state, true); } if (changed) { - // Stream mute changed, fire the intent. - Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); - intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); - intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state); - sendBroadcastToAll(intent); + broadcastMuteSetting(mStreamType, state); } return changed; } @@ -8146,6 +8435,44 @@ public class AudioService extends IAudioService.Stub return mIsMuted || mIsMutedInternally; } + /** + * Mute/unmute the stream + * @param state the new mute state + * @param apply true to propagate to HW, or false just to update the cache. May be needed + * to mute a stream and its aliases as applyAllVolume will force settings to aliases. + * It prevents unnecessary calls to {@see AudioSystem#setStreamVolume} + * @return true if the mute state was changed + */ + public boolean mute(boolean state, boolean apply) { + synchronized (VolumeStreamState.class) { + boolean changed = state != mIsMuted; + if (changed) { + mIsMuted = state; + if (apply) { + doMute(); + } + } + return changed; + } + } + + public void doMute() { + synchronized (VolumeStreamState.class) { + // If associated to volume group, update group cache + updateVolumeGroupIndex(getDeviceForStream(mStreamType), /* forceMuteState= */ true); + + // Set the new mute volume. This propagates the values to + // the audio system, otherwise the volume won't be changed + // at the lower level. + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + this, 0); + } + } + public int getStreamType() { return mStreamType; } @@ -8215,6 +8542,9 @@ public class AudioService extends IAudioService.Stub pw.println(); pw.print(" Devices: "); pw.print(AudioSystem.deviceSetToString(getDeviceSetForStream(mStreamType))); + pw.println(); + pw.print(" Volume Group: "); + pw.println(mVolumeGroupState != null ? mVolumeGroupState.name() : "n/a"); } } @@ -9881,7 +10211,7 @@ public class AudioService extends IAudioService.Stub private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000; private static final String ACTION_CHECK_MUSIC_ACTIVE = - AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE"; + "com.android.server.audio.action.CHECK_MUSIC_ACTIVE"; private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1; private int safeMediaVolumeIndex(int device) { @@ -9957,7 +10287,8 @@ public class AudioService extends IAudioService.Stub mPendingVolumeCommand.mIndex, mPendingVolumeCommand.mFlags, mPendingVolumeCommand.mDevice, - callingPackage, true /*hasModifyAudioSettings*/); + callingPackage, true /*hasModifyAudioSettings*/, + true /*canChangeMute*/); mPendingVolumeCommand = null; } } diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index c2c3f028abdb..6cbe03ed87d3 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -17,7 +17,6 @@ package com.android.server.audio; import android.annotation.NonNull; -import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.AudioSystem; @@ -221,6 +220,7 @@ public class AudioServiceEvents { static final int VOL_SET_GROUP_VOL = 8; static final int VOL_MUTE_STREAM_INT = 9; static final int VOL_SET_LE_AUDIO_VOL = 10; + static final int VOL_ADJUST_GROUP_VOL = 11; final int mOp; final int mStream; @@ -228,7 +228,6 @@ public class AudioServiceEvents { final int mVal2; final String mCaller; final String mGroupName; - final AudioAttributes mAudioAttributes; /** used for VOL_ADJUST_VOL_UID, * VOL_ADJUST_SUGG_VOL, @@ -241,7 +240,6 @@ public class AudioServiceEvents { mVal2 = val2; mCaller = caller; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -254,7 +252,6 @@ public class AudioServiceEvents { mStream = -1; mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -267,7 +264,6 @@ public class AudioServiceEvents { mStream = -1; mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -280,7 +276,6 @@ public class AudioServiceEvents { // unused mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -293,19 +288,18 @@ public class AudioServiceEvents { // unused mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } - /** used for VOL_SET_GROUP_VOL */ - VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) { + /** used for VOL_SET_GROUP_VOL, + * VOL_ADJUST_GROUP_VOL */ + VolumeEvent(int op, String group, int index, int flags, String caller) { mOp = op; mStream = -1; mVal1 = index; mVal2 = flags; mCaller = caller; mGroupName = group; - mAudioAttributes = aa; logMetricEvent(); } @@ -317,7 +311,6 @@ public class AudioServiceEvents { mVal2 = 0; mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -359,6 +352,15 @@ public class AudioServiceEvents { .record(); return; } + case VOL_ADJUST_GROUP_VOL: + new MediaMetrics.Item(mMetricsId) + .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller) + .set(MediaMetrics.Property.DIRECTION, mVal1 > 0 ? "up" : "down") + .set(MediaMetrics.Property.EVENT, "adjustVolumeGroupVolume") + .set(MediaMetrics.Property.FLAGS, mVal2) + .set(MediaMetrics.Property.GROUP, mGroupName) + .record(); + return; case VOL_SET_STREAM_VOL: new MediaMetrics.Item(mMetricsId) .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller) @@ -410,7 +412,6 @@ public class AudioServiceEvents { return; case VOL_SET_GROUP_VOL: new MediaMetrics.Item(mMetricsId) - .set(MediaMetrics.Property.ATTRIBUTES, mAudioAttributes.toString()) .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller) .set(MediaMetrics.Property.EVENT, "setVolumeIndexForAttributes") .set(MediaMetrics.Property.FLAGS, mVal2) @@ -436,6 +437,13 @@ public class AudioServiceEvents { .append(" flags:0x").append(Integer.toHexString(mVal2)) .append(") from ").append(mCaller) .toString(); + case VOL_ADJUST_GROUP_VOL: + return new StringBuilder("adjustVolumeGroupVolume(group:") + .append(mGroupName) + .append(" dir:").append(AudioManager.adjustToString(mVal1)) + .append(" flags:0x").append(Integer.toHexString(mVal2)) + .append(") from ").append(mCaller) + .toString(); case VOL_ADJUST_STREAM_VOL: return new StringBuilder("adjustStreamVolume(stream:") .append(AudioSystem.streamToString(mStream)) @@ -484,8 +492,7 @@ public class AudioServiceEvents { .append(" stream:").append(AudioSystem.streamToString(mStream)) .toString(); case VOL_SET_GROUP_VOL: - return new StringBuilder("setVolumeIndexForAttributes(attr:") - .append(mAudioAttributes.toString()) + return new StringBuilder("setVolumeIndexForAttributes(group:") .append(" group: ").append(mGroupName) .append(" index:").append(mVal1) .append(" flags:0x").append(Integer.toHexString(mVal2)) diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 258837116cd6..a17b4bfceaca 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -29,8 +29,12 @@ import android.util.Pair; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -60,8 +64,12 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, private String[] mMethodNames = {"getDevicesForAttributes"}; private static final boolean USE_CACHE_FOR_GETDEVICES = true; + private static final Object sDeviceCacheLock = new Object(); + @GuardedBy("sDeviceCacheLock") private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>> mDevicesForAttrCache; + @GuardedBy("sDeviceCacheLock") + private long mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis(); private int[] mMethodCacheHit; private static final Object sRoutingListenerLock = new Object(); @GuardedBy("sRoutingListenerLock") @@ -147,9 +155,11 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, AudioSystem.setRoutingCallback(sSingletonDefaultAdapter); AudioSystem.setVolumeRangeInitRequestCallback(sSingletonDefaultAdapter); if (USE_CACHE_FOR_GETDEVICES) { - sSingletonDefaultAdapter.mDevicesForAttrCache = - new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes()); - sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS]; + synchronized (sDeviceCacheLock) { + sSingletonDefaultAdapter.mDevicesForAttrCache = + new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes()); + sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS]; + } } if (ENABLE_GETDEVICES_STATS) { sSingletonDefaultAdapter.mMethodCallCounter = new int[NB_MEASUREMENTS]; @@ -163,8 +173,9 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, if (DEBUG_CACHE) { Log.d(TAG, "---- clearing cache ----------"); } - if (mDevicesForAttrCache != null) { - synchronized (mDevicesForAttrCache) { + synchronized (sDeviceCacheLock) { + if (mDevicesForAttrCache != null) { + mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis(); mDevicesForAttrCache.clear(); } } @@ -193,7 +204,7 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, if (USE_CACHE_FOR_GETDEVICES) { ArrayList<AudioDeviceAttributes> res; final Pair<AudioAttributes, Boolean> key = new Pair(attributes, forVolume); - synchronized (mDevicesForAttrCache) { + synchronized (sDeviceCacheLock) { res = mDevicesForAttrCache.get(key); if (res == null) { // result from AudioSystem guaranteed non-null, but could be invalid @@ -508,23 +519,31 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, */ public void dump(PrintWriter pw) { pw.println("\nAudioSystemAdapter:"); - pw.println(" mDevicesForAttrCache:"); - if (mDevicesForAttrCache != null) { - for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>> - entry : mDevicesForAttrCache.entrySet()) { - final AudioAttributes attributes = entry.getKey().first; - try { - final int stream = attributes.getVolumeControlStream(); - pw.println("\t" + attributes + " forVolume: " + entry.getKey().second - + " stream: " - + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")"); - for (AudioDeviceAttributes devAttr : entry.getValue()) { - pw.println("\t\t" + devAttr); + final DateTimeFormatter formatter = DateTimeFormatter + .ofPattern("MM-dd HH:mm:ss:SSS") + .withLocale(Locale.US) + .withZone(ZoneId.systemDefault()); + synchronized (sDeviceCacheLock) { + pw.println(" last cache clear time: " + formatter.format( + Instant.ofEpochMilli(mDevicesForAttributesCacheClearTimeMs))); + pw.println(" mDevicesForAttrCache:"); + if (mDevicesForAttrCache != null) { + for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>> + entry : mDevicesForAttrCache.entrySet()) { + final AudioAttributes attributes = entry.getKey().first; + try { + final int stream = attributes.getVolumeControlStream(); + pw.println("\t" + attributes + " forVolume: " + entry.getKey().second + + " stream: " + + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")"); + for (AudioDeviceAttributes devAttr : entry.getValue()) { + pw.println("\t\t" + devAttr); + } + } catch (IllegalArgumentException e) { + // dump could fail if attributes do not map to a stream. + pw.println("\t dump failed for attributes: " + attributes); + Log.e(TAG, "dump failed", e); } - } catch (IllegalArgumentException e) { - // dump could fail if attributes do not map to a stream. - pw.println("\t dump failed for attributes: " + attributes); - Log.e(TAG, "dump failed", e); } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 5cfe65baeb9d..4341634fb890 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -258,7 +258,7 @@ public final class DisplayManagerService extends SystemService { mDisplayWindowPolicyControllers = new SparseArray<>(); /** - * Map of every internal primary display device {@link HighBrightnessModeMetadata}s indexed by + * Map of every display device {@link HighBrightnessModeMetadata}s indexed by * {@link DisplayDevice#mUniqueId}. */ public final ArrayMap<String, HighBrightnessModeMetadata> mHighBrightnessModeMetadataMap = @@ -1525,6 +1525,7 @@ public final class DisplayManagerService extends SystemService { final int displayId = display.getDisplayIdLocked(); final boolean isDefault = displayId == Display.DEFAULT_DISPLAY; configureColorModeLocked(display, device); + if (!mAreUserDisabledHdrTypesAllowed) { display.setUserDisabledHdrTypes(mUserDisabledHdrTypes); } @@ -2636,7 +2637,8 @@ public final class DisplayManagerService extends SystemService { mLogicalDisplayMapper.forEachLocked(this::addDisplayPowerControllerLocked); } - private HighBrightnessModeMetadata getHighBrightnessModeMetadata(LogicalDisplay display) { + @VisibleForTesting + HighBrightnessModeMetadata getHighBrightnessModeMetadata(LogicalDisplay display) { final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); if (device == null) { Slog.wtf(TAG, "Display Device is null in DisplayPowerController for display: " @@ -2644,11 +2646,6 @@ public final class DisplayManagerService extends SystemService { return null; } - // HBM brightness mode is only applicable to internal physical displays. - if (display.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) { - return null; - } - final String uniqueId = device.getUniqueId(); if (mHighBrightnessModeMetadataMap.containsKey(uniqueId)) { @@ -2673,7 +2670,7 @@ public final class DisplayManagerService extends SystemService { final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore, display, mSyncRoot); - // If display is internal and has a HighBrightnessModeMetadata mapping, use that. + // If display already has a HighBrightnessModeMetadata mapping, use that. // Or create a new one and use that. // We also need to pass a mapping of the HighBrightnessModeTimeInfoMap to // displayPowerController, so the hbm info can be correctly associated diff --git a/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java b/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java index 37234ff0bf19..8aa36318924c 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java @@ -21,10 +21,9 @@ import java.util.ArrayDeque; /** * Represents High Brightness Mode metadata associated - * with a specific internal physical display. + * with a specific display. * Required for separately storing data like time information, - * and related events when display was in HBM mode per - * physical internal display. + * and related events when display was in HBM mode per display. */ class HighBrightnessModeMetadata { /** 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 017b96cc5f67..22767157d1d9 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -107,11 +107,6 @@ public final class ColorDisplayService extends SystemService { Matrix.setIdentityM(MATRIX_IDENTITY, 0); } - /** - * The transition time, in milliseconds, for Night Display to turn on/off. - */ - private static final long TRANSITION_DURATION = 3000L; - private static final int MSG_USER_CHANGED = 0; private static final int MSG_SET_UP = 1; private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 2; @@ -661,7 +656,7 @@ public final class ColorDisplayService extends SystemService { TintValueAnimator valueAnimator = TintValueAnimator.ofMatrix(COLOR_MATRIX_EVALUATOR, from == null ? MATRIX_IDENTITY : from, to); tintController.setAnimator(valueAnimator); - valueAnimator.setDuration(TRANSITION_DURATION); + valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds()); valueAnimator.setInterpolator(AnimationUtils.loadInterpolator( getContext(), android.R.interpolator.fast_out_slow_in)); valueAnimator.addUpdateListener((ValueAnimator animator) -> { diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java index 93a78c1507ad..f27ccc723596 100644 --- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java +++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java @@ -59,7 +59,8 @@ final class DisplayWhiteBalanceTintController extends TintController { private float[] mCurrentColorTemperatureXYZ; @VisibleForTesting boolean mSetUp = false; - private float[] mMatrixDisplayWhiteBalance = new float[16]; + private final float[] mMatrixDisplayWhiteBalance = new float[16]; + private long mTransitionDuration; private Boolean mIsAvailable; // This feature becomes disallowed if the device is in an unsupported strong/light state. private boolean mIsAllowed = true; @@ -119,6 +120,9 @@ final class DisplayWhiteBalanceTintController extends TintController { final int colorTemperature = res.getInteger( R.integer.config_displayWhiteBalanceColorTemperatureDefault); + mTransitionDuration = res.getInteger( + R.integer.config_displayWhiteBalanceTransitionTime); + synchronized (mLock) { mDisplayColorSpaceRGB = displayColorSpaceRGB; mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ; @@ -232,6 +236,11 @@ final class DisplayWhiteBalanceTintController extends TintController { } @Override + public long getTransitionDurationMilliseconds() { + return mTransitionDuration; + } + + @Override public void dump(PrintWriter pw) { synchronized (mLock) { pw.println(" mSetUp = " + mSetUp); diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java index 422dd328d2b6..c53ac06c66e1 100644 --- a/services/core/java/com/android/server/display/color/TintController.java +++ b/services/core/java/com/android/server/display/color/TintController.java @@ -16,7 +16,6 @@ package com.android.server.display.color; -import android.animation.ValueAnimator; import android.content.Context; import android.util.Slog; @@ -24,6 +23,11 @@ import java.io.PrintWriter; abstract class TintController { + /** + * The default transition time, in milliseconds, for color transforms to turn on/off. + */ + private static final long TRANSITION_DURATION = 3000L; + private ColorDisplayService.TintValueAnimator mAnimator; private Boolean mIsActivated; @@ -66,6 +70,10 @@ abstract class TintController { return mIsActivated == null; } + public long getTransitionDurationMilliseconds() { + return TRANSITION_DURATION; + } + /** * Dump debug information. */ diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index db9deb1ceb69..f87a1461f9d2 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -244,7 +244,10 @@ final class DreamController { } mListener.onDreamStopped(dream.mToken); + } else if (dream.mCanDoze && !mCurrentDream.mCanDoze) { + mListener.stopDozing(dream.mToken); } + } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } @@ -289,6 +292,7 @@ final class DreamController { */ public interface Listener { void onDreamStopped(Binder token); + void stopDozing(Binder token); } private final class DreamRecord implements DeathRecipient, ServiceConnection { diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index bb1e3931c02e..148b80ea0447 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -499,7 +499,12 @@ public final class DreamManagerService extends SystemService { } synchronized (mLock) { - if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.isDozing) { + if (mCurrentDream == null) { + return; + } + + final boolean sameDream = mCurrentDream.token == token; + if ((sameDream && mCurrentDream.isDozing) || (!sameDream && !mCurrentDream.isDozing)) { mCurrentDream.isDozing = false; mDozeWakeLock.release(); mPowerManagerInternal.setDozeOverrideFromDreamManager( @@ -765,6 +770,11 @@ public final class DreamManagerService extends SystemService { } } } + + @Override + public void stopDozing(Binder token) { + stopDozingInternal(token); + } }; private final ContentObserver mDozeEnabledObserver = new ContentObserver(null) { diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 4d55d4e545ee..25fefad8042f 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -132,6 +132,7 @@ abstract public class ManagedServices { // contains connections to all connected services, including app services // and system services + @GuardedBy("mMutex") private final ArrayList<ManagedServiceInfo> mServices = new ArrayList<>(); /** * The services that have been bound by us. If the service is also connected, it will also @@ -150,13 +151,15 @@ abstract public class ManagedServices { = new ArraySet<>(); // Just the packages from mEnabledServicesForCurrentProfiles private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>(); - // List of enabled packages that have nevertheless asked not to be run - private ArraySet<ComponentName> mSnoozingForCurrentProfiles = new ArraySet<>(); + // Per user id, list of enabled packages that have nevertheless asked not to be run + private final android.util.SparseSetArray<ComponentName> mSnoozing = + new android.util.SparseSetArray<>(); // List of approved packages or components (by user, then by primary/secondary) that are // allowed to be bound as managed services. A package or component appearing in this list does // not mean that we are currently bound to said package/component. - protected ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved = new ArrayMap<>(); + protected final ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved = + new ArrayMap<>(); // List of packages or components (by user) that are configured to be enabled/disabled // explicitly by the user @@ -315,6 +318,7 @@ abstract public class ManagedServices { return changes; } + @GuardedBy("mApproved") private boolean clearUserSetFlagLocked(ComponentName component, int userId) { String approvedValue = getApprovedValue(component.flattenToString()); ArraySet<String> userSet = mUserSetServices.get(userId); @@ -375,8 +379,8 @@ abstract public class ManagedServices { pw.println(" " + cmpt); } - pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):"); synchronized (mMutex) { + pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):"); for (ManagedServiceInfo info : mServices) { if (filter != null && !filter.matches(info.component)) continue; pw.println(" " + info.component @@ -386,10 +390,15 @@ abstract public class ManagedServices { } } - pw.println(" Snoozed " + getCaption() + "s (" + - mSnoozingForCurrentProfiles.size() + "):"); - for (ComponentName name : mSnoozingForCurrentProfiles) { - pw.println(" " + name.flattenToShortString()); + synchronized (mSnoozing) { + pw.println(" Snoozed " + getCaption() + "s (" + + mSnoozing.size() + "):"); + for (int i = 0; i < mSnoozing.size(); i++) { + pw.println(" User: " + mSnoozing.keyAt(i)); + for (ComponentName name : mSnoozing.valuesAt(i)) { + pw.println(" " + name.flattenToShortString()); + } + } } } @@ -431,8 +440,16 @@ abstract public class ManagedServices { } } - for (ComponentName name : mSnoozingForCurrentProfiles) { - name.dumpDebug(proto, ManagedServicesProto.SNOOZED); + synchronized (mSnoozing) { + for (int i = 0; i < mSnoozing.size(); i++) { + long token = proto.start(ManagedServicesProto.SNOOZED); + proto.write(ManagedServicesProto.SnoozedServices.USER_ID, + mSnoozing.keyAt(i)); + for (ComponentName name : mSnoozing.valuesAt(i)) { + name.dumpDebug(proto, ManagedServicesProto.SnoozedServices.SNOOZED); + } + proto.end(token); + } } } @@ -975,6 +992,9 @@ abstract public class ManagedServices { synchronized (mApproved) { mApproved.remove(user); } + synchronized (mSnoozing) { + mSnoozing.remove(user); + } rebindServices(true, user); } @@ -994,10 +1014,12 @@ abstract public class ManagedServices { return null; } final IBinder token = service.asBinder(); - final int N = mServices.size(); - for (int i = 0; i < N; i++) { - final ManagedServiceInfo info = mServices.get(i); - if (info.service.asBinder() == token) return info; + synchronized (mMutex) { + final int nServices = mServices.size(); + for (int i = 0; i < nServices; i++) { + final ManagedServiceInfo info = mServices.get(i); + if (info.service.asBinder() == token) return info; + } } return null; } @@ -1066,15 +1088,17 @@ abstract public class ManagedServices { } protected void setComponentState(ComponentName component, int userId, boolean enabled) { - boolean previous = !mSnoozingForCurrentProfiles.contains(component); - if (previous == enabled) { - return; - } + synchronized (mSnoozing) { + boolean previous = !mSnoozing.contains(userId, component); + if (previous == enabled) { + return; + } - if (enabled) { - mSnoozingForCurrentProfiles.remove(component); - } else { - mSnoozingForCurrentProfiles.add(component); + if (enabled) { + mSnoozing.remove(userId, component); + } else { + mSnoozing.add(userId, component); + } } // State changed @@ -1287,7 +1311,10 @@ abstract public class ManagedServices { } final Set<ComponentName> add = new HashSet<>(userComponents); - add.removeAll(mSnoozingForCurrentProfiles); + ArraySet<ComponentName> snoozed = mSnoozing.get(userId); + if (snoozed != null) { + add.removeAll(snoozed); + } componentsToBind.put(userId, add); @@ -1466,10 +1493,12 @@ abstract public class ManagedServices { } } + @GuardedBy("mMutex") private void registerServiceLocked(final ComponentName name, final int userid) { registerServiceLocked(name, userid, false /* isSystem */); } + @GuardedBy("mMutex") private void registerServiceLocked(final ComponentName name, final int userid, final boolean isSystem) { if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid); @@ -1600,6 +1629,7 @@ abstract public class ManagedServices { } } + @GuardedBy("mMutex") private void unregisterServiceLocked(ComponentName name, int userid) { final int N = mServices.size(); for (int i = N - 1; i >= 0; i--) { @@ -1634,6 +1664,7 @@ abstract public class ManagedServices { return serviceInfo; } + @GuardedBy("mMutex") private ManagedServiceInfo removeServiceLocked(int i) { final ManagedServiceInfo info = mServices.remove(i); onServiceRemovedLocked(info); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index b380d84c7c5c..d24954743a63 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -975,12 +975,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior); } else if (count > 3 && count <= getMaxMultiPressPowerCount()) { Slog.d(TAG, "No behavior defined for power press count " + count); - } else if (count == 1 && interactive) { - if (beganFromNonInteractive) { - // The screen off case, where we might want to start dreaming on power button press. - attemptToDreamFromShortPowerButtonPress(false, () -> {}); - return; - } + } else if (count == 1 && interactive && !beganFromNonInteractive) { if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) { Slog.i(TAG, "Suppressing power key because the user is interacting with the " + "fingerprint sensor"); diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 725254513519..7489f80946eb 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -709,7 +709,8 @@ class ActivityClientController extends IActivityClientController.Stub { synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); return r != null - ? r.getRequestedOrientation() : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + ? r.getOverrideOrientation() + : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 6b01a7726a43..2ebf8d9ee388 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -439,6 +439,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // finished destroying itself. private static final int DESTROY_TIMEOUT = 10 * 1000; + // Rounding tolerance to be used in aspect ratio computations + private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f; + final ActivityTaskManagerService mAtmService; @NonNull final ActivityInfo info; // activity info provided by developer in AndroidManifest @@ -1165,8 +1168,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + "mVoiceInteraction=true"); } pw.print(prefix); pw.print("mOccludesParent="); pw.println(mOccludesParent); - pw.print(prefix); pw.print("mOrientation="); - pw.println(ActivityInfo.screenOrientationToString(mOrientation)); + pw.print(prefix); pw.print("overrideOrientation="); + pw.println(ActivityInfo.screenOrientationToString(getOverrideOrientation())); + pw.print(prefix); pw.print("requestedOrientation="); + pw.println(ActivityInfo.screenOrientationToString(super.getOverrideOrientation())); pw.println(prefix + "mVisibleRequested=" + mVisibleRequested + " mVisible=" + mVisible + " mClientVisible=" + isClientVisible() + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "") @@ -1964,6 +1969,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A new ComponentName(info.packageName, info.targetActivity); } + // Don't move below setActivityType since it triggers onConfigurationChange -> + // resolveOverrideConfiguration that requires having mLetterboxUiController initialised. + // Don't move below setOrientation(info.screenOrientation) since it triggers + // getOverrideOrientation that requires having mLetterboxUiController + // initialised. + mLetterboxUiController = new LetterboxUiController(mWmService, this); + mCameraCompatControlEnabled = mWmService.mContext.getResources() + .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled); + mTargetSdk = info.applicationInfo.targetSdkVersion; mShowForAllUsers = (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0; setOrientation(info.screenOrientation); @@ -2084,12 +2098,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A launchMode = aInfo.launchMode; - // Don't move below setActivityType since it triggers onConfigurationChange -> - // resolveOverrideConfiguration that requires having mLetterboxUiController initialised. - mLetterboxUiController = new LetterboxUiController(mWmService, this); - mCameraCompatControlEnabled = mWmService.mContext.getResources() - .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled); - setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord); immersive = (aInfo.flags & FLAG_IMMERSIVE) != 0; @@ -2486,7 +2494,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (topAttached != null) { if (topAttached.isSnapshotCompatible(snapshot) // This trampoline must be the same rotation. - && mDisplayContent.getDisplayRotation().rotationForOrientation(mOrientation, + && mDisplayContent.getDisplayRotation().rotationForOrientation( + getOverrideOrientation(), mDisplayContent.getRotation()) == snapshot.getRotation()) { return STARTING_WINDOW_TYPE_SNAPSHOT; } @@ -7704,13 +7713,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mLetterboxUiController.getInheritedOrientation(); } } - if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) { + if (task != null && getOverrideOrientation() == SCREEN_ORIENTATION_BEHIND) { // We use Task here because we want to be consistent with what happens in // multi-window mode where other tasks orientations are ignored. final ActivityRecord belowCandidate = task.getActivity( - a -> a.mOrientation != SCREEN_ORIENTATION_UNSET && !a.finishing - && a.mOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND, this, - false /* includeBoundary */, true /* traverseTopToBottom */); + a -> a.canDefineOrientationForActivitiesAbove() /* callback */, + this /* boundary */, false /* includeBoundary */, + true /* traverseTopToBottom */); if (belowCandidate != null) { return belowCandidate.getRequestedConfigurationOrientation(forDisplay); } @@ -7718,6 +7727,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return super.getRequestedConfigurationOrientation(forDisplay); } + /** + * Whether this activity can be used as an orientation source for activities above with + * {@link SCREEN_ORIENTATION_BEHIND}. + */ + boolean canDefineOrientationForActivitiesAbove() { + if (finishing) { + return false; + } + final int overrideOrientation = getOverrideOrientation(); + return overrideOrientation != SCREEN_ORIENTATION_UNSET + && overrideOrientation != SCREEN_ORIENTATION_BEHIND; + } + @Override void onCancelFixedRotationTransform(int originalDisplayRotation) { if (this != mDisplayContent.getLastOrientationSource()) { @@ -7744,7 +7766,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - void setRequestedOrientation(int requestedOrientation) { + void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) { if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) { return; } @@ -7789,7 +7811,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Allow app to specify orientation regardless of its visibility state if the current // candidate want us to use orientation behind. I.e. the visible app on-top of this one // wants us to use the orientation of the app behind it. - return mOrientation; + return getOverrideOrientation(); } // The {@link ActivityRecord} should only specify an orientation when it is not closing. @@ -7797,15 +7819,31 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // task being started in the wrong orientation during the transition. if (!getDisplayContent().mClosingApps.contains(this) && (isVisibleRequested() || getDisplayContent().mOpeningApps.contains(this))) { - return mOrientation; + return getOverrideOrientation(); } return SCREEN_ORIENTATION_UNSET; } - /** Returns the app's preferred orientation regardless of its currently visibility state. */ + /** + * Returns the app's preferred orientation regardless of its current visibility state taking + * into account orientation per-app overrides applied by the device manufacturers. + */ + @Override + protected int getOverrideOrientation() { + return mLetterboxUiController.overrideOrientationIfNeeded(super.getOverrideOrientation()); + } + + /** + * Returns the app's preferred orientation regardless of its currently visibility state. This + * is used to return a requested value to an app if they call {@link + * android.app.Activity#getRequestedOrientation} since {@link #getOverrideOrientation} value + * with override can confuse an app if it's different from what they requested with {@link + * android.app.Activity#setRequestedOrientation}. + */ + @ActivityInfo.ScreenOrientation int getRequestedOrientation() { - return mOrientation; + return super.getOverrideOrientation(); } /** @@ -8204,8 +8242,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (screenResolvedBounds.width() <= parentAppBounds.width()) { float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier( newParentConfiguration); - offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width()) - * positionMultiplier); + offsetX = Math.max(0, (int) Math.ceil((parentAppBounds.width() + - screenResolvedBounds.width()) * positionMultiplier) + // This is added to make sure that insets added inside + // CompatDisplayInsets#getContainerBounds() do not break the alignment + // provided by the positionMultiplier + - screenResolvedBounds.left + parentAppBounds.left); } } @@ -8215,8 +8257,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (screenResolvedBounds.height() <= parentAppBounds.height()) { float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier( newParentConfiguration); - offsetY = (int) Math.ceil((parentAppBounds.height() - screenResolvedBounds.height()) - * positionMultiplier); + offsetY = Math.max(0, (int) Math.ceil((parentAppBounds.height() + - screenResolvedBounds.height()) * positionMultiplier) + // This is added to make sure that insets added inside + // CompatDisplayInsets#getContainerBounds() do not break the alignment + // provided by the positionMultiplier + - screenResolvedBounds.top + parentAppBounds.top); } } @@ -8357,8 +8403,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If orientation is respected when insets are applied, then stableBounds will be empty. boolean orientationRespectedWithInsets = orientationRespectedWithInsets(parentBounds, stableBounds); - if (orientationRespectedWithInsets - && handlesOrientationChangeFromDescendant(mOrientation)) { + if (orientationRespectedWithInsets && handlesOrientationChangeFromDescendant( + getOverrideOrientation())) { // No need to letterbox because of fixed orientation. Display will handle // fixed-orientation requests and a display rotation is enough to respect requested // orientation with insets applied. @@ -8915,7 +8961,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A int activityWidth = containingAppWidth; int activityHeight = containingAppHeight; - if (containingRatio > desiredAspectRatio) { + if (containingRatio - desiredAspectRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) { if (containingAppWidth < containingAppHeight) { // Width is the shorter side, so we use that to figure-out what the max. height // should be given the aspect ratio. @@ -8925,7 +8971,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // should be given the aspect ratio. activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f); } - } else if (containingRatio < desiredAspectRatio) { + } else if (desiredAspectRatio - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) { boolean adjustWidth; switch (getRequestedConfigurationOrientation()) { case ORIENTATION_LANDSCAPE: @@ -9003,7 +9049,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY) - && !ActivityInfo.isFixedOrientationPortrait(getRequestedOrientation())) { + && !ActivityInfo.isFixedOrientationPortrait( + getOverrideOrientation())) { return info.getMinAspectRatio(); } @@ -9998,6 +10045,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A isLandscape ? shortSide : longSide); } + // TODO(b/267151420): Explore removing getContainerBounds() from CompatDisplayInsets. /** Gets the horizontal centered container bounds for size compatibility mode. */ void getContainerBounds(Rect outAppBounds, Rect outBounds, int rotation, int orientation, boolean orientationRequested, boolean isFixedToUserRotation) { diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java index a6f855755192..7d9a4ec4b5c3 100644 --- a/services/core/java/com/android/server/wm/DeviceStateController.java +++ b/services/core/java/com/android/server/wm/DeviceStateController.java @@ -16,80 +16,107 @@ package com.android.server.wm; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.os.Handler; import android.os.HandlerExecutor; +import com.android.internal.R; import com.android.internal.util.ArrayUtils; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; /** - * Class that registers callbacks with the {@link DeviceStateManager} and - * responds to fold state changes by forwarding such events to a delegate. + * Class that registers callbacks with the {@link DeviceStateManager} and responds to device + * changes. */ -final class DeviceStateController { +final class DeviceStateController implements DeviceStateManager.DeviceStateCallback { + + @NonNull private final DeviceStateManager mDeviceStateManager; - private final Context mContext; + @NonNull + private final int[] mOpenDeviceStates; + @NonNull + private final int[] mHalfFoldedDeviceStates; + @NonNull + private final int[] mFoldedDeviceStates; + @NonNull + private final int[] mRearDisplayDeviceStates; + @NonNull + private final int[] mReverseRotationAroundZAxisStates; + @NonNull + private final List<Consumer<DeviceState>> mDeviceStateCallbacks = new ArrayList<>(); - private FoldStateListener mDeviceStateListener; + @Nullable + private DeviceState mLastDeviceState; + private int mCurrentState; - public enum FoldState { - UNKNOWN, OPEN, FOLDED, HALF_FOLDED + public enum DeviceState { + UNKNOWN, OPEN, FOLDED, HALF_FOLDED, REAR, } - DeviceStateController(Context context, Handler handler, Consumer<FoldState> delegate) { - mContext = context; - mDeviceStateManager = mContext.getSystemService(DeviceStateManager.class); + DeviceStateController(@NonNull Context context, @NonNull Handler handler) { + mDeviceStateManager = context.getSystemService(DeviceStateManager.class); + + mOpenDeviceStates = context.getResources() + .getIntArray(R.array.config_openDeviceStates); + mHalfFoldedDeviceStates = context.getResources() + .getIntArray(R.array.config_halfFoldedDeviceStates); + mFoldedDeviceStates = context.getResources() + .getIntArray(R.array.config_foldedDeviceStates); + mRearDisplayDeviceStates = context.getResources() + .getIntArray(R.array.config_rearDisplayDeviceStates); + mReverseRotationAroundZAxisStates = context.getResources() + .getIntArray(R.array.config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis); + if (mDeviceStateManager != null) { - mDeviceStateListener = new FoldStateListener(mContext, delegate); - mDeviceStateManager - .registerCallback(new HandlerExecutor(handler), - mDeviceStateListener); + mDeviceStateManager.registerCallback(new HandlerExecutor(handler), this); } } void unregisterFromDeviceStateManager() { - if (mDeviceStateListener != null) { - mDeviceStateManager.unregisterCallback(mDeviceStateListener); + if (mDeviceStateManager != null) { + mDeviceStateManager.unregisterCallback(this); } } + void registerDeviceStateCallback(@NonNull Consumer<DeviceState> callback) { + mDeviceStateCallbacks.add(callback); + } + /** - * A listener for half-fold device state events that dispatches state changes to a delegate. + * @return true if the rotation direction on the Z axis should be reversed. */ - static final class FoldStateListener implements DeviceStateManager.DeviceStateCallback { - - private final int[] mHalfFoldedDeviceStates; - private final int[] mFoldedDeviceStates; + boolean shouldReverseRotationDirectionAroundZAxis() { + return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState); + } - @Nullable - private FoldState mLastResult; - private final Consumer<FoldState> mDelegate; + @Override + public void onStateChanged(int state) { + mCurrentState = state; - FoldStateListener(Context context, Consumer<FoldState> delegate) { - mFoldedDeviceStates = context.getResources().getIntArray( - com.android.internal.R.array.config_foldedDeviceStates); - mHalfFoldedDeviceStates = context.getResources().getIntArray( - com.android.internal.R.array.config_halfFoldedDeviceStates); - mDelegate = delegate; + final DeviceState deviceState; + if (ArrayUtils.contains(mHalfFoldedDeviceStates, state)) { + deviceState = DeviceState.HALF_FOLDED; + } else if (ArrayUtils.contains(mFoldedDeviceStates, state)) { + deviceState = DeviceState.FOLDED; + } else if (ArrayUtils.contains(mRearDisplayDeviceStates, state)) { + deviceState = DeviceState.REAR; + } else if (ArrayUtils.contains(mOpenDeviceStates, state)) { + deviceState = DeviceState.OPEN; + } else { + deviceState = DeviceState.UNKNOWN; } - @Override - public void onStateChanged(int state) { - final boolean halfFolded = ArrayUtils.contains(mHalfFoldedDeviceStates, state); - FoldState result; - if (halfFolded) { - result = FoldState.HALF_FOLDED; - } else { - final boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state); - result = folded ? FoldState.FOLDED : FoldState.OPEN; - } - if (mLastResult == null || !mLastResult.equals(result)) { - mLastResult = result; - mDelegate.accept(result); + if (mLastDeviceState == null || !mLastDeviceState.equals(deviceState)) { + mLastDeviceState = deviceState; + + for (Consumer<DeviceState> callback : mDeviceStateCallbacks) { + callback.accept(mLastDeviceState); } } } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index af5bd14baf31..fad2ddadc88f 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -93,7 +93,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { DisplayArea(WindowManagerService wms, Type type, String name, int featureId) { super(wms); // TODO(display-area): move this up to ConfigurationContainer - mOrientation = SCREEN_ORIENTATION_UNSET; + setOverrideOrientation(SCREEN_ORIENTATION_UNSET); mType = type; mName = name; mFeatureId = featureId; @@ -165,7 +165,8 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { // If this is set to ignore the orientation request, we don't propagate descendant // orientation request. final int orientation = requestingContainer != null - ? requestingContainer.mOrientation : SCREEN_ORIENTATION_UNSET; + ? requestingContainer.getOverrideOrientation() + : SCREEN_ORIENTATION_UNSET; return !getIgnoreOrientationRequest(orientation) && super.onDescendantOrientationChanged(requestingContainer); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 9af1b2bd40c6..aede04bf504b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -27,6 +27,7 @@ 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.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; @@ -1125,14 +1126,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mAtmService.getRecentTasks().getInputListener()); } + mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH); + mDisplayPolicy = new DisplayPolicy(mWmService, this); - mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address); + mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address, + mDeviceStateController); - mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH, - newFoldState -> { + final Consumer<DeviceStateController.DeviceState> deviceStateConsumer = + (@NonNull DeviceStateController.DeviceState newFoldState) -> { mDisplaySwitchTransitionLauncher.foldStateChanged(newFoldState); mDisplayRotation.foldStateChanged(newFoldState); - }); + }; + mDeviceStateController.registerDeviceStateCallback(deviceStateConsumer); mCloseToSquareMaxAspectRatio = mWmService.mContext.getResources().getFloat( R.dimen.config_closeToSquareDisplayMaxAspectRatio); @@ -1572,7 +1577,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // If display rotation class tells us that it doesn't consider app requested orientation, // this display won't rotate just because of an app changes its requested orientation. Thus // it indicates that this display chooses not to handle this request. - final int orientation = requestingContainer != null ? requestingContainer.mOrientation + final int orientation = requestingContainer != null + ? requestingContainer.getOverrideOrientation() : SCREEN_ORIENTATION_UNSET; final boolean handled = handlesOrientationChangeFromDescendant(orientation); if (config == null) { @@ -1698,15 +1704,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (mTransitionController.useShellTransitionsRotation()) { return ROTATION_UNDEFINED; } + final int activityOrientation = r.getOverrideOrientation(); if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM - || getIgnoreOrientationRequest(r.mOrientation)) { + || getIgnoreOrientationRequest(activityOrientation)) { return ROTATION_UNDEFINED; } - if (r.mOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) { + if (activityOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) { final ActivityRecord nextCandidate = getActivity( - a -> a.mOrientation != SCREEN_ORIENTATION_UNSET - && a.mOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND, - r, false /* includeBoundary */, true /* traverseTopToBottom */); + a -> a.canDefineOrientationForActivitiesAbove() /* callback */, + r /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */); if (nextCandidate != null) { r = nextCandidate; } @@ -2723,6 +2729,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final int orientation = super.getOrientation(); if (!handlesOrientationChangeFromDescendant(orientation)) { + ActivityRecord topActivity = topRunningActivity(/* considerKeyguardState= */ true); + if (topActivity != null && topActivity.mLetterboxUiController + .shouldUseDisplayLandscapeNaturalOrientation()) { + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Display id=%d is ignoring orientation request for %d, return %d" + + " following a per-app override for %s", + mDisplayId, orientation, SCREEN_ORIENTATION_LANDSCAPE, topActivity); + return SCREEN_ORIENTATION_LANDSCAPE; + } mLastOrientationSource = null; // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation ProtoLog.v(WM_DEBUG_ORIENTATION, diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index f8fd2b953c20..cf7d5d9548fd 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2007,7 +2007,8 @@ public class DisplayPolicy { dc.getDisplayPolicy().simulateLayoutDisplay(df); final InsetsState insetsState = df.mInsetsState; final Rect displayFrame = insetsState.getDisplayFrame(); - final Insets decor = calculateDecorInsetsWithInternalTypes(insetsState); + final Insets decor = insetsState.calculateInsets(displayFrame, DECOR_TYPES, + true /* ignoreVisibility */); final Insets statusBar = insetsState.calculateInsets(displayFrame, Type.statusBars(), true /* ignoreVisibility */); mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom); @@ -2039,17 +2040,8 @@ public class DisplayPolicy { } } - // TODO (b/235842600): Use public type once we can treat task bar as navigation bar. - static final int[] INTERNAL_DECOR_TYPES; - static { - final ArraySet<Integer> decorTypes = InsetsState.toInternalType( - Type.displayCutout() | Type.navigationBars()); - decorTypes.remove(ITYPE_EXTRA_NAVIGATION_BAR); - INTERNAL_DECOR_TYPES = new int[decorTypes.size()]; - for (int i = 0; i < INTERNAL_DECOR_TYPES.length; i++) { - INTERNAL_DECOR_TYPES[i] = decorTypes.valueAt(i); - } - } + + static final int DECOR_TYPES = Type.displayCutout() | Type.navigationBars(); private final DisplayContent mDisplayContent; private final Info[] mInfoForRotation = new Info[4]; @@ -2076,20 +2068,6 @@ public class DisplayPolicy { info.mNeedUpdate = true; } } - - // TODO (b/235842600): Remove this method once we can treat task bar as navigation bar. - private static Insets calculateDecorInsetsWithInternalTypes(InsetsState state) { - final Rect frame = state.getDisplayFrame(); - Insets insets = Insets.NONE; - for (int i = INTERNAL_DECOR_TYPES.length - 1; i >= 0; i--) { - final InsetsSource source = state.peekSource(INTERNAL_DECOR_TYPES[i]); - if (source != null) { - insets = Insets.max(source.calculateInsets(frame, true /* ignoreVisibility */), - insets); - } - } - return insets; - } } /** diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index e6d8b3db4564..3404279d2c59 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -41,6 +41,7 @@ import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_D import android.annotation.AnimRes; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.content.ContentResolver; @@ -117,6 +118,8 @@ public class DisplayRotation { private SettingsObserver mSettingsObserver; @Nullable private FoldController mFoldController; + @NonNull + private final DeviceStateController mDeviceStateController; @ScreenOrientation private int mCurrentAppOrientation = SCREEN_ORIENTATION_UNSPECIFIED; @@ -218,21 +221,24 @@ public class DisplayRotation { private boolean mDemoRotationLock; DisplayRotation(WindowManagerService service, DisplayContent displayContent, - DisplayAddress displayAddress) { + DisplayAddress displayAddress, @NonNull DeviceStateController deviceStateController) { this(service, displayContent, displayAddress, displayContent.getDisplayPolicy(), - service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock()); + service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock(), + deviceStateController); } @VisibleForTesting DisplayRotation(WindowManagerService service, DisplayContent displayContent, DisplayAddress displayAddress, DisplayPolicy displayPolicy, - DisplayWindowSettings displayWindowSettings, Context context, Object lock) { + DisplayWindowSettings displayWindowSettings, Context context, Object lock, + @NonNull DeviceStateController deviceStateController) { mService = service; mDisplayContent = displayContent; mDisplayPolicy = displayPolicy; mDisplayWindowSettings = displayWindowSettings; mContext = context; mLock = lock; + mDeviceStateController = deviceStateController; isDefaultDisplay = displayContent.isDefaultDisplay; mCompatPolicyForImmersiveApps = initImmersiveAppCompatPolicy(service, displayContent); @@ -243,11 +249,13 @@ public class DisplayRotation { mDeskDockRotation = readRotation(R.integer.config_deskDockRotation); mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation); - mRotation = readDefaultDisplayRotation(displayAddress); + int defaultRotation = readDefaultDisplayRotation(displayAddress); + mRotation = defaultRotation; if (isDefaultDisplay) { final Handler uiHandler = UiThread.getHandler(); - mOrientationListener = new OrientationListener(mContext, uiHandler); + mOrientationListener = + new OrientationListener(mContext, uiHandler, defaultRotation); mOrientationListener.setCurrentRotation(mRotation); mSettingsObserver = new SettingsObserver(uiHandler); mSettingsObserver.observe(); @@ -1137,6 +1145,15 @@ public class DisplayRotation { int sensorRotation = mOrientationListener != null ? mOrientationListener.getProposedRotation() // may be -1 : -1; + if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) { + // Flipping 270 and 90 has the same effect as changing the direction which rotation is + // applied. + if (sensorRotation == Surface.ROTATION_90) { + sensorRotation = Surface.ROTATION_270; + } else if (sensorRotation == Surface.ROTATION_270) { + sensorRotation = Surface.ROTATION_90; + } + } mLastSensorRotation = sensorRotation; if (sensorRotation < 0) { sensorRotation = lastRotation; @@ -1573,7 +1590,7 @@ public class DisplayRotation { proto.end(token); } - boolean isDeviceInPosture(DeviceStateController.FoldState state, boolean isTabletop) { + boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) { if (mFoldController == null) return false; return mFoldController.isDeviceInPosture(state, isTabletop); } @@ -1585,10 +1602,10 @@ public class DisplayRotation { /** * Called by the DeviceStateManager callback when the device state changes. */ - void foldStateChanged(DeviceStateController.FoldState foldState) { + void foldStateChanged(DeviceStateController.DeviceState deviceState) { if (mFoldController != null) { synchronized (mLock) { - mFoldController.foldStateChanged(foldState); + mFoldController.foldStateChanged(deviceState); } } } @@ -1596,8 +1613,8 @@ public class DisplayRotation { private class FoldController { @Surface.Rotation private int mHalfFoldSavedRotation = -1; // No saved rotation - private DeviceStateController.FoldState mFoldState = - DeviceStateController.FoldState.UNKNOWN; + private DeviceStateController.DeviceState mDeviceState = + DeviceStateController.DeviceState.UNKNOWN; private boolean mInHalfFoldTransition = false; private final boolean mIsDisplayAlwaysSeparatingHinge; private final Set<Integer> mTabletopRotations; @@ -1637,32 +1654,33 @@ public class DisplayRotation { R.bool.config_isDisplayHingeAlwaysSeparating); } - boolean isDeviceInPosture(DeviceStateController.FoldState state, boolean isTabletop) { - if (state != mFoldState) { + boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) { + if (state != mDeviceState) { return false; } - if (mFoldState == DeviceStateController.FoldState.HALF_FOLDED) { + if (mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) { return !(isTabletop ^ mTabletopRotations.contains(mRotation)); } return true; } - DeviceStateController.FoldState getFoldState() { - return mFoldState; + DeviceStateController.DeviceState getFoldState() { + return mDeviceState; } boolean isSeparatingHinge() { - return mFoldState == DeviceStateController.FoldState.HALF_FOLDED - || (mFoldState == DeviceStateController.FoldState.OPEN + return mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED + || (mDeviceState == DeviceStateController.DeviceState.OPEN && mIsDisplayAlwaysSeparatingHinge); } boolean overrideFrozenRotation() { - return mFoldState == DeviceStateController.FoldState.HALF_FOLDED; + return mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED; } boolean shouldRevertOverriddenRotation() { - return mFoldState == DeviceStateController.FoldState.OPEN // When transitioning to open. + // When transitioning to open. + return mDeviceState == DeviceStateController.DeviceState.OPEN && mInHalfFoldTransition && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted. && mUserRotationMode @@ -1676,30 +1694,30 @@ public class DisplayRotation { return savedRotation; } - void foldStateChanged(DeviceStateController.FoldState newState) { + void foldStateChanged(DeviceStateController.DeviceState newState) { ProtoLog.v(WM_DEBUG_ORIENTATION, "foldStateChanged: displayId %d, halfFoldStateChanged %s, " + "saved rotation: %d, mUserRotation: %d, mLastSensorRotation: %d, " + "mLastOrientation: %d, mRotation: %d", mDisplayContent.getDisplayId(), newState.name(), mHalfFoldSavedRotation, mUserRotation, mLastSensorRotation, mLastOrientation, mRotation); - if (mFoldState == DeviceStateController.FoldState.UNKNOWN) { - mFoldState = newState; + if (mDeviceState == DeviceStateController.DeviceState.UNKNOWN) { + mDeviceState = newState; return; } - if (newState == DeviceStateController.FoldState.HALF_FOLDED - && mFoldState != DeviceStateController.FoldState.HALF_FOLDED) { + if (newState == DeviceStateController.DeviceState.HALF_FOLDED + && mDeviceState != DeviceStateController.DeviceState.HALF_FOLDED) { // The device has transitioned to HALF_FOLDED state: save the current rotation and // update the device rotation. mHalfFoldSavedRotation = mRotation; - mFoldState = newState; + mDeviceState = newState; // Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will // return true, so rotation is unlocked. mService.updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */); } else { mInHalfFoldTransition = true; - mFoldState = newState; + mDeviceState = newState; // Tell the device to update its orientation. mService.updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */); @@ -1719,8 +1737,9 @@ public class DisplayRotation { private class OrientationListener extends WindowOrientationListener implements Runnable { transient boolean mEnabled; - OrientationListener(Context context, Handler handler) { - super(context, handler); + OrientationListener(Context context, Handler handler, + @Surface.Rotation int defaultRotation) { + super(context, handler, defaultRotation); } @Override @@ -1822,7 +1841,7 @@ public class DisplayRotation { final long mTimestamp = System.currentTimeMillis(); final int mHalfFoldSavedRotation; final boolean mInHalfFoldTransition; - final DeviceStateController.FoldState mFoldState; + final DeviceStateController.DeviceState mDeviceState; @Nullable final String mDisplayRotationCompatPolicySummary; Record(DisplayRotation dr, int fromRotation, int toRotation) { @@ -1843,8 +1862,9 @@ public class DisplayRotation { if (source != null) { mLastOrientationSource = source.toString(); final WindowState w = source.asWindowState(); - mSourceOrientation = - w != null ? w.mAttrs.screenOrientation : source.mOrientation; + mSourceOrientation = w != null + ? w.mAttrs.screenOrientation + : source.getOverrideOrientation(); } else { mLastOrientationSource = null; mSourceOrientation = SCREEN_ORIENTATION_UNSET; @@ -1852,11 +1872,11 @@ public class DisplayRotation { if (dr.mFoldController != null) { mHalfFoldSavedRotation = dr.mFoldController.mHalfFoldSavedRotation; mInHalfFoldTransition = dr.mFoldController.mInHalfFoldTransition; - mFoldState = dr.mFoldController.mFoldState; + mDeviceState = dr.mFoldController.mDeviceState; } else { mHalfFoldSavedRotation = NO_FOLD_CONTROLLER; mInHalfFoldTransition = false; - mFoldState = DeviceStateController.FoldState.UNKNOWN; + mDeviceState = DeviceStateController.DeviceState.UNKNOWN; } mDisplayRotationCompatPolicySummary = dc.mDisplayRotationCompatPolicy == null ? null @@ -1882,7 +1902,7 @@ public class DisplayRotation { pw.println(prefix + " halfFoldSavedRotation=" + mHalfFoldSavedRotation + " mInHalfFoldTransition=" + mInHalfFoldTransition - + " mFoldState=" + mFoldState); + + " mFoldState=" + mDeviceState); } if (mDisplayRotationCompatPolicySummary != null) { pw.println(prefix + mDisplayRotationCompatPolicySummary); diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index c6037dab6568..3ffb2fa7eaad 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -296,8 +296,8 @@ final class DisplayRotationCompatPolicy { && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED // "locked" and "nosensor" values are often used by camera apps that can't // handle dynamic changes so we shouldn't force rotate them. - && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR - && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED + && activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR + && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED && mCameraIdPackageBiMap.containsPackageName(activity.packageName) && activity.mLetterboxUiController.shouldForceRotateForCameraCompat(); } diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 800fe090b457..b64420af7c98 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST; import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY; import android.annotation.IntDef; @@ -311,12 +312,23 @@ final class LetterboxConfiguration { mDeviceConfig.updateFlagActiveStatus( /* isActive */ mIsDisplayRotationImmersiveAppCompatPolicyEnabled, /* key */ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY); + mDeviceConfig.updateFlagActiveStatus( + /* isActive */ true, + /* key */ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST); mLetterboxConfigurationPersister = letterboxConfigurationPersister; mLetterboxConfigurationPersister.start(); } /** + * Whether enabling ignoreOrientationRequest is allowed on the device. This value is controlled + * via {@link android.provider.DeviceConfig}. + */ + boolean isIgnoreOrientationRequestAllowed() { + return mDeviceConfig.getFlag(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST); + } + + /** * Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link * #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java index cf123a1f9ace..3f067e3a1556 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java +++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java @@ -38,10 +38,16 @@ final class LetterboxConfigurationDeviceConfig private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY = true; + static final String KEY_ALLOW_IGNORE_ORIENTATION_REQUEST = + "allow_ignore_orientation_request"; + private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true; + @VisibleForTesting static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of( KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY, - DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY + DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY, + KEY_ALLOW_IGNORE_ORIENTATION_REQUEST, + DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST ); // Whether enabling rotation compat policy for immersive apps that prevents auto rotation @@ -52,6 +58,10 @@ final class LetterboxConfigurationDeviceConfig private boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled = DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY; + // Whether enabling ignoreOrientationRequest is allowed on the device. + private boolean mIsAllowIgnoreOrientationRequest = + DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST; + // Set of active device configs that need to be updated in // DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged. private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>(); @@ -93,6 +103,8 @@ final class LetterboxConfigurationDeviceConfig switch (key) { case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY: return mIsDisplayRotationImmersiveAppCompatPolicyEnabled; + case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST: + return mIsAllowIgnoreOrientationRequest; default: throw new AssertionError("Unexpected flag name: " + key); } @@ -108,6 +120,10 @@ final class LetterboxConfigurationDeviceConfig mIsDisplayRotationImmersiveAppCompatPolicyEnabled = getDeviceConfig(key, defaultValue); break; + case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST: + mIsAllowIgnoreOrientationRequest = + getDeviceConfig(key, defaultValue); + break; default: throw new AssertionError("Unexpected flag name: " + key); } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 67e188fa9045..c5a50cad41d8 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -17,11 +17,21 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; +import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR; +import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; +import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.ActivityInfo.isFixedOrientation; +import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; import static android.content.pm.ActivityInfo.screenOrientationToString; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -29,6 +39,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; @@ -62,6 +74,9 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_RE import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; + import android.annotation.Nullable; import android.app.ActivityManager.TaskDescription; import android.content.pm.ActivityInfo.ScreenOrientation; @@ -105,6 +120,40 @@ final class LetterboxUiController { private final ActivityRecord mActivityRecord; + /** + * Taskbar expanded height. Used to determine when to crop an app window to display the + * rounded corners above the expanded taskbar. + */ + private final float mExpandedTaskBarHeight; + + // TODO(b/265576778): Cache other overrides as well. + + // Corresponds to OVERRIDE_ANY_ORIENTATION + private final boolean mIsOverrideAnyOrientationEnabled; + // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT + private final boolean mIsOverrideToPortraitOrientationEnabled; + // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR + private final boolean mIsOverrideToNosensorOrientationEnabled; + // Corresponds to OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE + private final boolean mIsOverrideToReverseLandscapeOrientationEnabled; + // Corresponds to OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION + private final boolean mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled; + + // Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION + private final boolean mIsOverrideCameraCompatDisableForceRotationEnabled; + // Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH + private final boolean mIsOverrideCameraCompatDisableRefreshEnabled; + // Corresponds to OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE + private final boolean mIsOverrideCameraCompatEnableRefreshViaPauseEnabled; + + // Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION + private final boolean mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled; + + @Nullable + private final Boolean mBooleanPropertyAllowOrientationOverride; + @Nullable + private final Boolean mBooleanPropertyAllowDisplayOrientationOverride; + /* * WindowContainerListener responsible to make translucent activities inherit * constraints from the first opaque activity beneath them. It's null for not @@ -184,6 +233,38 @@ final class LetterboxUiController { () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ true), PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE); + + mExpandedTaskBarHeight = + getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height); + + mBooleanPropertyAllowOrientationOverride = + readComponentProperty(packageManager, mActivityRecord.packageName, + /* gatingCondition */ null, + PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); + mBooleanPropertyAllowDisplayOrientationOverride = + readComponentProperty(packageManager, mActivityRecord.packageName, + /* gatingCondition */ null, + PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE); + + mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION); + mIsOverrideToPortraitOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT); + mIsOverrideToReverseLandscapeOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE); + mIsOverrideToNosensorOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR); + mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION); + + mIsOverrideCameraCompatDisableForceRotationEnabled = + isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION); + mIsOverrideCameraCompatDisableRefreshEnabled = + isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH); + mIsOverrideCameraCompatEnableRefreshViaPauseEnabled = + isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE); + + mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION); } /** @@ -198,8 +279,8 @@ final class LetterboxUiController { */ @Nullable private static Boolean readComponentProperty(PackageManager packageManager, String packageName, - BooleanSupplier gatingCondition, String propertyName) { - if (!gatingCondition.getAsBoolean()) { + @Nullable BooleanSupplier gatingCondition, String propertyName) { + if (gatingCondition != null && !gatingCondition.getAsBoolean()) { return null; } try { @@ -253,7 +334,7 @@ final class LetterboxUiController { if (!shouldEnableWithOverrideAndProperty( /* gatingCondition */ mLetterboxConfiguration ::isPolicyForIgnoringRequestedOrientationEnabled, - OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION, + mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled, mBooleanPropertyIgnoreRequestedOrientation)) { return false; } @@ -299,6 +380,66 @@ final class LetterboxUiController { } /** + * Whether should fix display orientation to landscape natural orientation when a task is + * fullscreen and the display is ignoring orientation requests. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Opt-out component property isn't enabled + * <li>Opt-in per-app override is enabled + * <li>Task is in fullscreen. + * <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled + * <li>Natural orientation of the display is landscape. + * </ul> + */ + boolean shouldUseDisplayLandscapeNaturalOrientation() { + return shouldEnableWithOptInOverrideAndOptOutProperty( + /* gatingCondition */ () -> mActivityRecord.mDisplayContent != null + && mActivityRecord.getTask() != null + && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest() + && !mActivityRecord.getTask().inMultiWindowMode() + && mActivityRecord.mDisplayContent.getNaturalOrientation() + == ORIENTATION_LANDSCAPE, + mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled, + mBooleanPropertyAllowDisplayOrientationOverride); + } + + @ScreenOrientation + int overrideOrientationIfNeeded(@ScreenOrientation int candidate) { + if (FALSE.equals(mBooleanPropertyAllowOrientationOverride)) { + return candidate; + } + + if (mIsOverrideToReverseLandscapeOrientationEnabled + && (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) { + Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_REVERSE_LANDSCAPE)); + return SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + } + + if (!mIsOverrideAnyOrientationEnabled && isFixedOrientation(candidate)) { + return candidate; + } + + if (mIsOverrideToPortraitOrientationEnabled) { + Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_PORTRAIT)); + return SCREEN_ORIENTATION_PORTRAIT; + } + + if (mIsOverrideToNosensorOrientationEnabled) { + Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_NOSENSOR)); + return SCREEN_ORIENTATION_NOSENSOR; + } + + return candidate; + } + + /** * Whether activity is eligible for activity "refresh" after camera compat force rotation * treatment. See {@link DisplayRotationCompatPolicy} for context. * @@ -313,7 +454,7 @@ final class LetterboxUiController { return shouldEnableWithOptOutOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), - OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH, + mIsOverrideCameraCompatDisableRefreshEnabled, mBooleanPropertyCameraCompatAllowRefresh); } @@ -335,7 +476,7 @@ final class LetterboxUiController { return shouldEnableWithOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), - OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, + mIsOverrideCameraCompatEnableRefreshViaPauseEnabled, mBooleanPropertyCameraCompatEnableRefreshViaPause); } @@ -354,15 +495,19 @@ final class LetterboxUiController { return shouldEnableWithOptOutOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), - OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION, + mIsOverrideCameraCompatDisableForceRotationEnabled, mBooleanPropertyCameraCompatAllowForceRotation); } + private boolean isCompatChangeEnabled(long overrideChangeId) { + return mActivityRecord.info.isChangeEnabled(overrideChangeId); + } + /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} isn't {@code false} - * <li>OEM didn't opt out with a {@code overrideChangeId} override + * <li>OEM didn't opt out with a per-app override * <li>App developers didn't opt out with a component {@code property} * </ul> * @@ -370,12 +515,30 @@ final class LetterboxUiController { * disabled on per-app basis by OEMs or app developers. */ private boolean shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition, - long overrideChangeId, Boolean property) { + boolean isOverrideChangeEnabled, Boolean property) { if (!gatingCondition.getAsBoolean()) { return false; } - return !Boolean.FALSE.equals(property) - && !mActivityRecord.info.isChangeEnabled(overrideChangeId); + return !FALSE.equals(property) && !isOverrideChangeEnabled; + } + + /** + * Returns {@code true} when the following conditions are met: + * <ul> + * <li>{@code gatingCondition} isn't {@code false} + * <li>OEM did opt in with a per-app override + * <li>App developers didn't opt out with a component {@code property} + * </ul> + * + * <p>This is used for the treatments that are enabled based with the heuristic but can be + * disabled on per-app basis by OEMs or app developers. + */ + private boolean shouldEnableWithOptInOverrideAndOptOutProperty(BooleanSupplier gatingCondition, + boolean isOverrideChangeEnabled, Boolean property) { + if (!gatingCondition.getAsBoolean()) { + return false; + } + return !FALSE.equals(property) && isOverrideChangeEnabled; } /** @@ -384,21 +547,20 @@ final class LetterboxUiController { * <li>{@code gatingCondition} isn't {@code false} * <li>App developers didn't opt out with a component {@code property} * <li>App developers opted in with a component {@code property} or an OEM opted in with a - * component {@code property} + * per-app override * </ul> * * <p>This is used for the treatments that are enabled only on per-app basis. */ private boolean shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition, - long overrideChangeId, Boolean property) { + boolean isOverrideChangeEnabled, Boolean property) { if (!gatingCondition.getAsBoolean()) { return false; } - if (Boolean.FALSE.equals(property)) { + if (FALSE.equals(property)) { return false; } - return Boolean.TRUE.equals(property) - || mActivityRecord.info.isChangeEnabled(overrideChangeId); + return TRUE.equals(property) || isOverrideChangeEnabled; } boolean hasWallpaperBackgroundForLetterbox() { @@ -422,7 +584,7 @@ final class LetterboxUiController { if (w == null) { return; } - adjustBoundsForTaskbar(w, outBounds); + adjustBoundsIfNeeded(w, outBounds); } else { outBounds.setEmpty(); } @@ -465,13 +627,13 @@ final class LetterboxUiController { if (w == null || winHint != null && w != winHint) { return; } - updateRoundedCorners(w); + updateRoundedCornersIfNeeded(w); // If there is another main window that is not an application-starting window, we should // update rounded corners for it as well, to avoid flickering rounded corners. final WindowState nonStartingAppW = mActivityRecord.findMainWindow( /* includeStartingApp= */ false); if (nonStartingAppW != null && nonStartingAppW != w) { - updateRoundedCorners(nonStartingAppW); + updateRoundedCornersIfNeeded(nonStartingAppW); } updateWallpaperForLetterbox(w); @@ -533,7 +695,7 @@ final class LetterboxUiController { // Note that we check the task rather than the parent as with ActivityEmbedding the parent might // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is // actually fullscreen. - private boolean isDisplayFullScreenAndInPosture(DeviceStateController.FoldState state, + private boolean isDisplayFullScreenAndInPosture(DeviceStateController.DeviceState state, boolean isTabletop) { Task task = mActivityRecord.getTask(); return mActivityRecord.mDisplayContent != null @@ -559,7 +721,7 @@ final class LetterboxUiController { // Don't check resolved configuration because it may not be updated yet during // configuration change. boolean bookMode = isDisplayFullScreenAndInPosture( - DeviceStateController.FoldState.HALF_FOLDED, false /* isTabletop */); + DeviceStateController.DeviceState.HALF_FOLDED, false /* isTabletop */); return isHorizontalReachabilityEnabled(parentConfiguration) // Using the last global dynamic position to avoid "jumps" when moving // between apps or activities. @@ -571,7 +733,7 @@ final class LetterboxUiController { // Don't check resolved configuration because it may not be updated yet during // configuration change. boolean tabletopMode = isDisplayFullScreenAndInPosture( - DeviceStateController.FoldState.HALF_FOLDED, true /* isTabletop */); + DeviceStateController.DeviceState.HALF_FOLDED, true /* isTabletop */); return isVerticalReachabilityEnabled(parentConfiguration) // Using the last global dynamic position to avoid "jumps" when moving // between apps or activities. @@ -724,6 +886,7 @@ final class LetterboxUiController { * <li>Activity is portrait-only. * <li>Fullscreen window in landscape device orientation. * <li>Horizontal Reachability is enabled. + * <li>Activity fills parent vertically. * </ul> */ private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) { @@ -731,10 +894,14 @@ final class LetterboxUiController { && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE - && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT); + && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT) + // Check whether the activity fills the parent vertically. + && parentConfiguration.windowConfiguration.getBounds().height() + == mActivityRecord.getBounds().height(); } - private boolean isHorizontalReachabilityEnabled() { + @VisibleForTesting + boolean isHorizontalReachabilityEnabled() { return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration()); } @@ -746,6 +913,7 @@ final class LetterboxUiController { * <li>Activity is landscape-only. * <li>Fullscreen window in portrait device orientation. * <li>Vertical Reachability is enabled. + * <li>Activity fills parent horizontally. * </ul> */ private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) { @@ -753,10 +921,14 @@ final class LetterboxUiController { && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (parentConfiguration.orientation == ORIENTATION_PORTRAIT - && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE); + && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE) + // Check whether the activity fills the parent horizontally. + && parentConfiguration.windowConfiguration.getBounds().width() + == mActivityRecord.getBounds().width(); } - private boolean isVerticalReachabilityEnabled() { + @VisibleForTesting + boolean isVerticalReachabilityEnabled() { return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration()); } @@ -765,8 +937,8 @@ final class LetterboxUiController { return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed() // Check for FLAG_SHOW_WALLPAPER explicitly instead of using // WindowContainer#showWallpaper because the later will return true when this - // activity is using blurred wallpaper for letterbox backgroud. - && (mainWindow.mAttrs.flags & FLAG_SHOW_WALLPAPER) == 0; + // activity is using blurred wallpaper for letterbox background. + && (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0; } @VisibleForTesting @@ -818,106 +990,107 @@ final class LetterboxUiController { return mLetterboxConfiguration.getLetterboxBackgroundColor(); } - private void updateRoundedCorners(WindowState mainWindow) { + private void updateRoundedCornersIfNeeded(final WindowState mainWindow) { final SurfaceControl windowSurface = mainWindow.getSurfaceControl(); - if (windowSurface != null && windowSurface.isValid()) { - 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); - return; - } + if (windowSurface == null || !windowSurface.isValid()) { + return; + } - Rect cropBounds = null; + // cropBounds must be non-null for the cornerRadius to be ever applied. + mActivityRecord.getSyncTransaction() + .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow)) + .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow)); + } - if (hasVisibleTaskbar(mainWindow)) { - cropBounds = new Rect(mActivityRecord.getBounds()); + @VisibleForTesting + @Nullable + Rect getCropBoundsIfNeeded(final WindowState mainWindow) { + 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. + return null; + } - // Rounded corners should be displayed above the taskbar. - // It is important to call adjustBoundsForTaskbarUnchecked before offsetTo - // because taskbar bounds are in screen coordinates - adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds); + final Rect 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); - } + // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo} + // because taskbar bounds used in {@link #adjustBoundsIfNeeded} + // are in screen coordinates + adjustBoundsIfNeeded(mainWindow, cropBounds); - transaction - .setWindowCrop(windowSurface, cropBounds) - .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow)); - } + // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface + // control is in the top left corner of an app window so offsetting bounds + // accordingly. + cropBounds.offsetTo(0, 0); + return cropBounds; } - private boolean requiresRoundedCorners(WindowState mainWindow) { - final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow); - + private boolean requiresRoundedCorners(final WindowState mainWindow) { return isLetterboxedNotForDisplayCutout(mainWindow) - && mLetterboxConfiguration.isLetterboxActivityCornersRounded() - && taskbarInsetsSource != null; + && mLetterboxConfiguration.isLetterboxActivityCornersRounded(); } // 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 + // 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. - int getRoundedCornersRadius(WindowState mainWindow) { - if (!requiresRoundedCorners(mainWindow)) { + int getRoundedCornersRadius(final WindowState mainWindow) { + if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) { return 0; } + final int radius; if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) { - return mLetterboxConfiguration.getLetterboxActivityCornersRadius(); + radius = mLetterboxConfiguration.getLetterboxActivityCornersRadius(); + } else { + final InsetsState insetsState = mainWindow.getInsetsState(); + radius = Math.min( + getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT), + getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT)); } - final InsetsState insetsState = mainWindow.getInsetsState(); - return Math.min( - getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT), - getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT)); + final float scale = mainWindow.mInvGlobalScale; + return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius; } /** - * 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. + * Returns the taskbar in case it is visible and expanded in height, otherwise returns null. */ - private boolean hasVisibleTaskbar(WindowState mainWindow) { - final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow); - - return taskbarInsetsSource != null - && taskbarInsetsSource.isVisible(); - } - - private InsetsSource getTaskbarInsetsSource(WindowState mainWindow) { - final InsetsState insetsState = mainWindow.getInsetsState(); - return insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + @VisibleForTesting + @Nullable + InsetsSource getExpandedTaskbarOrNull(final WindowState mainWindow) { + final InsetsSource taskbar = mainWindow.getInsetsState().peekSource( + InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + if (taskbar != null && taskbar.isVisible() + && taskbar.getFrame().height() >= mExpandedTaskBarHeight) { + return taskbar; + } + return null; } - private void adjustBoundsForTaskbar(WindowState mainWindow, Rect bounds) { + private void adjustBoundsIfNeeded(final WindowState mainWindow, final 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); + // Adjust the bounds only in case there is an expanded taskbar, + // otherwise the rounded corners will be shown behind the navbar. + final InsetsSource expandedTaskbarOrNull = getExpandedTaskbarOrNull(mainWindow); + if (expandedTaskbarOrNull != null) { + // Rounded corners should be displayed above the expanded taskbar. + bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top); } - } - 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); - scaleIfNeeded(bounds); + final float scale = mainWindow.mInvGlobalScale; + if (scale != 1f && scale > 0f) { + bounds.scale(scale); + } } private int getInsetsStateCornerRadius( @@ -1087,7 +1260,7 @@ final class LetterboxUiController { int letterboxPositionForHorizontalReachability = getLetterboxConfiguration() .getLetterboxPositionForHorizontalReachability( isDisplayFullScreenAndInPosture( - DeviceStateController.FoldState.HALF_FOLDED, + DeviceStateController.DeviceState.HALF_FOLDED, false /* isTabletop */)); positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition( letterboxPositionForHorizontalReachability); @@ -1095,7 +1268,7 @@ final class LetterboxUiController { int letterboxPositionForVerticalReachability = getLetterboxConfiguration() .getLetterboxPositionForVerticalReachability( isDisplayFullScreenAndInPosture( - DeviceStateController.FoldState.HALF_FOLDED, + DeviceStateController.DeviceState.HALF_FOLDED, true /* isTabletop */)); positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition( letterboxPositionForVerticalReachability); @@ -1204,7 +1377,8 @@ final class LetterboxUiController { // To avoid wrong behaviour, we're not forcing orientation for activities with not // fixed orientation (e.g. permission dialogs). return hasInheritedLetterboxBehavior() - && mActivityRecord.mOrientation != SCREEN_ORIENTATION_UNSPECIFIED; + && mActivityRecord.getOverrideOrientation() + != SCREEN_ORIENTATION_UNSPECIFIED; } float getInheritedMinAspectRatio() { @@ -1259,20 +1433,4 @@ final class LetterboxUiController { mInheritedSizeCompatScale = 1f; mInheritedCompatDisplayInsets = null; } - - private void scaleIfNeeded(Rect bounds) { - if (boundsNeedToScale()) { - bounds.scale(1.0f / mActivityRecord.getCompatScale()); - } - } - - private boolean boundsNeedToScale() { - if (hasInheritedLetterboxBehavior()) { - return mIsInheritedInSizeCompatMode - && mInheritedSizeCompatScale < 1.0f; - } else { - return mActivityRecord.inSizeCompatMode() - && mActivityRecord.getCompatScale() < 1.0f; - } - } } diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java index 30bdc3477edf..2edb082ab8df 100644 --- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java +++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java @@ -51,10 +51,10 @@ public class PhysicalDisplaySwitchTransitionLauncher { /** * Called by the DeviceStateManager callback when the state changes. */ - void foldStateChanged(DeviceStateController.FoldState newFoldState) { + void foldStateChanged(DeviceStateController.DeviceState newDeviceState) { // Ignore transitions to/from half-folded. - if (newFoldState == DeviceStateController.FoldState.HALF_FOLDED) return; - mIsFolded = newFoldState == DeviceStateController.FoldState.FOLDED; + if (newDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) return; + mIsFolded = newDeviceState == DeviceStateController.DeviceState.FOLDED; } /** diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 8bdab9c22ab7..9a20354bcf81 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -73,6 +73,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Point; @@ -178,8 +179,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< protected final WindowList<E> mChildren = new WindowList<E>(); // The specified orientation for this window container. - @ActivityInfo.ScreenOrientation - protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + // Shouldn't be accessed directly since subclasses can override getOverrideOrientation. + @ScreenOrientation + private int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED; /** * The window container which decides its orientation since the last time @@ -1427,19 +1429,20 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< /** * Gets the configuration orientation by the requested screen orientation - * ({@link ActivityInfo.ScreenOrientation}) of this activity. + * ({@link ScreenOrientation}) of this activity. * * @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE}, * {@link Configuration#ORIENTATION_PORTRAIT}, * {@link Configuration#ORIENTATION_UNDEFINED}). */ + @ScreenOrientation int getRequestedConfigurationOrientation() { return getRequestedConfigurationOrientation(false /* forDisplay */); } /** * Gets the configuration orientation by the requested screen orientation - * ({@link ActivityInfo.ScreenOrientation}) of this activity. + * ({@link ScreenOrientation}) of this activity. * * @param forDisplay whether it is the requested config orientation for display. * If {@code true}, we may reverse the requested orientation if the root is @@ -1450,8 +1453,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * {@link Configuration#ORIENTATION_PORTRAIT}, * {@link Configuration#ORIENTATION_UNDEFINED}). */ + @ScreenOrientation int getRequestedConfigurationOrientation(boolean forDisplay) { - int requestedOrientation = mOrientation; + int requestedOrientation = getOverrideOrientation(); final RootDisplayArea root = getRootDisplayArea(); if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { // Reverse the requested orientation if the orientation of its root is different from @@ -1461,7 +1465,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // (portrait). // When an app below the DAG is requesting landscape, it should actually request the // display to be portrait, so that the DAG and the app will be in landscape. - requestedOrientation = reverseOrientation(mOrientation); + requestedOrientation = reverseOrientation(getOverrideOrientation()); } if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) { @@ -1486,7 +1490,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * * @param orientation the specified orientation. */ - void setOrientation(int orientation) { + void setOrientation(@ScreenOrientation int orientation) { setOrientation(orientation, null /* requestingContainer */); } @@ -1494,17 +1498,17 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * Sets the specified orientation of this container. It percolates this change upward along the * hierarchy to let each level of the hierarchy a chance to respond to it. * - * @param orientation the specified orientation. Needs to be one of {@link - * android.content.pm.ActivityInfo.ScreenOrientation}. + * @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}. * @param requestingContainer the container which orientation request has changed. Mostly used * to ensure it gets correct configuration. */ - void setOrientation(int orientation, @Nullable WindowContainer requestingContainer) { - if (mOrientation == orientation) { + void setOrientation(@ScreenOrientation int orientation, + @Nullable WindowContainer requestingContainer) { + if (getOverrideOrientation() == orientation) { return; } - mOrientation = orientation; + setOverrideOrientation(orientation); final WindowContainer parent = getParent(); if (parent != null) { if (getConfiguration().orientation != getRequestedConfigurationOrientation() @@ -1523,9 +1527,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - @ActivityInfo.ScreenOrientation + @ScreenOrientation int getOrientation() { - return getOrientation(mOrientation); + return getOrientation(getOverrideOrientation()); } /** @@ -1539,7 +1543,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * better match. * @return The orientation as specified by this branch or the window hierarchy. */ - int getOrientation(int candidate) { + @ScreenOrientation + int getOrientation(@ScreenOrientation int candidate) { mLastOrientationSource = null; if (!providesOrientation()) { return SCREEN_ORIENTATION_UNSET; @@ -1549,16 +1554,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // specified; otherwise we prefer to use the orientation of its topmost child that has one // specified and fall back on this container's unset or unspecified value as a candidate // if none of the children have a better candidate for the orientation. - if (mOrientation != SCREEN_ORIENTATION_UNSET - && mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { + if (getOverrideOrientation() != SCREEN_ORIENTATION_UNSET + && getOverrideOrientation() != SCREEN_ORIENTATION_UNSPECIFIED) { mLastOrientationSource = this; - return mOrientation; + return getOverrideOrientation(); } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); - // TODO: Maybe mOrientation should default to SCREEN_ORIENTATION_UNSET vs. + // TODO: Maybe mOverrideOrientation should default to SCREEN_ORIENTATION_UNSET vs. // SCREEN_ORIENTATION_UNSPECIFIED? final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND ? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET); @@ -1590,6 +1595,20 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** + * Returns orientation specified on this level of hierarchy without taking children into + * account, like {@link #getOrientation} does, allowing subclasses to override. See {@link + * ActivityRecord#getOverrideOrientation} for an example. + */ + @ScreenOrientation + protected int getOverrideOrientation() { + return mOverrideOrientation; + } + + protected void setOverrideOrientation(@ScreenOrientation int orientation) { + mOverrideOrientation = orientation; + } + + /** * @return The deepest source which decides the orientation of this window container since the * last time {@link #getOrientation(int) was called. */ @@ -2635,7 +2654,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final long token = proto.start(fieldId); super.dumpDebug(proto, CONFIGURATION_CONTAINER, logLevel); - proto.write(ORIENTATION, mOrientation); + proto.write(ORIENTATION, mOverrideOrientation); proto.write(VISIBLE, isVisible); writeIdentifierToProto(proto, IDENTIFIER); if (mSurfaceAnimator.isAnimating()) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 5065014b9bee..2911890bf80e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -4170,7 +4170,8 @@ public class WindowManagerService extends IWindowManager.Stub * <p>Note: this assumes that {@link #mGlobalLock} is held by the caller. */ boolean isIgnoreOrientationRequestDisabled() { - return mIsIgnoreOrientationRequestDisabled; + return mIsIgnoreOrientationRequestDisabled + || !mLetterboxConfiguration.isIgnoreOrientationRequestAllowed(); } @Override diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java index 3e165e442d79..14c816db0dbb 100644 --- a/services/core/java/com/android/server/wm/WindowOrientationListener.java +++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java @@ -88,14 +88,19 @@ public abstract class WindowOrientationListener { private final Object mLock = new Object(); + @Surface.Rotation + private final int mDefaultRotation; + /** * Creates a new WindowOrientationListener. * * @param context for the WindowOrientationListener. * @param handler Provides the Looper for receiving sensor updates. + * @param defaultRotation Default rotation of the display. */ - public WindowOrientationListener(Context context, Handler handler) { - this(context, handler, SensorManager.SENSOR_DELAY_UI); + public WindowOrientationListener(Context context, Handler handler, + @Surface.Rotation int defaultRotation) { + this(context, handler, defaultRotation, SensorManager.SENSOR_DELAY_UI); } /** @@ -103,7 +108,7 @@ public abstract class WindowOrientationListener { * * @param context for the WindowOrientationListener. * @param handler Provides the Looper for receiving sensor updates. - * @param wmService WindowManagerService to read the device config from. + * @param defaultRotation Default rotation of the display. * @param rate at which sensor events are processed (see also * {@link android.hardware.SensorManager SensorManager}). Use the default * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL @@ -111,10 +116,11 @@ public abstract class WindowOrientationListener { * * This constructor is private since no one uses it. */ - private WindowOrientationListener( - Context context, Handler handler, int rate) { + private WindowOrientationListener(Context context, Handler handler, + @Surface.Rotation int defaultRotation, int rate) { mContext = context; mHandler = handler; + mDefaultRotation = defaultRotation; mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); mRate = rate; List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION); @@ -1159,7 +1165,7 @@ public abstract class WindowOrientationListener { "Reusing the last rotation resolution: " + mLastRotationResolution); finalizeRotation(mLastRotationResolution); } else { - finalizeRotation(Surface.ROTATION_0); + finalizeRotation(mDefaultRotation); } return; } diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index 33ac73560968..ea0481e03b97 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -43,6 +43,9 @@ <!-- needed by TrustManagerServiceTest to access LockSettings' secure storage --> <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> + <!-- needed by GameManagerServiceTest because GameManager creates a UidObserver --> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> + <application android:testOnly="true" android:debuggable="true"> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index fa4a9de62a88..2d5f0b01ab4c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -29,13 +29,16 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.Manifest; +import android.app.ActivityManager; import android.app.GameManager; import android.app.GameModeInfo; import android.app.GameState; @@ -203,6 +206,23 @@ public class GameManagerServiceTests { LocalServices.addService(PowerManagerInternal.class, mMockPowerManager); } + private void mockAppCategory(String packageName, @ApplicationInfo.Category int category) + throws Exception { + reset(mMockPackageManager); + final ApplicationInfo gameApplicationInfo = new ApplicationInfo(); + gameApplicationInfo.category = category; + gameApplicationInfo.packageName = packageName; + final PackageInfo pi = new PackageInfo(); + pi.packageName = packageName; + pi.applicationInfo = gameApplicationInfo; + final List<PackageInfo> packages = new ArrayList<>(); + packages.add(pi); + when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt())) + .thenReturn(packages); + when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(gameApplicationInfo); + } + @After public void tearDown() throws Exception { LocalServices.removeServiceForTest(PowerManagerInternal.class); @@ -1597,4 +1617,113 @@ public class GameManagerServiceTests { ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(0.0f)); } + + private GameManagerService createServiceAndStartUser(int userId) { + GameManagerService gameManagerService = new GameManagerService(mMockContext, + mTestLooper.getLooper()); + startUser(gameManagerService, userId); + return gameManagerService; + } + + @Test + public void testGamePowerMode_gamePackage() throws Exception { + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); + String[] packages = {mPackageName}; + when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); + } + + @Test + public void testGamePowerMode_twoGames() throws Exception { + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); + String[] packages1 = {mPackageName}; + when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1); + String someGamePkg = "some.game"; + String[] packages2 = {someGamePkg}; + int somePackageId = DEFAULT_PACKAGE_UID + 1; + when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); + HashMap<Integer, Boolean> powerState = new HashMap<>(); + doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1))) + .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean()); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + assertTrue(powerState.get(Mode.GAME)); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + gameManagerService.mUidObserver.onUidStateChanged( + somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + assertTrue(powerState.get(Mode.GAME)); + gameManagerService.mUidObserver.onUidStateChanged( + somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + assertFalse(powerState.get(Mode.GAME)); + } + + @Test + public void testGamePowerMode_twoGamesOverlap() throws Exception { + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); + String[] packages1 = {mPackageName}; + when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1); + String someGamePkg = "some.game"; + String[] packages2 = {someGamePkg}; + int somePackageId = DEFAULT_PACKAGE_UID + 1; + when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + gameManagerService.mUidObserver.onUidStateChanged( + somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + gameManagerService.mUidObserver.onUidStateChanged( + somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); + } + + @Test + public void testGamePowerMode_released() throws Exception { + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); + String[] packages = {mPackageName}; + when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); + } + + @Test + public void testGamePowerMode_noPackage() throws Exception { + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); + String[] packages = {}; + when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true); + } + + @Test + public void testGamePowerMode_notAGamePackage() throws Exception { + mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE); + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); + String[] packages = {"someapp"}; + when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true); + } + + @Test + public void testGamePowerMode_notAGamePackageNotReleased() throws Exception { + mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE); + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); + String[] packages = {"someapp"}; + when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false); + } } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 2edb909258f9..52fade120733 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -424,6 +424,37 @@ public class DisplayManagerServiceTest { } /** + * Tests that HighBrightnessModeMetadata is non-null on all display devices. + */ + @Test + public void testHighBrightnessModeMetadataNonNull() throws Exception { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mShortMockedInjector); + registerDefaultDisplays(displayManager); + displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY); + + // Add the FakeDisplayDevice + FakeDisplayDevice displayDevice = new FakeDisplayDevice("unique_hbm_device"); + DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo(); + + displayDevice.setDisplayDeviceInfo(displayDeviceInfo); + + LogicalDisplay logicalDisplay = new LogicalDisplay(1, 1, displayDevice); + HighBrightnessModeMetadata hbmMeta = + displayManager.getHighBrightnessModeMetadata(logicalDisplay); + + assertNotNull(hbmMeta); + + // Check is Hbm metadata is correctly added for the display device. + String uniqueId = displayDevice.getUniqueId(); + assertTrue(uniqueId.equals("unique_hbm_device")); + assertTrue(displayManager.mHighBrightnessModeMetadataMap.containsKey(uniqueId)); + HighBrightnessModeMetadata hbmMetaFromMap = + displayManager.mHighBrightnessModeMetadataMap.get(uniqueId); + assertEquals(hbmMeta, hbmMetaFromMap); + } + + /** * Tests that we get a Runtime exception when we cannot initialize the default display. */ @Test @@ -1349,6 +1380,11 @@ public class DisplayManagerServiceTest { super(null, null, "", mContext); } + FakeDisplayDevice(String uniqueDeviceId) { + super(null, null, uniqueDeviceId, mContext); + } + + public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) { mDisplayDeviceInfo = displayDeviceInfo; } diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index 650eef0338e6..a7da2417b0bc 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -16,6 +16,7 @@ package com.android.server.display; +import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY_GROUP; import static android.view.Display.TYPE_EXTERNAL; @@ -441,6 +442,11 @@ public class LogicalDisplayMapperTest { /* isOverrideActive= */false, /* isInteractive= */true, /* isBootCompleted= */true)); + assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED, + INVALID_DEVICE_STATE, + /* isOverrideActive= */false, + /* isInteractive= */true, + /* isBootCompleted= */true)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java new file mode 100644 index 000000000000..6c73f716493c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.dreams; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.service.dreams.DreamOverlayService; +import android.service.dreams.IDreamOverlay; +import android.service.dreams.IDreamOverlayCallback; +import android.service.dreams.IDreamOverlayClient; +import android.service.dreams.IDreamOverlayClientCallback; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * A collection of tests to exercise {@link DreamOverlayService}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DreamOverlayServiceTest { + private static final ComponentName FIRST_DREAM_COMPONENT = + ComponentName.unflattenFromString("com.foo.bar/.DreamService"); + private static final ComponentName SECOND_DREAM_COMPONENT = + ComponentName.unflattenFromString("com.foo.baz/.DreamService"); + + @Mock + WindowManager.LayoutParams mLayoutParams; + + @Mock + IDreamOverlayCallback mOverlayCallback; + + /** + * {@link TestDreamOverlayService} is a simple {@link DreamOverlayService} implementation for + * tracking interactions across {@link IDreamOverlay} binder interface. The service reports + * interactions to a {@link Monitor} instance provided at construction. + */ + private static class TestDreamOverlayService extends DreamOverlayService { + /** + * An interface implemented to be informed when the corresponding methods in + * {@link TestDreamOverlayService} are invoked. + */ + interface Monitor { + void onStartDream(); + void onEndDream(); + void onWakeUp(); + } + + private final Monitor mMonitor; + + TestDreamOverlayService(Monitor monitor) { + super(); + mMonitor = monitor; + } + + @Override + public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) { + mMonitor.onStartDream(); + } + + @Override + public void onEndDream() { + mMonitor.onEndDream(); + super.onEndDream(); + } + + @Override + public void onWakeUp(@NonNull Runnable onCompleteCallback) { + mMonitor.onWakeUp(); + super.onWakeUp(onCompleteCallback); + } + } + + /** + * A {@link IDreamOverlayClientCallback} implementation that captures the requested client. + */ + private static class OverlayClientCallback extends IDreamOverlayClientCallback.Stub { + public IDreamOverlayClient retrievedClient; + @Override + public void onDreamOverlayClient(IDreamOverlayClient client) throws RemoteException { + retrievedClient = client; + } + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Verifies that only the currently started dream is able to affect the overlay. + */ + @Test + public void testOverlayClientInteraction() throws RemoteException { + final TestDreamOverlayService.Monitor monitor = Mockito.mock( + TestDreamOverlayService.Monitor.class); + final TestDreamOverlayService service = new TestDreamOverlayService(monitor); + final IBinder binder = service.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder); + + // Create two overlay clients and ensure they are unique. + final IDreamOverlayClient firstClient = getClient(overlay); + assertThat(firstClient).isNotNull(); + + final IDreamOverlayClient secondClient = getClient(overlay); + assertThat(secondClient).isNotNull(); + + assertThat(firstClient).isNotEqualTo(secondClient); + + // Start a dream with the first client and ensure the dream is now active from the + // overlay's perspective. + firstClient.startDream(mLayoutParams, mOverlayCallback, + FIRST_DREAM_COMPONENT.flattenToString(), false); + + + verify(monitor).onStartDream(); + assertThat(service.getDreamComponent()).isEqualTo(FIRST_DREAM_COMPONENT); + + Mockito.clearInvocations(monitor); + + // Start a dream from the second client and verify that the overlay has both cycled to + // the new dream (ended/started). + secondClient.startDream(mLayoutParams, mOverlayCallback, + SECOND_DREAM_COMPONENT.flattenToString(), false); + + verify(monitor).onEndDream(); + verify(monitor).onStartDream(); + assertThat(service.getDreamComponent()).isEqualTo(SECOND_DREAM_COMPONENT); + + Mockito.clearInvocations(monitor); + + // Verify that interactions with the first, now inactive client don't affect the overlay. + firstClient.endDream(); + verify(monitor, never()).onEndDream(); + + firstClient.wakeUp(); + verify(monitor, never()).onWakeUp(); + } + + private static IDreamOverlayClient getClient(IDreamOverlay overlay) throws RemoteException { + final OverlayClientCallback callback = new OverlayClientCallback(); + overlay.getClient(callback); + return callback.retrievedClient; + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java index 3de65c19a1a4..1b3a19954d7f 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java @@ -132,6 +132,19 @@ public class WindowOrientationListenerTest { assertThat(mFinalizedRotation).isEqualTo(DEFAULT_SENSOR_ROTATION); } + @Test + public void testOnSensorChanged_screenLocked_doNotCallRotationResolverReturnDefaultRotation() { + mWindowOrientationListener = new TestableWindowOrientationListener(mMockContext, + mHandler, /* defaultRotation */ Surface.ROTATION_180); + mWindowOrientationListener.mRotationResolverService = mFakeRotationResolverInternal; + mWindowOrientationListener.mIsScreenLocked = true; + + mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent); + + assertThat(mWindowOrientationListener.mIsOnProposedRotationChangedCalled).isFalse(); + assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_180); + } + static final class TestableRotationResolver extends RotationResolverInternal { @Surface.Rotation RotationResolverCallbackInternal mCallback; @@ -166,21 +179,17 @@ public class WindowOrientationListenerTest { } } - @Test - public void testOnSensorChanged_inLockScreen_doNotCallRotationResolver() { - mWindowOrientationListener.mIsScreenLocked = true; - - mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent); - - assertThat(mWindowOrientationListener.mIsOnProposedRotationChangedCalled).isFalse(); - } - final class TestableWindowOrientationListener extends WindowOrientationListener { private boolean mIsOnProposedRotationChangedCalled = false; private boolean mIsScreenLocked; TestableWindowOrientationListener(Context context, Handler handler) { - super(context, handler); + this(context, handler, Surface.ROTATION_0); + } + + TestableWindowOrientationListener(Context context, Handler handler, + @Surface.Rotation int defaultRotation) { + super(context, handler, defaultRotation); this.mOrientationJudge = new OrientationSensorJudge(); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 798604306b43..8b1384ed894f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -1582,6 +1582,55 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + public void testSetComponentState_differentUsers() throws Exception { + Context context = mock(Context.class); + PackageManager pm = mock(PackageManager.class); + ApplicationInfo ai = new ApplicationInfo(); + ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; + + when(context.getPackageName()).thenReturn(mContext.getPackageName()); + when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageManager()).thenReturn(pm); + when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); + + ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + ComponentName cn = ComponentName.unflattenFromString("a/a"); + + addExpectedServices(service, Arrays.asList("a"), mZero.id); + addExpectedServices(service, Arrays.asList("a"), mTen.id); + when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> { + Object[] args = invocation.getArguments(); + ServiceConnection sc = (ServiceConnection) args[1]; + sc.onServiceConnected(cn, mock(IBinder.class)); + return true; + }); + service.addApprovedList("a/a", 0, true); + service.addApprovedList("a/a", 10, false); + + service.registerService(cn, mZero.id); + assertTrue(service.isBound(cn, mZero.id)); + + service.onUserSwitched(mTen.id); + assertFalse(service.isBound(cn, mZero.id)); + service.registerService(cn, mTen.id); + assertTrue(service.isBound(cn, mTen.id)); + + service.setComponentState(cn, mTen.id, false); + assertFalse(service.isBound(cn, mZero.id)); + assertFalse(service.isBound(cn, mTen.id)); + + // Service should be rebound on user 0, since it was only disabled for user 10. + service.onUserSwitched(mZero.id); + assertTrue(service.isBound(cn, mZero.id)); + assertFalse(service.isBound(cn, mTen.id)); + + // Service should stay unbound on going back to user 10. + service.onUserSwitched(mTen.id); + assertFalse(service.isBound(cn, mZero.id)); + assertFalse(service.isBound(cn, mTen.id)); + } + @Test public void testOnPackagesChanged_nullValuesPassed_noNullPointers() { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, @@ -1847,7 +1896,7 @@ public class ManagedServicesTest extends UiServiceTestCase { } private void addExpectedServices(final ManagedServices service, final List<String> packages, - int userId) { + int userId) throws Exception { ManagedServices.Config config = service.getConfig(); when(mPm.queryIntentServicesAsUser(any(), anyInt(), eq(userId))). thenAnswer(new Answer<List<ResolveInfo>>() { @@ -1876,6 +1925,20 @@ public class ManagedServicesTest extends UiServiceTestCase { return new ArrayList<>(); } }); + + when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer( + (Answer<ServiceInfo>) invocation -> { + ComponentName invocationCn = invocation.getArgument(0); + if (invocationCn != null && packages.contains(invocationCn.getPackageName())) { + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.packageName = invocationCn.getPackageName(); + serviceInfo.name = invocationCn.getClassName(); + serviceInfo.permission = service.getConfig().bindPermission; + return serviceInfo; + } + return null; + } + ); } private List<String> stringToList(String list) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java index 86732c9a3f46..2a28ae29e692 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java @@ -16,13 +16,12 @@ package com.android.server.wm; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -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 org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; import android.content.Context; import android.content.res.Resources; @@ -32,9 +31,10 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.R; + import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; import java.util.function.Consumer; @@ -48,92 +48,76 @@ import java.util.function.Consumer; @Presubmit public class DeviceStateControllerTests { - private DeviceStateController.FoldStateListener mFoldStateListener; private DeviceStateController mTarget; private DeviceStateControllerBuilder mBuilder; private Context mMockContext; - private Handler mMockHandler; - private Resources mMockRes; private DeviceStateManager mMockDeviceStateManager; - - private Consumer<DeviceStateController.FoldState> mDelegate; - private DeviceStateController.FoldState mCurrentState = DeviceStateController.FoldState.UNKNOWN; + private DeviceStateController.DeviceState mCurrentState = + DeviceStateController.DeviceState.UNKNOWN; @Before public void setUp() { mBuilder = new DeviceStateControllerBuilder(); - mCurrentState = DeviceStateController.FoldState.UNKNOWN; + mCurrentState = DeviceStateController.DeviceState.UNKNOWN; } - private void initialize(boolean supportFold, boolean supportHalfFold) throws Exception { + private void initialize(boolean supportFold, boolean supportHalfFold) { mBuilder.setSupportFold(supportFold, supportHalfFold); - mDelegate = (newFoldState) -> { + Consumer<DeviceStateController.DeviceState> delegate = (newFoldState) -> { mCurrentState = newFoldState; }; - mBuilder.setDelegate(mDelegate); + mBuilder.setDelegate(delegate); mBuilder.build(); - verifyFoldStateListenerRegistration(1); + verify(mMockDeviceStateManager).registerCallback(any(), any()); } @Test - public void testInitialization() throws Exception { + public void testInitialization() { initialize(true /* supportFold */, true /* supportHalfFolded */); - mFoldStateListener.onStateChanged(mUnfoldedStates[0]); - assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN); + mTarget.onStateChanged(mOpenDeviceStates[0]); + assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState); } @Test - public void testInitializationWithNoFoldSupport() throws Exception { + public void testInitializationWithNoFoldSupport() { initialize(false /* supportFold */, false /* supportHalfFolded */); - mFoldStateListener.onStateChanged(mFoldedStates[0]); + mTarget.onStateChanged(mFoldedStates[0]); // Note that the folded state is ignored. - assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN); + assertEquals(DeviceStateController.DeviceState.UNKNOWN, mCurrentState); } @Test - public void testWithFoldSupported() throws Exception { + public void testWithFoldSupported() { initialize(true /* supportFold */, false /* supportHalfFolded */); - mFoldStateListener.onStateChanged(mUnfoldedStates[0]); - assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN); - mFoldStateListener.onStateChanged(mFoldedStates[0]); - assertEquals(mCurrentState, DeviceStateController.FoldState.FOLDED); - mFoldStateListener.onStateChanged(mHalfFoldedStates[0]); - assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN); // Ignored + mTarget.onStateChanged(mOpenDeviceStates[0]); + assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState); + mTarget.onStateChanged(mFoldedStates[0]); + assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState); + mTarget.onStateChanged(mHalfFoldedStates[0]); + assertEquals(DeviceStateController.DeviceState.UNKNOWN, mCurrentState); // Ignored } @Test - public void testWithHalfFoldSupported() throws Exception { + public void testWithHalfFoldSupported() { initialize(true /* supportFold */, true /* supportHalfFolded */); - mFoldStateListener.onStateChanged(mUnfoldedStates[0]); - assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN); - mFoldStateListener.onStateChanged(mFoldedStates[0]); - assertEquals(mCurrentState, DeviceStateController.FoldState.FOLDED); - mFoldStateListener.onStateChanged(mHalfFoldedStates[0]); - assertEquals(mCurrentState, DeviceStateController.FoldState.HALF_FOLDED); + mTarget.onStateChanged(mOpenDeviceStates[0]); + assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState); + mTarget.onStateChanged(mFoldedStates[0]); + assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState); + mTarget.onStateChanged(mHalfFoldedStates[0]); + assertEquals(DeviceStateController.DeviceState.HALF_FOLDED, mCurrentState); } - private final int[] mFoldedStates = {0}; - private final int[] mUnfoldedStates = {1}; + private final int[] mOpenDeviceStates = {1}; private final int[] mHalfFoldedStates = {2}; - - - private void verifyFoldStateListenerRegistration(int numOfInvocation) { - final ArgumentCaptor<DeviceStateController.FoldStateListener> listenerCaptor = - ArgumentCaptor.forClass(DeviceStateController.FoldStateListener.class); - verify(mMockDeviceStateManager, times(numOfInvocation)).registerCallback( - any(), - listenerCaptor.capture()); - if (numOfInvocation > 0) { - mFoldStateListener = listenerCaptor.getValue(); - } - } + private final int[] mRearDisplayStates = {3}; private class DeviceStateControllerBuilder { private boolean mSupportFold = false; private boolean mSupportHalfFold = false; - private Consumer<DeviceStateController.FoldState> mDelegate; + private Consumer<DeviceStateController.DeviceState> mDelegate; DeviceStateControllerBuilder setSupportFold( boolean supportFold, boolean supportHalfFold) { @@ -143,34 +127,44 @@ public class DeviceStateControllerTests { } DeviceStateControllerBuilder setDelegate( - Consumer<DeviceStateController.FoldState> delegate) { + Consumer<DeviceStateController.DeviceState> delegate) { mDelegate = delegate; return this; } private void mockFold(boolean enableFold, boolean enableHalfFold) { + if (enableFold || enableHalfFold) { + when(mMockContext.getResources() + .getIntArray(R.array.config_openDeviceStates)) + .thenReturn(mOpenDeviceStates); + when(mMockContext.getResources() + .getIntArray(R.array.config_rearDisplayDeviceStates)) + .thenReturn(mRearDisplayStates); + } + if (enableFold) { - when(mMockContext.getResources().getIntArray( - com.android.internal.R.array.config_foldedDeviceStates)) + when(mMockContext.getResources() + .getIntArray(R.array.config_foldedDeviceStates)) .thenReturn(mFoldedStates); } if (enableHalfFold) { - when(mMockContext.getResources().getIntArray( - com.android.internal.R.array.config_halfFoldedDeviceStates)) + when(mMockContext.getResources() + .getIntArray(R.array.config_halfFoldedDeviceStates)) .thenReturn(mHalfFoldedStates); } } - private void build() throws Exception { + private void build() { mMockContext = mock(Context.class); - mMockRes = mock(Resources.class); - when(mMockContext.getResources()).thenReturn((mMockRes)); mMockDeviceStateManager = mock(DeviceStateManager.class); when(mMockContext.getSystemService(DeviceStateManager.class)) .thenReturn(mMockDeviceStateManager); + Resources mockRes = mock(Resources.class); + when(mMockContext.getResources()).thenReturn((mockRes)); mockFold(mSupportFold, mSupportHalfFold); - mMockHandler = mock(Handler.class); - mTarget = new DeviceStateController(mMockContext, mMockHandler, mDelegate); + Handler mockHandler = mock(Handler.class); + mTarget = new DeviceStateController(mMockContext, mockHandler); + mTarget.registerDeviceStateCallback(mDelegate); } } } 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 b0639bffe694..dc12469960ff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1736,7 +1736,7 @@ public class DisplayContentTests extends WindowTestsBase { // No need to apply rotation if the display ignores orientation request. doCallRealMethod().when(displayContent).rotationForActivityInDifferentOrientation(any()); - pinnedActivity.mOrientation = SCREEN_ORIENTATION_LANDSCAPE; + pinnedActivity.setOverrideOrientation(SCREEN_ORIENTATION_LANDSCAPE); displayContent.setIgnoreOrientationRequest(true); assertEquals(WindowConfiguration.ROTATION_UNDEFINED, displayContent.rotationForActivityInDifferentOrientation(pinnedActivity)); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 4ce43e1fc469..ed2b0a36cd5c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -56,6 +56,7 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceStateManager; import android.os.PowerManagerInternal; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; @@ -111,6 +112,7 @@ public class DisplayRotationTests { private ContentResolver mMockResolver; private FakeSettingsProvider mFakeSettingsProvider; private StatusBarManagerInternal mMockStatusBarManagerInternal; + private DeviceStateManager mMockDeviceStateManager; // Fields below are callbacks captured from test target. private ContentObserver mShowRotationSuggestionsObserver; @@ -120,6 +122,7 @@ public class DisplayRotationTests { private DisplayRotationBuilder mBuilder; + private DeviceStateController mDeviceStateController; private DisplayRotation mTarget; @BeforeClass @@ -484,6 +487,34 @@ public class DisplayRotationTests { SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); } + @Test + public void testReverseRotation() throws Exception { + mBuilder.build(); + configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false); + + when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true); + + thawRotation(); + + enableOrientationSensor(); + + mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90)); + assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation( + SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); + + mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_270)); + assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation( + SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); + + mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_0)); + assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation( + SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); + + mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180)); + assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation( + SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_180)); + } + private boolean waitForUiHandler() { final CountDownLatch latch = new CountDownLatch(1); UiThread.getHandler().post(latch::countDown); @@ -705,7 +736,7 @@ public class DisplayRotationTests { enableOrientationSensor(); - mTarget.foldStateChanged(DeviceStateController.FoldState.OPEN); + mTarget.foldStateChanged(DeviceStateController.DeviceState.OPEN); freezeRotation(Surface.ROTATION_270); mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_0)); @@ -715,7 +746,7 @@ public class DisplayRotationTests { SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); // ... until half-fold - mTarget.foldStateChanged(DeviceStateController.FoldState.HALF_FOLDED); + mTarget.foldStateChanged(DeviceStateController.DeviceState.HALF_FOLDED); assertTrue(waitForUiHandler()); verify(sMockWm).updateRotation(false, false); assertTrue(waitForUiHandler()); @@ -723,7 +754,7 @@ public class DisplayRotationTests { SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); // ... then transition back to flat - mTarget.foldStateChanged(DeviceStateController.FoldState.OPEN); + mTarget.foldStateChanged(DeviceStateController.DeviceState.OPEN); assertTrue(waitForUiHandler()); verify(sMockWm, atLeast(1)).updateRotation(false, false); assertTrue(waitForUiHandler()); @@ -1097,8 +1128,14 @@ public class DisplayRotationTests { mMockDisplayWindowSettings = mock(DisplayWindowSettings.class); + mMockDeviceStateManager = mock(DeviceStateManager.class); + when(mMockContext.getSystemService(eq(DeviceStateManager.class))) + .thenReturn(mMockDeviceStateManager); + + mDeviceStateController = mock(DeviceStateController.class); mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayAddress, - mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object()) { + mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object(), + mDeviceStateController) { @Override DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy( WindowManagerService service, DisplayContent displayContent) { diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 5e087f06b36b..c7f19fb1099d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -16,33 +16,61 @@ package com.android.server.wm; +import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; +import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR; +import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; +import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +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.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; +import android.annotation.Nullable; import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; +import android.content.res.Resources; +import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.RoundedCorner; +import android.view.RoundedCorners; +import android.view.WindowManager; import androidx.test.filters.SmallTest; +import com.android.internal.R; + import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.Before; @@ -61,14 +89,24 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(WindowTestRunner.class) public class LetterboxUiControllerTest extends WindowTestsBase { + private static final int TASKBAR_COLLAPSED_HEIGHT = 10; + private static final int TASKBAR_EXPANDED_HEIGHT = 20; + private static final int SCREEN_WIDTH = 200; + private static final int SCREEN_HEIGHT = 100; + private static final Rect TASKBAR_COLLAPSED_BOUNDS = new Rect(0, + SCREEN_HEIGHT - TASKBAR_COLLAPSED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT); + private static final Rect TASKBAR_EXPANDED_BOUNDS = new Rect(0, + SCREEN_HEIGHT - TASKBAR_EXPANDED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT); @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); private ActivityRecord mActivity; + private Task mTask; private DisplayContent mDisplayContent; private LetterboxUiController mController; private LetterboxConfiguration mLetterboxConfiguration; + private final Rect mLetterboxedPortraitTaskBounds = new Rect(); @Before public void setUp() throws Exception { @@ -308,6 +346,286 @@ public class LetterboxUiControllerTest extends WindowTestsBase { assertTrue(mController.shouldForceRotateForCameraCompat()); } + @Test + public void testGetCropBoundsIfNeeded_noCrop() { + final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar); + + // Do not apply crop if taskbar is collapsed + taskbar.setFrame(TASKBAR_COLLAPSED_BOUNDS); + assertNull(mController.getExpandedTaskbarOrNull(mainWindow)); + + mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 4, + SCREEN_WIDTH - SCREEN_WIDTH / 4, SCREEN_HEIGHT - SCREEN_HEIGHT / 4); + + final Rect noCrop = mController.getCropBoundsIfNeeded(mainWindow); + assertNotEquals(null, noCrop); + assertEquals(0, noCrop.left); + assertEquals(0, noCrop.top); + assertEquals(mLetterboxedPortraitTaskBounds.width(), noCrop.right); + assertEquals(mLetterboxedPortraitTaskBounds.height(), noCrop.bottom); + } + + @Test + public void testGetCropBoundsIfNeeded_appliesCrop() { + final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar); + + // Apply crop if taskbar is expanded + taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS); + assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow)); + + mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4, + SCREEN_HEIGHT); + + final Rect crop = mController.getCropBoundsIfNeeded(mainWindow); + assertNotEquals(null, crop); + assertEquals(0, crop.left); + assertEquals(0, crop.top); + assertEquals(mLetterboxedPortraitTaskBounds.width(), crop.right); + assertEquals(mLetterboxedPortraitTaskBounds.height() - TASKBAR_EXPANDED_HEIGHT, + crop.bottom); + } + + @Test + public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() { + final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar); + final float scaling = 2.0f; + + // Apply crop if taskbar is expanded + taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS); + assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow)); + // With SizeCompat scaling + doReturn(true).when(mActivity).inSizeCompatMode(); + mainWindow.mInvGlobalScale = scaling; + + mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4, + SCREEN_HEIGHT); + + final int appWidth = mLetterboxedPortraitTaskBounds.width(); + final int appHeight = mLetterboxedPortraitTaskBounds.height(); + + final Rect crop = mController.getCropBoundsIfNeeded(mainWindow); + assertNotEquals(null, crop); + assertEquals(0, crop.left); + assertEquals(0, crop.top); + assertEquals((int) (appWidth * scaling), crop.right); + assertEquals((int) ((appHeight - TASKBAR_EXPANDED_HEIGHT) * scaling), crop.bottom); + } + + @Test + public void testGetRoundedCornersRadius_withRoundedCornersFromInsets() { + final float invGlobalScale = 0.5f; + final int expectedRadius = 7; + final int configurationRadius = 15; + + final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null); + mainWindow.mInvGlobalScale = invGlobalScale; + final InsetsState insets = mainWindow.getInsetsState(); + + RoundedCorners roundedCorners = new RoundedCorners( + /*topLeft=*/ null, + /*topRight=*/ null, + /*bottomRight=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT, + configurationRadius, /*centerX=*/ 1, /*centerY=*/ 1), + /*bottomLeft=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT, + configurationRadius * 2 /*2 is to test selection of the min radius*/, + /*centerX=*/ 1, /*centerY=*/ 1) + ); + doReturn(roundedCorners).when(insets).getRoundedCorners(); + mLetterboxConfiguration.setLetterboxActivityCornersRadius(-1); + + assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); + } + + @Test + public void testGetRoundedCornersRadius_withLetterboxActivityCornersRadius() { + final float invGlobalScale = 0.5f; + final int expectedRadius = 7; + final int configurationRadius = 15; + + final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null); + mainWindow.mInvGlobalScale = invGlobalScale; + mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius); + + assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); + + } + + @Test + public void testGetRoundedCornersRadius_noScalingApplied() { + final int configurationRadius = 15; + + final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null); + mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius); + + mainWindow.mInvGlobalScale = -1f; + assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow)); + + mainWindow.mInvGlobalScale = 0f; + assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow)); + + mainWindow.mInvGlobalScale = 1f; + assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow)); + } + + private WindowState mockForGetCropBoundsAndRoundedCorners(@Nullable InsetsSource taskbar) { + final WindowState mainWindow = mock(WindowState.class); + final InsetsState insets = mock(InsetsState.class); + final Resources resources = mWm.mContext.getResources(); + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(); + + mainWindow.mInvGlobalScale = 1f; + spyOn(resources); + spyOn(mActivity); + + if (taskbar != null) { + taskbar.setVisible(true); + doReturn(taskbar).when(insets).peekSource(taskbar.getType()); + } + doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds(); + doReturn(true).when(mActivity).isVisible(); + doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + doReturn(insets).when(mainWindow).getInsetsState(); + doReturn(attrs).when(mainWindow).getAttrs(); + doReturn(true).when(mainWindow).isDrawn(); + doReturn(false).when(mainWindow).isLetterboxedForDisplayCutout(); + doReturn(true).when(mainWindow).areAppWindowBoundsLetterboxed(); + doReturn(true).when(mLetterboxConfiguration).isLetterboxActivityCornersRounded(); + doReturn(TASKBAR_EXPANDED_HEIGHT).when(resources).getDimensionPixelSize( + R.dimen.taskbar_frame_height); + + // Need to reinitialise due to the change in resources getDimensionPixelSize output. + mController = new LetterboxUiController(mWm, mActivity); + + return mainWindow; + } + + // overrideOrientationIfNeeded + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) + public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait() + throws Exception { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR}) + public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsNosensor() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_NOSENSOR); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR}) + public void testOverrideOrientationIfNeeded_nosensorOverride_orientationFixed_returnsUnchanged() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT); + } + + @Test + @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE}) + public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationPortraitOrUndefined_returnsUnchanged() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT); + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED); + } + + @Test + @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE}) + public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationLandscape_returnsReverseLandscape() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_LANDSCAPE), + SCREEN_ORIENTATION_REVERSE_LANDSCAPE); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) + public void testOverrideOrientationIfNeeded_portraitOverride_orientationFixed_returnsUnchanged() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_NOSENSOR); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_portraitAndIgnoreFixedOverrides_returnsPortrait() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_PORTRAIT); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_noSensorAndIgnoreFixedOverrides_returnsNosensor() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_NOSENSOR); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) + public void testOverrideOrientationIfNeeded_propertyIsFalse_returnsUnchanged() + throws Exception { + mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED); + } + + // shouldUseDisplayLandscapeNaturalOrientation + + @Test + @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) + public void testShouldUseDisplayLandscapeNaturalOrientation_override_returnsTrue() { + prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation(); + assertTrue(mController.shouldUseDisplayLandscapeNaturalOrientation()); + } + + @Test + @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) + public void testShouldUseDisplayLandscapeNaturalOrientation_overrideAndFalseProperty_returnsFalse() + throws Exception { + mockThatProperty(PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + + prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation(); + assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation()); + } + + @Test + @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) + public void testShouldUseDisplayLandscapeNaturalOrientation_portraitNaturalOrientation_returnsFalse() { + prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation(); + doReturn(ORIENTATION_PORTRAIT).when(mDisplayContent).getNaturalOrientation(); + + assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation()); + } + + @Test + @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) + public void testShouldUseDisplayLandscapeNaturalOrientation_disabledIgnoreOrientationRequest_returnsFalse() { + prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation(); + mDisplayContent.setIgnoreOrientationRequest(false); + + assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation()); + } + + @Test + @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) + public void testShouldUseDisplayLandscapeNaturalOrientation_inMultiWindowMode_returnsFalse() { + prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation(); + + spyOn(mTask); + doReturn(true).when(mTask).inMultiWindowMode(); + + assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation()); + } + private void mockThatProperty(String propertyName, boolean value) throws Exception { Property property = new Property(propertyName, /* value */ value, /* packageName */ "", /* className */ ""); @@ -316,6 +634,12 @@ public class LetterboxUiControllerTest extends WindowTestsBase { doReturn(property).when(pm).getProperty(eq(propertyName), anyString()); } + private void prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation() { + spyOn(mDisplayContent); + doReturn(ORIENTATION_LANDSCAPE).when(mDisplayContent).getNaturalOrientation(); + mDisplayContent.setIgnoreOrientationRequest(true); + } + private void prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch() { doReturn(true).when(mLetterboxConfiguration) .isPolicyForIgnoringRequestedOrientationEnabled(); @@ -325,10 +649,10 @@ public class LetterboxUiControllerTest extends WindowTestsBase { private ActivityRecord setUpActivityWithComponent() { mDisplayContent = new TestDisplayContent .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build(); - Task task = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build(); + mTask = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build(); final ActivityRecord activity = new ActivityBuilder(mAtm) .setOnTop(true) - .setTask(task) + .setTask(mTask) // Set the component to be that of the test class in order to enable compat changes .setComponent(ComponentName.createRelative(mContext, com.android.server.wm.LetterboxUiControllerTest.class.getName())) diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 48084743afde..06e3854088e9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -468,7 +468,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mWm.setRecentsAnimationController(mController); spyOn(mDisplayContent.mFixedRotationTransitionListener); final ActivityRecord recents = mock(ActivityRecord.class); - recents.mOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + recents.setOverrideOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); doReturn(ORIENTATION_PORTRAIT).when(recents) .getRequestedConfigurationOrientation(anyBoolean()); mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recents); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index e5ff91f1ba37..2be193b76524 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -98,7 +98,7 @@ import androidx.test.filters.MediumTest; import com.android.internal.policy.SystemBarUtils; import com.android.internal.statusbar.LetterboxDetails; import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.wm.DeviceStateController.FoldState; +import com.android.server.wm.DeviceStateController.DeviceState; import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; @@ -2313,6 +2313,29 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testDisplayIgnoreOrientationRequest_disabledViaDeviceConfig_orientationRespected() { + // Set up a display in landscape + setUpDisplaySizeWithApp(2800, 1400); + + final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false, + RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT); + activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + spyOn(activity.mWmService.mLetterboxConfiguration); + doReturn(true).when(activity.mWmService.mLetterboxConfiguration) + .isIgnoreOrientationRequestAllowed(); + + // Display should not be rotated. + assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation()); + + doReturn(false).when(activity.mWmService.mLetterboxConfiguration) + .isIgnoreOrientationRequestAllowed(); + + // Display should be rotated. + assertEquals(SCREEN_ORIENTATION_PORTRAIT, activity.mDisplayContent.getOrientation()); + } + + @Test public void testSandboxDisplayApis_unresizableAppNotSandboxed() { // Set up a display in landscape with an unresizable app. setUpDisplaySizeWithApp(2500, 1000); @@ -2584,6 +2607,133 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testIsHorizontalReachabilityEnabled_splitScreen_false() { + mAtm.mDevEnableNonResizableMultiWindow = true; + setUpDisplaySizeWithApp(2800, 1000); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); + + // Unresizable portrait-only activity. + prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT); + + // Move activity to split screen which takes half of the screen. + mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test"); + organizer.mPrimary.setBounds(0, 0, 1400, 1000); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode()); + + // Horizontal reachability is disabled because the app is in split screen. + assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled()); + } + + @Test + public void testIsVerticalReachabilityEnabled_splitScreen_false() { + mAtm.mDevEnableNonResizableMultiWindow = true; + setUpDisplaySizeWithApp(1000, 2800); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); + + // Unresizable landscape-only activity. + prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE); + + // Move activity to split screen which takes half of the screen. + mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test"); + organizer.mPrimary.setBounds(0, 0, 1000, 1400); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode()); + + // Vertical reachability is disabled because the app is in split screen. + assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled()); + } + + @Test + public void testIsVerticalReachabilityEnabled_doesNotMatchParentWidth_false() { + setUpDisplaySizeWithApp(1000, 2800); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + + // Unresizable landscape-only activity. + prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE); + + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + // Activity now in size compat mode. + assertTrue(mActivity.inSizeCompatMode()); + + // Vertical reachability is disabled because the app does not match parent width + assertNotEquals(mActivity.getBounds().width(), mActivity.mDisplayContent.getBounds() + .width()); + assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled()); + } + + @Test + public void testIsHorizontalReachabilityEnabled_doesNotMatchParentHeight_false() { + setUpDisplaySizeWithApp(2800, 1000); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + + // Unresizable portrait-only activity. + prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT); + + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + // Activity now in size compat mode. + assertTrue(mActivity.inSizeCompatMode()); + + // Horizontal reachability is disabled because the app does not match parent height + assertNotEquals(mActivity.getBounds().height(), mActivity.mDisplayContent.getBounds() + .height()); + assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled()); + } + + @Test + public void testIsHorizontalReachabilityEnabled_inSizeCompatMode_matchesParentHeight_true() { + setUpDisplaySizeWithApp(1800, 2200); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + + // Unresizable portrait-only activity. + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + // Activity now in size compat mode. + assertTrue(mActivity.inSizeCompatMode()); + + // Horizontal reachability is enabled because the app matches parent height + assertEquals(mActivity.getBounds().height(), mActivity.mDisplayContent.getBounds() + .height()); + assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled()); + } + + @Test + public void testIsVerticalReachabilityEnabled_inSizeCompatMode_matchesParentWidth_true() { + setUpDisplaySizeWithApp(2200, 1800); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + + // Unresizable landscape-only activity. + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + // Activity now in size compat mode. + assertTrue(mActivity.inSizeCompatMode()); + + // Vertical reachability is enabled because the app matches parent width + assertEquals(mActivity.getBounds().width(), mActivity.mDisplayContent.getBounds().width()); + assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled()); + } + + @Test public void testLetterboxDetailsForStatusBar_noLetterbox() { setUpDisplaySizeWithApp(2800, 1000); addStatusBar(mActivity.mDisplayContent); @@ -2694,7 +2844,7 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mRootWindowContainer.performSurfacePlacement(); final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class); - verify(mTransaction, times(2)).setWindowCrop( + verify(mTransaction, times(2)).setCrop( eq(w1.getSurfaceControl()), cropCapturer.capture() ); @@ -2783,6 +2933,39 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testUpdateResolvedBoundsHorizontalPosition_leftInsets_appCentered() { + // Set up folded display + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1100, 2100) + .setCanRotate(true) + .build(); + display.setIgnoreOrientationRequest(true); + final DisplayPolicy policy = display.getDisplayPolicy(); + DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90, + display.mBaseDisplayHeight, display.mBaseDisplayWidth); + decorInfo.mNonDecorInsets.set(130, 0, 60, 0); + spyOn(policy); + doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90, + display.mBaseDisplayHeight, display.mBaseDisplayWidth); + mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + + setUpApp(display); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + // Resize the display to simulate unfolding in portrait + resizeDisplay(mTask.mDisplayContent, 2200, 1800); + assertTrue(mActivity.inSizeCompatMode()); + + // Simulate real display not taking non-decor insets into consideration + display.getWindowConfiguration().setAppBounds(0, 0, 2200, 1800); + + // Rotate display to landscape + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + // App is centered + assertEquals(mActivity.getBounds(), new Rect(350, 50, 1450, 2150)); + } + + @Test public void testUpdateResolvedBoundsHorizontalPosition_left() { // Display configured as (2800, 1400). assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( @@ -2928,6 +3111,20 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testApplyAspectRatio_containingRatioAlmostEqualToMaxRatio_boundsUnchanged() { + setUpDisplaySizeWithApp(1981, 2576); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + + final Rect originalBounds = new Rect(mActivity.getBounds()); + prepareUnresizable(mActivity, 1.3f, SCREEN_ORIENTATION_UNSPECIFIED); + + // The containing aspect ratio is now 1.3003534, while the desired aspect ratio is 1.3. The + // bounds of the activity should not be changed as the difference is too small + assertEquals(mActivity.getBounds(), originalBounds); + } + + @Test public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() { // When activity width equals parent width, multiplier shouldn't have any effect. assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( @@ -2939,6 +3136,39 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testUpdateResolvedBoundsVerticalPosition_topInsets_appCentered() { + // Set up folded display + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2100, 1100) + .setCanRotate(true) + .build(); + display.setIgnoreOrientationRequest(true); + final DisplayPolicy policy = display.getDisplayPolicy(); + DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90, + display.mBaseDisplayHeight, display.mBaseDisplayWidth); + decorInfo.mNonDecorInsets.set(0, 130, 0, 60); + spyOn(policy); + doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90, + display.mBaseDisplayHeight, display.mBaseDisplayWidth); + mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + + setUpApp(display); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + + // Resize the display to simulate unfolding in portrait + resizeDisplay(mTask.mDisplayContent, 1800, 2200); + assertTrue(mActivity.inSizeCompatMode()); + + // Simulate real display not taking non-decor insets into consideration + display.getWindowConfiguration().setAppBounds(0, 0, 1800, 2200); + + // Rotate display to landscape + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + // App is centered + assertEquals(mActivity.getBounds(), new Rect(50, 350, 2150, 1450)); + } + + @Test public void testUpdateResolvedBoundsVerticalPosition_top() { // Display configured as (1400, 2800). assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity( @@ -3059,9 +3289,9 @@ public class SizeCompatTests extends WindowTestsBase { private void setFoldablePosture(boolean isHalfFolded, boolean isTabletop) { final DisplayRotation r = mActivity.mDisplayContent.getDisplayRotation(); doReturn(isHalfFolded).when(r).isDisplaySeparatingHinge(); - doReturn(false).when(r).isDeviceInPosture(any(FoldState.class), anyBoolean()); + doReturn(false).when(r).isDeviceInPosture(any(DeviceState.class), anyBoolean()); if (isHalfFolded) { - doReturn(true).when(r).isDeviceInPosture(FoldState.HALF_FOLDED, isTabletop); + doReturn(true).when(r).isDeviceInPosture(DeviceState.HALF_FOLDED, isTabletop); } mActivity.recomputeConfiguration(); } 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 d31ae6aeb42a..83be4f0ac20b 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,7 @@ 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.Surface.ROTATION_0; import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; @@ -78,6 +79,12 @@ class TestDisplayContent extends DisplayContent { final InputMonitor inputMonitor = getInputMonitor(); spyOn(inputMonitor); doNothing().when(inputMonitor).resumeDispatchingLw(any()); + + // For devices that set the sysprop ro.bootanim.set_orientation_<display_id> + // See DisplayRotation#readDefaultDisplayRotation for context. + // Without that, meaning of height and width in context of the tests can be swapped if + // the default rotation is 90 or 270. + displayRotation.setRotation(ROTATION_0); } public static class Builder { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index ed7d12384662..2446fc497f04 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -1557,7 +1557,7 @@ public class WindowContainerTests extends WindowTestsBase { @Override int getOrientation() { - return getOrientation(super.mOrientation); + return getOrientation(super.getOverrideOrientation()); } @Override diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 42a5af7fdce3..17c354a8a80e 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -36,6 +36,7 @@ import libcore.io.IoUtils; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; /** @@ -106,6 +107,12 @@ public final class UsbAlsaManager { return false; } + /** + * List of connected MIDI devices + */ + private final HashMap<String, UsbMidiDevice> + mMidiDevices = new HashMap<String, UsbMidiDevice>(); + // UsbMidiDevice for USB peripheral mode (gadget) device private UsbMidiDevice mPeripheralMidiDevice = null; @@ -249,6 +256,8 @@ public final class UsbAlsaManager { } } + addMidiDevice(deviceAddress, usbDevice, parser, cardRec); + logDevices("deviceAdded()"); if (DEBUG) { @@ -256,6 +265,54 @@ public final class UsbAlsaManager { } } + private void addMidiDevice(String deviceAddress, UsbDevice usbDevice, + UsbDescriptorParser parser, AlsaCardsParser.AlsaCardRecord cardRec) { + boolean hasMidi = parser.hasMIDIInterface(); + // UsbHostManager will create UsbDirectMidiDevices instead if MIDI 2 is supported. + boolean hasMidi2 = parser.containsUniversalMidiDeviceEndpoint(); + if (DEBUG) { + Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature); + Slog.d(TAG, "hasMidi2: " + hasMidi2); + } + if (mHasMidiFeature && hasMidi && !hasMidi2) { + Bundle properties = new Bundle(); + String manufacturer = usbDevice.getManufacturerName(); + String product = usbDevice.getProductName(); + String version = usbDevice.getVersion(); + String name; + if (manufacturer == null || manufacturer.isEmpty()) { + name = product; + } else if (product == null || product.isEmpty()) { + name = manufacturer; + } else { + name = manufacturer + " " + product; + } + properties.putString(MidiDeviceInfo.PROPERTY_NAME, name); + properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer); + properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product); + properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version); + properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER, + usbDevice.getSerialNumber()); + properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, cardRec.getCardNum()); + properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, 0 /*deviceNum*/); + properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice); + + int numLegacyMidiInputs = parser.calculateNumLegacyMidiInputs(); + int numLegacyMidiOutputs = parser.calculateNumLegacyMidiOutputs(); + if (DEBUG) { + Slog.d(TAG, "numLegacyMidiInputs: " + numLegacyMidiInputs); + Slog.d(TAG, "numLegacyMidiOutputs:" + numLegacyMidiOutputs); + } + + UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties, + cardRec.getCardNum(), 0 /*device*/, numLegacyMidiInputs, + numLegacyMidiOutputs); + if (usbMidiDevice != null) { + mMidiDevices.put(deviceAddress, usbMidiDevice); + } + } + } + /* package */ synchronized void usbDeviceRemoved(String deviceAddress/*UsbDevice usbDevice*/) { if (DEBUG) { Slog.d(TAG, "deviceRemoved(" + deviceAddress + ")"); @@ -269,6 +326,13 @@ public final class UsbAlsaManager { selectDefaultDevice(); // if there any external devices left, select one of them } + // MIDI + UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress); + if (usbMidiDevice != null) { + Slog.i(TAG, "USB MIDI Device Removed: " + deviceAddress); + IoUtils.closeQuietly(usbMidiDevice); + } + logDevices("usbDeviceRemoved()"); } @@ -324,6 +388,12 @@ public final class UsbAlsaManager { usbAlsaDevice.dump(dump, "alsa_devices", UsbAlsaManagerProto.ALSA_DEVICES); } + for (String deviceAddr : mMidiDevices.keySet()) { + // A UsbMidiDevice does not have a handle to the UsbDevice anymore + mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices", + UsbAlsaManagerProto.MIDI_DEVICES); + } + dump.end(token); } diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index f3892768921c..b3eb28552796 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -444,14 +444,19 @@ public class UsbHostManager { } else { Slog.e(TAG, "Universal Midi Device is null."); } - } - if (parser.containsLegacyMidiDeviceEndpoint()) { - UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext, - newDevice, parser, false, uniqueUsbDeviceIdentifier); - if (midiDevice != null) { - midiDevices.add(midiDevice); - } else { - Slog.e(TAG, "Legacy Midi Device is null."); + + // Use UsbDirectMidiDevice only if this supports MIDI 2.0 as well. + // ALSA removes the audio sound card if MIDI interfaces are removed. + // This means that as long as ALSA is used for audio, MIDI 1.0 USB + // devices should use the ALSA path for MIDI. + if (parser.containsLegacyMidiDeviceEndpoint()) { + midiDevice = UsbDirectMidiDevice.create(mContext, + newDevice, parser, false, uniqueUsbDeviceIdentifier); + if (midiDevice != null) { + midiDevices.add(midiDevice); + } else { + Slog.e(TAG, "Legacy Midi Device is null."); + } } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java index 3f2d8c8ef47c..c6ea22856dc7 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java @@ -79,6 +79,10 @@ public final class UsbConfigDescriptor extends UsbDescriptor { mInterfaceDescriptors.add(interfaceDesc); } + ArrayList<UsbInterfaceDescriptor> getInterfaceDescriptors() { + return mInterfaceDescriptors; + } + private boolean isAudioInterface(UsbInterfaceDescriptor descriptor) { return descriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO && descriptor.getUsbSubclass() == UsbDescriptor.AUDIO_AUDIOSTREAMING; diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index cd6ea681db07..626ce8927158 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -40,6 +40,7 @@ public final class UsbDescriptorParser { private UsbDeviceDescriptor mDeviceDescriptor; private UsbConfigDescriptor mCurConfigDescriptor; private UsbInterfaceDescriptor mCurInterfaceDescriptor; + private UsbEndpointDescriptor mCurEndpointDescriptor; // The AudioClass spec implemented by the AudioClass Interfaces // This may well be different than the overall USB Spec. @@ -165,7 +166,7 @@ public final class UsbDescriptorParser { break; case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT: - descriptor = new UsbEndpointDescriptor(length, type); + descriptor = mCurEndpointDescriptor = new UsbEndpointDescriptor(length, type); if (mCurInterfaceDescriptor != null) { mCurInterfaceDescriptor.addEndpointDescriptor( (UsbEndpointDescriptor) descriptor); @@ -265,6 +266,9 @@ public final class UsbDescriptorParser { + Integer.toHexString(subClass)); break; } + if (mCurEndpointDescriptor != null && descriptor != null) { + mCurEndpointDescriptor.setClassSpecificEndpointDescriptor(descriptor); + } } break; @@ -798,6 +802,84 @@ public final class UsbDescriptorParser { /** * @hide */ + private int calculateNumLegacyMidiPorts(boolean isOutput) { + // Only look at the first config. + UsbConfigDescriptor configDescriptor = null; + for (UsbDescriptor descriptor : mDescriptors) { + if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CONFIG) { + if (descriptor instanceof UsbConfigDescriptor) { + configDescriptor = (UsbConfigDescriptor) descriptor; + break; + } else { + Log.w(TAG, "Unrecognized Config l: " + descriptor.getLength() + + " t:0x" + Integer.toHexString(descriptor.getType())); + } + } + } + if (configDescriptor == null) { + Log.w(TAG, "Config not found"); + return 0; + } + + ArrayList<UsbInterfaceDescriptor> legacyMidiInterfaceDescriptors = + new ArrayList<UsbInterfaceDescriptor>(); + for (UsbInterfaceDescriptor interfaceDescriptor + : configDescriptor.getInterfaceDescriptors()) { + if (interfaceDescriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO) { + if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { + UsbDescriptor midiHeaderDescriptor = + interfaceDescriptor.getMidiHeaderInterfaceDescriptor(); + if (midiHeaderDescriptor != null) { + if (midiHeaderDescriptor instanceof UsbMSMidiHeader) { + UsbMSMidiHeader midiHeader = + (UsbMSMidiHeader) midiHeaderDescriptor; + if (midiHeader.getMidiStreamingClass() == MS_MIDI_1_0) { + legacyMidiInterfaceDescriptors.add(interfaceDescriptor); + } + } + } + } + } + } + + int count = 0; + for (UsbInterfaceDescriptor interfaceDescriptor : legacyMidiInterfaceDescriptors) { + for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(i); + // 0 is output, 1 << 7 is input. + if ((endpoint.getDirection() == 0) == isOutput) { + UsbDescriptor classSpecificEndpointDescriptor = + endpoint.getClassSpecificEndpointDescriptor(); + if (classSpecificEndpointDescriptor != null + && (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) { + UsbACMidi10Endpoint midiEndpoint = + (UsbACMidi10Endpoint) classSpecificEndpointDescriptor; + count += midiEndpoint.getNumJacks(); + } + } + } + } + return count; + } + + /** + * @hide + */ + public int calculateNumLegacyMidiInputs() { + return calculateNumLegacyMidiPorts(false /*isOutput*/); + } + + /** + * @hide + */ + public int calculateNumLegacyMidiOutputs() { + return calculateNumLegacyMidiPorts(true /*isOutput*/); + } + + /** + * @hide + */ public float getInputHeadsetProbability() { if (hasMIDIInterface()) { return 0.0f; diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java index ab07ce7fdb7a..1f448acac5e8 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java @@ -79,6 +79,8 @@ public class UsbEndpointDescriptor extends UsbDescriptor { private byte mRefresh; private byte mSyncAddress; + private UsbDescriptor mClassSpecificEndpointDescriptor; + public UsbEndpointDescriptor(int length, byte type) { super(length, type); mHierarchyLevel = 4; @@ -112,6 +114,14 @@ public class UsbEndpointDescriptor extends UsbDescriptor { return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION; } + void setClassSpecificEndpointDescriptor(UsbDescriptor descriptor) { + mClassSpecificEndpointDescriptor = descriptor; + } + + UsbDescriptor getClassSpecificEndpointDescriptor() { + return mClassSpecificEndpointDescriptor; + } + /** * Returns a UsbEndpoint that this UsbEndpointDescriptor is describing. */ |