diff options
690 files changed, 18185 insertions, 9259 deletions
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java index 7e814af3451d..1403ba2744b3 100644 --- a/core/java/android/animation/AnimationHandler.java +++ b/core/java/android/animation/AnimationHandler.java @@ -432,8 +432,9 @@ public class AnimationHandler { /** * Callbacks that receives notifications for animation timing and frame commit timing. + * @hide */ - interface AnimationFrameCallback { + public interface AnimationFrameCallback { /** * Run animation based on the frame time. * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index f320b742a430..e8379205d55f 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -9591,21 +9591,16 @@ public class Notification implements Parcelable @NonNull public ArrayList<Action> getActionsListWithSystemActions() { // Define the system actions we expect to see - final Action negativeAction = makeNegativeAction(); - final Action answerAction = makeAnswerAction(); - // Sort the expected actions into the correct order: - // * If there's no answer action, put the hang up / decline action at the end - // * Otherwise put the answer action at the end, and put the decline action at start. - final Action firstAction = answerAction == null ? null : negativeAction; - final Action lastAction = answerAction == null ? negativeAction : answerAction; + final Action firstAction = makeNegativeAction(); + final Action lastAction = makeAnswerAction(); // Start creating the result list. int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS; ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS); - if (firstAction != null) { - resultActions.add(firstAction); - --nonContextualActionSlotsRemaining; - } + + // Always have a first action. + resultActions.add(firstAction); + --nonContextualActionSlotsRemaining; // Copy actions into the new list, correcting system actions. if (mBuilder.mActions != null) { @@ -9621,14 +9616,14 @@ public class Notification implements Parcelable --nonContextualActionSlotsRemaining; } // If there's exactly one action slot left, fill it with the lastAction. - if (nonContextualActionSlotsRemaining == 1) { + if (lastAction != null && nonContextualActionSlotsRemaining == 1) { resultActions.add(lastAction); --nonContextualActionSlotsRemaining; } } } // If there are any action slots left, the lastAction still needs to be added. - if (nonContextualActionSlotsRemaining >= 1) { + if (lastAction != null && nonContextualActionSlotsRemaining >= 1) { resultActions.add(lastAction); } return resultActions; diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index e022ca306674..0ea53ce52a52 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -2080,6 +2080,21 @@ public class WallpaperManager { } /** + * Set the live wallpaper for the given screen(s). + * + * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT + * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change + * another user's wallpaper. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) + public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name, + @SetWallpaperFlags int which) { + return setWallpaperComponent(name); + } + + /** * Set the display position of the current wallpaper within any larger space, when * that wallpaper is visible behind the given window. The X and Y offsets * are floating point numbers ranging from 0 to 1, representing where the diff --git a/core/java/android/app/servertransaction/PendingTransactionActions.java b/core/java/android/app/servertransaction/PendingTransactionActions.java index a47fe821cd01..81747782cab2 100644 --- a/core/java/android/app/servertransaction/PendingTransactionActions.java +++ b/core/java/android/app/servertransaction/PendingTransactionActions.java @@ -25,11 +25,12 @@ import android.os.Bundle; import android.os.PersistableBundle; import android.os.TransactionTooLargeException; import android.util.Log; -import android.util.LogWriter; import android.util.Slog; import com.android.internal.util.IndentingPrintWriter; +import java.io.StringWriter; + /** * Container that has data pending to be used at later stages of * {@link android.app.servertransaction.ClientTransaction}. @@ -134,6 +135,16 @@ public class PendingTransactionActions { mDescription = description; } + private String collectBundleStates() { + final StringWriter writer = new StringWriter(); + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + pw.println("Bundle stats:"); + Bundle.dumpStats(pw, mState); + pw.println("PersistableBundle stats:"); + Bundle.dumpStats(pw, mPersistentState); + return writer.toString().stripTrailing(); + } + @Override public void run() { // Tell activity manager we have been stopped. @@ -142,19 +153,24 @@ public class PendingTransactionActions { // TODO(lifecycler): Use interface callback instead of AMS. ActivityClient.getInstance().activityStopped( mActivity.token, mState, mPersistentState, mDescription); - } catch (RuntimeException ex) { - // Dump statistics about bundle to help developers debug - final LogWriter writer = new LogWriter(Log.WARN, TAG); - final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println("Bundle stats:"); - Bundle.dumpStats(pw, mState); - pw.println("PersistableBundle stats:"); - Bundle.dumpStats(pw, mPersistentState); - - if (ex.getCause() instanceof TransactionTooLargeException - && mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) { - Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex); - return; + } catch (RuntimeException runtimeException) { + // Collect the statistics about bundle + final String bundleStats = collectBundleStates(); + + RuntimeException ex = runtimeException; + if (ex.getCause() instanceof TransactionTooLargeException) { + // Embed the stats into exception message to help developers debug if the + // transaction size is too large. + final String message = ex.getMessage() + "\n" + bundleStats; + ex = new RuntimeException(message, ex.getCause()); + if (mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) { + Log.e(TAG, "App sent too much data in instance state, so it was ignored", + ex); + return; + } + } else { + // Otherwise, dump the stats anyway. + Log.w(TAG, bundleStats); } throw ex; } diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index cc303fb1f413..24e47bf9e47c 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -418,14 +418,7 @@ public class AppWidgetHost { AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget); view.setInteractionHandler(mInteractionHandler); view.setAppWidget(appWidgetId, appWidget); - addListener(appWidgetId, view); - RemoteViews views; - try { - views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId); - } catch (RemoteException e) { - throw new RuntimeException("system server dead?", e); - } - view.updateAppWidget(views); + setListener(appWidgetId, view); return view; } @@ -513,13 +506,19 @@ public class AppWidgetHost { * The AppWidgetHost retains a pointer to the newly-created listener. * @param appWidgetId The ID of the app widget for which to add the listener * @param listener The listener interface that deals with actions towards the widget view - * * @hide */ - public void addListener(int appWidgetId, @NonNull AppWidgetHostListener listener) { + public void setListener(int appWidgetId, @NonNull AppWidgetHostListener listener) { synchronized (mListeners) { mListeners.put(appWidgetId, listener); } + RemoteViews views = null; + try { + views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId); + } catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + listener.updateAppWidget(views); } /** diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 0f6010fffcb6..2a47851764da 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -272,6 +272,9 @@ public final class CameraExtensionCharacteristics { @Override public void onServiceConnected(ComponentName component, IBinder binder) { mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder); + if (mProxy == null) { + throw new IllegalStateException("Camera Proxy service is null"); + } try { mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported(); } catch (RemoteException e) { diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 5ca0da2d3f97..8afd6de235a0 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -335,4 +335,16 @@ public abstract class PowerManagerInternal { /** Allows power button to intercept a power key button press. */ public abstract boolean interceptPowerKeyDown(KeyEvent event); + + /** + * Internal version of {@link android.os.PowerManager#nap} which allows for napping while the + * device is not awake. + */ + public abstract void nap(long eventTime, boolean allowWake); + + /** + * Returns true if ambient display is suppressed by any app with any token. This method will + * return false if ambient display is not available. + */ + public abstract boolean isAmbientDisplaySuppressed(); } diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java index f6a7c8eb8c4b..a2fa1392b079 100644 --- a/core/java/android/service/dreams/DreamActivity.java +++ b/core/java/android/service/dreams/DreamActivity.java @@ -44,6 +44,8 @@ import android.text.TextUtils; public class DreamActivity extends Activity { static final String EXTRA_CALLBACK = "binder"; static final String EXTRA_DREAM_TITLE = "title"; + @Nullable + private DreamService.DreamActivityCallbacks mCallback; public DreamActivity() {} @@ -57,11 +59,19 @@ public class DreamActivity extends Activity { } final Bundle extras = getIntent().getExtras(); - final DreamService.DreamActivityCallback callback = - (DreamService.DreamActivityCallback) extras.getBinder(EXTRA_CALLBACK); + mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK); - if (callback != null) { - callback.onActivityCreated(this); + if (mCallback != null) { + mCallback.onActivityCreated(this); } } + + @Override + public void onDestroy() { + if (mCallback != null) { + mCallback.onActivityDestroyed(); + } + + super.onDestroy(); + } } diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 3c1fef02f9ba..cb0dce91589e 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1295,7 +1295,7 @@ public class DreamService extends Service implements Window.Callback { Intent i = new Intent(this, DreamActivity.class); i.setPackage(getApplicationContext().getPackageName()); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallback(mDreamToken)); + i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken)); final ServiceInfo serviceInfo = fetchServiceInfo(this, new ComponentName(this, getClass())); i.putExtra(DreamActivity.EXTRA_DREAM_TITLE, fetchDreamLabel(this, serviceInfo)); @@ -1488,10 +1488,10 @@ public class DreamService extends Service implements Window.Callback { } /** @hide */ - final class DreamActivityCallback extends Binder { + final class DreamActivityCallbacks extends Binder { private final IBinder mActivityDreamToken; - DreamActivityCallback(IBinder token) { + DreamActivityCallbacks(IBinder token) { mActivityDreamToken = token; } @@ -1516,6 +1516,12 @@ public class DreamService extends Service implements Window.Callback { mActivity = activity; onWindowCreated(activity.getWindow()); } + + // If DreamActivity is destroyed, wake up from Dream. + void onActivityDestroyed() { + mActivity = null; + onDestroy(); + } } /** diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl index 56e2486dd626..f46c60fc4f7a 100644 --- a/core/java/android/service/wallpaper/IWallpaperService.aidl +++ b/core/java/android/service/wallpaper/IWallpaperService.aidl @@ -25,6 +25,6 @@ import android.service.wallpaper.IWallpaperConnection; oneway interface IWallpaperService { void attach(IWallpaperConnection connection, IBinder windowToken, int windowType, boolean isPreview, - int reqWidth, int reqHeight, in Rect padding, int displayId); + int reqWidth, int reqHeight, in Rect padding, int displayId, int which); void detach(); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 01749c0bebb9..e5792a9ef4e2 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -18,6 +18,7 @@ package android.service.wallpaper; import static android.app.WallpaperManager.COMMAND_FREEZE; import static android.app.WallpaperManager.COMMAND_UNFREEZE; +import static android.app.WallpaperManager.SetWallpaperFlags; import static android.graphics.Matrix.MSCALE_X; import static android.graphics.Matrix.MSCALE_Y; import static android.graphics.Matrix.MSKEW_X; @@ -2427,7 +2428,7 @@ public abstract class WallpaperService extends Service { @Override public void attach(IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, - int displayId) { + int displayId, @SetWallpaperFlags int which) { mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType, isPreview, reqWidth, reqHeight, padding, displayId); } diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index 44f419a8097d..e407707231ca 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -241,6 +241,8 @@ public class RemoteAnimationTarget implements Parcelable { */ public boolean willShowImeOnTarget; + public int rotationChange; + public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent, Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position, Rect localBounds, Rect screenSpaceBounds, @@ -302,6 +304,7 @@ public class RemoteAnimationTarget implements Parcelable { backgroundColor = in.readInt(); showBackdrop = in.readBoolean(); willShowImeOnTarget = in.readBoolean(); + rotationChange = in.readInt(); } public void setShowBackdrop(boolean shouldShowBackdrop) { @@ -316,6 +319,14 @@ public class RemoteAnimationTarget implements Parcelable { return willShowImeOnTarget; } + public void setRotationChange(int rotationChange) { + this.rotationChange = rotationChange; + } + + public int getRotationChange() { + return rotationChange; + } + @Override public int describeContents() { return 0; @@ -345,6 +356,7 @@ public class RemoteAnimationTarget implements Parcelable { dest.writeInt(backgroundColor); dest.writeBoolean(showBackdrop); dest.writeBoolean(willShowImeOnTarget); + dest.writeInt(rotationChange); } public void dump(PrintWriter pw, String prefix) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4522c0dcf719..8f4a836b6861 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -284,7 +284,7 @@ public final class ViewRootImpl implements ViewParent, * @hide */ public static final boolean LOCAL_LAYOUT = - SystemProperties.getBoolean("persist.debug.local_layout", false); + SystemProperties.getBoolean("persist.debug.local_layout", true); /** * Set this system property to true to force the view hierarchy to render diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index 4f74ca72b4aa..2ae2c09680bf 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -43,6 +43,7 @@ import android.view.ViewGroup; import android.widget.TextView; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import com.android.internal.app.chooser.ChooserTargetInfo; import com.android.internal.app.chooser.DisplayResolveInfo; @@ -86,6 +87,7 @@ public class ChooserListAdapter extends ResolverListAdapter { private final ChooserActivityLogger mChooserActivityLogger; private int mNumShortcutResults = 0; + private final Map<SelectableTargetInfo, LoadDirectShareIconTask> mIconLoaders = new HashMap<>(); private boolean mApplySharingAppLimits; // Reserve spots for incoming direct share targets by adding placeholders @@ -239,7 +241,6 @@ public class ChooserListAdapter extends ResolverListAdapter { mListViewDataChanged = false; } - private void createPlaceHolders() { mNumShortcutResults = 0; mServiceTargets.clear(); @@ -268,12 +269,16 @@ public class ChooserListAdapter extends ResolverListAdapter { holder.bindIcon(info); if (info instanceof SelectableTargetInfo) { // direct share targets should append the application name for a better readout - DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo(); + SelectableTargetInfo sti = (SelectableTargetInfo) info; + DisplayResolveInfo rInfo = sti.getDisplayResolveInfo(); CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : ""; CharSequence extendedInfo = info.getExtendedInfo(); String contentDescription = String.join(" ", info.getDisplayLabel(), extendedInfo != null ? extendedInfo : "", appName); holder.updateContentDescription(contentDescription); + if (!sti.hasDisplayIcon()) { + loadDirectShareIcon(sti); + } } else if (info instanceof DisplayResolveInfo) { DisplayResolveInfo dri = (DisplayResolveInfo) info; if (!dri.hasDisplayIcon()) { @@ -318,6 +323,20 @@ public class ChooserListAdapter extends ResolverListAdapter { } } + private void loadDirectShareIcon(SelectableTargetInfo info) { + LoadDirectShareIconTask task = (LoadDirectShareIconTask) mIconLoaders.get(info); + if (task == null) { + task = createLoadDirectShareIconTask(info); + mIconLoaders.put(info, task); + task.loadIcon(); + } + } + + @VisibleForTesting + protected LoadDirectShareIconTask createLoadDirectShareIconTask(SelectableTargetInfo info) { + return new LoadDirectShareIconTask(info); + } + void updateAlphabeticalList() { new AsyncTask<Void, Void, List<DisplayResolveInfo>>() { @Override @@ -332,7 +351,7 @@ public class ChooserListAdapter extends ResolverListAdapter { Map<String, DisplayResolveInfo> consolidated = new HashMap<>(); for (DisplayResolveInfo info : allTargets) { String resolvedTarget = info.getResolvedComponentName().getPackageName() - + '#' + info.getDisplayLabel(); + + '#' + info.getDisplayLabel(); DisplayResolveInfo multiDri = consolidated.get(resolvedTarget); if (multiDri == null) { consolidated.put(resolvedTarget, info); @@ -341,7 +360,7 @@ public class ChooserListAdapter extends ResolverListAdapter { } else { // create consolidated target from the single DisplayResolveInfo MultiDisplayResolveInfo multiDisplayResolveInfo = - new MultiDisplayResolveInfo(resolvedTarget, multiDri); + new MultiDisplayResolveInfo(resolvedTarget, multiDri); multiDisplayResolveInfo.addTarget(info); consolidated.put(resolvedTarget, multiDisplayResolveInfo); } @@ -731,7 +750,8 @@ public class ChooserListAdapter extends ResolverListAdapter { * Necessary methods to communicate between {@link ChooserListAdapter} * and {@link ChooserActivity}. */ - interface ChooserListCommunicator extends ResolverListCommunicator { + @VisibleForTesting + public interface ChooserListCommunicator extends ResolverListCommunicator { int getMaxRankedTargets(); @@ -739,4 +759,35 @@ public class ChooserListAdapter extends ResolverListAdapter { boolean isSendAction(Intent targetIntent); } + + /** + * Loads direct share targets icons. + */ + @VisibleForTesting + public class LoadDirectShareIconTask extends AsyncTask<Void, Void, Boolean> { + private final SelectableTargetInfo mTargetInfo; + + private LoadDirectShareIconTask(SelectableTargetInfo targetInfo) { + mTargetInfo = targetInfo; + } + + @Override + protected Boolean doInBackground(Void... voids) { + return mTargetInfo.loadIcon(); + } + + @Override + protected void onPostExecute(Boolean isLoaded) { + if (isLoaded) { + notifyDataSetChanged(); + } + } + + /** + * An alias for execute to use with unit tests. + */ + public void loadIcon() { + execute(); + } + } } diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index f6075b008f72..4a1f7eb06c40 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -870,7 +870,12 @@ public class ResolverListAdapter extends BaseAdapter { void onHandlePackagesChanged(ResolverListAdapter listAdapter); } - static class ViewHolder { + /** + * A view holder keeps a reference to a list view and provides functionality for managing its + * state. + */ + @VisibleForTesting + public static class ViewHolder { public View itemView; public Drawable defaultItemViewBackground; @@ -878,7 +883,8 @@ public class ResolverListAdapter extends BaseAdapter { public TextView text2; public ImageView icon; - ViewHolder(View view) { + @VisibleForTesting + public ViewHolder(View view) { itemView = view; defaultItemViewBackground = view.getBackground(); text = (TextView) view.findViewById(com.android.internal.R.id.text1); diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java index 4b9b7cb98dac..d7f3a76c61e0 100644 --- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java +++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java @@ -37,6 +37,7 @@ import android.service.chooser.ChooserTarget; import android.text.SpannableStringBuilder; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.ChooserActivity; import com.android.internal.app.ResolverActivity; import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter; @@ -59,8 +60,11 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { private final String mDisplayLabel; private final PackageManager mPm; private final SelectableTargetInfoCommunicator mSelectableTargetInfoCommunicator; + @GuardedBy("this") + private ShortcutInfo mShortcutInfo; private Drawable mBadgeIcon = null; private CharSequence mBadgeContentDescription; + @GuardedBy("this") private Drawable mDisplayIcon; private final Intent mFillInIntent; private final int mFillInFlags; @@ -78,6 +82,7 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { mModifiedScore = modifiedScore; mPm = mContext.getPackageManager(); mSelectableTargetInfoCommunicator = selectableTargetInfoComunicator; + mShortcutInfo = shortcutInfo; mIsPinned = shortcutInfo != null && shortcutInfo.isPinned(); if (sourceInfo != null) { final ResolveInfo ri = sourceInfo.getResolveInfo(); @@ -92,8 +97,6 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { } } } - // TODO(b/121287224): do this in the background thread, and only for selected targets - mDisplayIcon = getChooserTargetIconDrawable(chooserTarget, shortcutInfo); if (sourceInfo != null) { mBackupResolveInfo = null; @@ -118,7 +121,10 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { mChooserTarget = other.mChooserTarget; mBadgeIcon = other.mBadgeIcon; mBadgeContentDescription = other.mBadgeContentDescription; - mDisplayIcon = other.mDisplayIcon; + synchronized (other) { + mShortcutInfo = other.mShortcutInfo; + mDisplayIcon = other.mDisplayIcon; + } mFillInIntent = fillInIntent; mFillInFlags = flags; mModifiedScore = other.mModifiedScore; @@ -141,6 +147,27 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { return mSourceInfo; } + /** + * Load display icon, if needed. + */ + public boolean loadIcon() { + ShortcutInfo shortcutInfo; + Drawable icon; + synchronized (this) { + shortcutInfo = mShortcutInfo; + icon = mDisplayIcon; + } + boolean shouldLoadIcon = icon == null && shortcutInfo != null; + if (shouldLoadIcon) { + icon = getChooserTargetIconDrawable(mChooserTarget, shortcutInfo); + synchronized (this) { + mDisplayIcon = icon; + mShortcutInfo = null; + } + } + return shouldLoadIcon; + } + private Drawable getChooserTargetIconDrawable(ChooserTarget target, @Nullable ShortcutInfo shortcutInfo) { Drawable directShareIcon = null; @@ -271,10 +298,17 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { } @Override - public Drawable getDisplayIcon(Context context) { + public synchronized Drawable getDisplayIcon(Context context) { return mDisplayIcon; } + /** + * @return true if display icon is available + */ + public synchronized boolean hasDisplayIcon() { + return mDisplayIcon != null; + } + public ChooserTarget getChooserTarget() { return mChooserTarget; } diff --git a/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java new file mode 100644 index 000000000000..d4fe7c8d7f36 --- /dev/null +++ b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java @@ -0,0 +1,815 @@ +/* + * 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.internal.dynamicanimation.animation; + +import android.animation.AnimationHandler; +import android.animation.ValueAnimator; +import android.annotation.FloatRange; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.os.Looper; +import android.util.AndroidRuntimeException; +import android.util.FloatProperty; +import android.view.View; + +import java.util.ArrayList; + +/** + * This class is the base class of physics-based animations. It manages the animation's + * lifecycle such as {@link #start()} and {@link #cancel()}. This base class also handles the common + * setup for all the subclass animations. For example, DynamicAnimation supports adding + * {@link OnAnimationEndListener} and {@link OnAnimationUpdateListener} so that the important + * animation events can be observed through the callbacks. The start conditions for any subclass of + * DynamicAnimation can be set using {@link #setStartValue(float)} and + * {@link #setStartVelocity(float)}. + * + * @param <T> subclass of DynamicAnimation + */ +public abstract class DynamicAnimation<T extends DynamicAnimation<T>> + implements AnimationHandler.AnimationFrameCallback { + + /** + * ViewProperty holds the access of a property of a {@link View}. When an animation is + * created with a {@link ViewProperty} instance, the corresponding property value of the view + * will be updated through this ViewProperty instance. + */ + public abstract static class ViewProperty extends FloatProperty<View> { + private ViewProperty(String name) { + super(name); + } + } + + /** + * View's translationX property. + */ + public static final ViewProperty TRANSLATION_X = new ViewProperty("translationX") { + @Override + public void setValue(View view, float value) { + view.setTranslationX(value); + } + + @Override + public Float get(View view) { + return view.getTranslationX(); + } + }; + + /** + * View's translationY property. + */ + public static final ViewProperty TRANSLATION_Y = new ViewProperty("translationY") { + @Override + public void setValue(View view, float value) { + view.setTranslationY(value); + } + + @Override + public Float get(View view) { + return view.getTranslationY(); + } + }; + + /** + * View's translationZ property. + */ + public static final ViewProperty TRANSLATION_Z = new ViewProperty("translationZ") { + @Override + public void setValue(View view, float value) { + view.setTranslationZ(value); + } + + @Override + public Float get(View view) { + return view.getTranslationZ(); + } + }; + + /** + * View's scaleX property. + */ + public static final ViewProperty SCALE_X = new ViewProperty("scaleX") { + @Override + public void setValue(View view, float value) { + view.setScaleX(value); + } + + @Override + public Float get(View view) { + return view.getScaleX(); + } + }; + + /** + * View's scaleY property. + */ + public static final ViewProperty SCALE_Y = new ViewProperty("scaleY") { + @Override + public void setValue(View view, float value) { + view.setScaleY(value); + } + + @Override + public Float get(View view) { + return view.getScaleY(); + } + }; + + /** + * View's rotation property. + */ + public static final ViewProperty ROTATION = new ViewProperty("rotation") { + @Override + public void setValue(View view, float value) { + view.setRotation(value); + } + + @Override + public Float get(View view) { + return view.getRotation(); + } + }; + + /** + * View's rotationX property. + */ + public static final ViewProperty ROTATION_X = new ViewProperty("rotationX") { + @Override + public void setValue(View view, float value) { + view.setRotationX(value); + } + + @Override + public Float get(View view) { + return view.getRotationX(); + } + }; + + /** + * View's rotationY property. + */ + public static final ViewProperty ROTATION_Y = new ViewProperty("rotationY") { + @Override + public void setValue(View view, float value) { + view.setRotationY(value); + } + + @Override + public Float get(View view) { + return view.getRotationY(); + } + }; + + /** + * View's x property. + */ + public static final ViewProperty X = new ViewProperty("x") { + @Override + public void setValue(View view, float value) { + view.setX(value); + } + + @Override + public Float get(View view) { + return view.getX(); + } + }; + + /** + * View's y property. + */ + public static final ViewProperty Y = new ViewProperty("y") { + @Override + public void setValue(View view, float value) { + view.setY(value); + } + + @Override + public Float get(View view) { + return view.getY(); + } + }; + + /** + * View's z property. + */ + public static final ViewProperty Z = new ViewProperty("z") { + @Override + public void setValue(View view, float value) { + view.setZ(value); + } + + @Override + public Float get(View view) { + return view.getZ(); + } + }; + + /** + * View's alpha property. + */ + public static final ViewProperty ALPHA = new ViewProperty("alpha") { + @Override + public void setValue(View view, float value) { + view.setAlpha(value); + } + + @Override + public Float get(View view) { + return view.getAlpha(); + } + }; + + // Properties below are not RenderThread compatible + /** + * View's scrollX property. + */ + public static final ViewProperty SCROLL_X = new ViewProperty("scrollX") { + @Override + public void setValue(View view, float value) { + view.setScrollX((int) value); + } + + @Override + public Float get(View view) { + return (float) view.getScrollX(); + } + }; + + /** + * View's scrollY property. + */ + public static final ViewProperty SCROLL_Y = new ViewProperty("scrollY") { + @Override + public void setValue(View view, float value) { + view.setScrollY((int) value); + } + + @Override + public Float get(View view) { + return (float) view.getScrollY(); + } + }; + + /** + * The minimum visible change in pixels that can be visible to users. + */ + @SuppressLint("MinMaxConstant") + public static final float MIN_VISIBLE_CHANGE_PIXELS = 1f; + /** + * The minimum visible change in degrees that can be visible to users. + */ + @SuppressLint("MinMaxConstant") + public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES = 1f / 10f; + /** + * The minimum visible change in alpha that can be visible to users. + */ + @SuppressLint("MinMaxConstant") + public static final float MIN_VISIBLE_CHANGE_ALPHA = 1f / 256f; + /** + * The minimum visible change in scale that can be visible to users. + */ + @SuppressLint("MinMaxConstant") + public static final float MIN_VISIBLE_CHANGE_SCALE = 1f / 500f; + + // Use the max value of float to indicate an unset state. + private static final float UNSET = Float.MAX_VALUE; + + // Multiplier to the min visible change value for value threshold + private static final float THRESHOLD_MULTIPLIER = 0.75f; + + // Internal tracking for velocity. + float mVelocity = 0; + + // Internal tracking for value. + float mValue = UNSET; + + // Tracks whether start value is set. If not, the animation will obtain the value at the time + // of starting through the getter and use that as the starting value of the animation. + boolean mStartValueIsSet = false; + + // Target to be animated. + final Object mTarget; + + // View property id. + final FloatProperty mProperty; + + // Package private tracking of animation lifecycle state. Visible to subclass animations. + boolean mRunning = false; + + // Min and max values that defines the range of the animation values. + float mMaxValue = Float.MAX_VALUE; + float mMinValue = -mMaxValue; + + // Last frame time. Always gets reset to -1 at the end of the animation. + private long mLastFrameTime = 0; + + private float mMinVisibleChange; + + // List of end listeners + private final ArrayList<OnAnimationEndListener> mEndListeners = new ArrayList<>(); + + // List of update listeners + private final ArrayList<OnAnimationUpdateListener> mUpdateListeners = new ArrayList<>(); + + // Animation handler used to schedule updates for this animation. + private AnimationHandler mAnimationHandler; + + // Internal state for value/velocity pair. + static class MassState { + float mValue; + float mVelocity; + } + + /** + * Creates a dynamic animation with the given FloatValueHolder instance. + * + * @param floatValueHolder the FloatValueHolder instance to be animated. + */ + DynamicAnimation(final FloatValueHolder floatValueHolder) { + mTarget = null; + mProperty = new FloatProperty("FloatValueHolder") { + @Override + public Float get(Object object) { + return floatValueHolder.getValue(); + } + + @Override + public void setValue(Object object, float value) { + floatValueHolder.setValue(value); + } + }; + mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS; + } + + /** + * Creates a dynamic animation to animate the given property for the given {@link View} + * + * @param object the Object whose property is to be animated + * @param property the property to be animated + */ + + <K> DynamicAnimation(K object, FloatProperty<K> property) { + mTarget = object; + mProperty = property; + if (mProperty == ROTATION || mProperty == ROTATION_X + || mProperty == ROTATION_Y) { + mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES; + } else if (mProperty == ALPHA) { + mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA; + } else if (mProperty == SCALE_X || mProperty == SCALE_Y) { + mMinVisibleChange = MIN_VISIBLE_CHANGE_SCALE; + } else { + mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS; + } + } + + /** + * Sets the start value of the animation. If start value is not set, the animation will get + * the current value for the view's property, and use that as the start value. + * + * @param startValue start value for the animation + * @return the Animation whose start value is being set + */ + @SuppressWarnings("unchecked") + public T setStartValue(float startValue) { + mValue = startValue; + mStartValueIsSet = true; + return (T) this; + } + + /** + * Start velocity of the animation. Default velocity is 0. Unit: change in property per + * second (e.g. pixels per second, scale/alpha value change per second). + * + * <p>Note when using a fixed value as the start velocity (as opposed to getting the velocity + * through touch events), it is recommended to define such a value in dp/second and convert it + * to pixel/second based on the density of the screen to achieve a consistent look across + * different screens. + * + * <p>To convert from dp/second to pixel/second: + * <pre class="prettyprint"> + * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, + * getResources().getDisplayMetrics()); + * </pre> + * + * @param startVelocity start velocity of the animation + * @return the Animation whose start velocity is being set + */ + @SuppressWarnings("unchecked") + public T setStartVelocity(float startVelocity) { + mVelocity = startVelocity; + return (T) this; + } + + /** + * Sets the max value of the animation. Animations will not animate beyond their max value. + * Whether or not animation will come to an end when max value is reached is dependent on the + * child animation's implementation. + * + * @param max maximum value of the property to be animated + * @return the Animation whose max value is being set + */ + @SuppressWarnings("unchecked") + public T setMaxValue(float max) { + // This max value should be checked and handled in the subclass animations, instead of + // assuming the end of the animations when the max/min value is hit in the base class. + // The reason is that hitting max/min value may just be a transient state, such as during + // the spring oscillation. + mMaxValue = max; + return (T) this; + } + + /** + * Sets the min value of the animation. Animations will not animate beyond their min value. + * Whether or not animation will come to an end when min value is reached is dependent on the + * child animation's implementation. + * + * @param min minimum value of the property to be animated + * @return the Animation whose min value is being set + */ + @SuppressWarnings("unchecked") + public T setMinValue(float min) { + mMinValue = min; + return (T) this; + } + + /** + * Adds an end listener to the animation for receiving onAnimationEnd callbacks. If the listener + * is {@code null} or has already been added to the list of listeners for the animation, no op. + * + * @param listener the listener to be added + * @return the animation to which the listener is added + */ + @SuppressWarnings("unchecked") + public T addEndListener(OnAnimationEndListener listener) { + if (!mEndListeners.contains(listener)) { + mEndListeners.add(listener); + } + return (T) this; + } + + /** + * Removes the end listener from the animation, so as to stop receiving animation end callbacks. + * + * @param listener the listener to be removed + */ + public void removeEndListener(OnAnimationEndListener listener) { + removeEntry(mEndListeners, listener); + } + + /** + * Adds an update listener to the animation for receiving per-frame animation update callbacks. + * If the listener is {@code null} or has already been added to the list of listeners for the + * animation, no op. + * + * <p>Note that update listener should only be added before the start of the animation. + * + * @param listener the listener to be added + * @return the animation to which the listener is added + * @throws UnsupportedOperationException if the update listener is added after the animation has + * started + */ + @SuppressWarnings("unchecked") + public T addUpdateListener(OnAnimationUpdateListener listener) { + if (isRunning()) { + // Require update listener to be added before the animation, such as when we start + // the animation, we know whether the animation is RenderThread compatible. + throw new UnsupportedOperationException("Error: Update listeners must be added before" + + "the animation."); + } + if (!mUpdateListeners.contains(listener)) { + mUpdateListeners.add(listener); + } + return (T) this; + } + + /** + * Removes the update listener from the animation, so as to stop receiving animation update + * callbacks. + * + * @param listener the listener to be removed + */ + public void removeUpdateListener(OnAnimationUpdateListener listener) { + removeEntry(mUpdateListeners, listener); + } + + + /** + * This method sets the minimal change of animation value that is visible to users, which helps + * determine a reasonable threshold for the animation's termination condition. It is critical + * to set the minimal visible change for custom properties (i.e. non-<code>ViewProperty</code>s) + * unless the custom property is in pixels. + * + * <p>For custom properties, this minimum visible change defaults to change in pixel + * (i.e. {@link #MIN_VISIBLE_CHANGE_PIXELS}. It is recommended to adjust this value that is + * reasonable for the property to be animated. A general rule of thumb to calculate such a value + * is: minimum visible change = range of custom property value / corresponding pixel range. For + * example, if the property to be animated is a progress (from 0 to 100) that corresponds to a + * 200-pixel change. Then the min visible change should be 100 / 200. (i.e. 0.5). + * + * <p>It's not necessary to call this method when animating {@link ViewProperty}s, as the + * minimum visible change will be derived from the property. For example, if the property to be + * animated is in pixels (i.e. {@link #TRANSLATION_X}, {@link #TRANSLATION_Y}, + * {@link #TRANSLATION_Z}, @{@link #SCROLL_X} or {@link #SCROLL_Y}), the default minimum visible + * change is 1 (pixel). For {@link #ROTATION}, {@link #ROTATION_X} or {@link #ROTATION_Y}, the + * animation will use {@link #MIN_VISIBLE_CHANGE_ROTATION_DEGREES} as the min visible change, + * which is 1/10. Similarly, the minimum visible change for alpha ( + * i.e. {@link #MIN_VISIBLE_CHANGE_ALPHA} is defined as 1 / 256. + * + * @param minimumVisibleChange minimum change in property value that is visible to users + * @return the animation whose min visible change is being set + * @throws IllegalArgumentException if the given threshold is not positive + */ + @SuppressWarnings("unchecked") + public T setMinimumVisibleChange(@FloatRange(from = 0.0, fromInclusive = false) + float minimumVisibleChange) { + if (minimumVisibleChange <= 0) { + throw new IllegalArgumentException("Minimum visible change must be positive."); + } + mMinVisibleChange = minimumVisibleChange; + setValueThreshold(minimumVisibleChange * THRESHOLD_MULTIPLIER); + return (T) this; + } + + /** + * Returns the minimum change in the animation property that could be visibly different to + * users. + * + * @return minimum change in property value that is visible to users + */ + public float getMinimumVisibleChange() { + return mMinVisibleChange; + } + + /** + * Remove {@code null} entries from the list. + */ + private static <T> void removeNullEntries(ArrayList<T> list) { + // Clean up null entries + for (int i = list.size() - 1; i >= 0; i--) { + if (list.get(i) == null) { + list.remove(i); + } + } + } + + /** + * Remove an entry from the list by marking it {@code null} and clean up later. + */ + private static <T> void removeEntry(ArrayList<T> list, T entry) { + int id = list.indexOf(entry); + if (id >= 0) { + list.set(id, null); + } + } + + /****************Animation Lifecycle Management***************/ + + /** + * Starts an animation. If the animation has already been started, no op. Note that calling + * {@link #start()} will not immediately set the property value to start value of the animation. + * The property values will be changed at each animation pulse, which happens before the draw + * pass. As a result, the changes will be reflected in the next frame, the same as if the values + * were set immediately. This method should only be called on main thread. + * + * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler + * is created on the same thread as the first call to start/cancel an animation. All the + * subsequent animation lifecycle manipulations need to be on that same thread, until the + * AnimationHandler is reset (using [setAnimationHandler]). + * + * @throws AndroidRuntimeException if this method is not called on the same thread as the + * animation handler + */ + @MainThread + public void start() { + if (!isCurrentThread()) { + throw new AndroidRuntimeException("Animations may only be started on the same thread " + + "as the animation handler"); + } + if (!mRunning) { + startAnimationInternal(); + } + } + + boolean isCurrentThread() { + return Thread.currentThread() == Looper.myLooper().getThread(); + } + + /** + * Cancels the on-going animation. If the animation hasn't started, no op. + * + * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler + * is created on the same thread as the first call to start/cancel an animation. All the + * subsequent animation lifecycle manipulations need to be on that same thread, until the + * AnimationHandler is reset (using [setAnimationHandler]). + * + * @throws AndroidRuntimeException if this method is not called on the same thread as the + * animation handler + */ + @MainThread + public void cancel() { + if (!isCurrentThread()) { + throw new AndroidRuntimeException("Animations may only be canceled from the same " + + "thread as the animation handler"); + } + if (mRunning) { + endAnimationInternal(true); + } + } + + /** + * Returns whether the animation is currently running. + * + * @return {@code true} if the animation is currently running, {@code false} otherwise + */ + public boolean isRunning() { + return mRunning; + } + + /************************** Private APIs below ********************************/ + + // This gets called when the animation is started, to finish the setup of the animation + // before the animation pulsing starts. + private void startAnimationInternal() { + if (!mRunning) { + mRunning = true; + if (!mStartValueIsSet) { + mValue = getPropertyValue(); + } + // Sanity check: + if (mValue > mMaxValue || mValue < mMinValue) { + throw new IllegalArgumentException("Starting value need to be in between min" + + " value and max value"); + } + getAnimationHandler().addAnimationFrameCallback(this, 0); + } + } + + /** + * This gets call on each frame of the animation. Animation value and velocity are updated + * in this method based on the new frame time. The property value of the view being animated + * is then updated. The animation's ending conditions are also checked in this method. Once + * the animation reaches equilibrium, the animation will come to its end, and end listeners + * will be notified, if any. + */ + @Override + public boolean doAnimationFrame(long frameTime) { + if (mLastFrameTime == 0) { + // First frame. + mLastFrameTime = frameTime; + setPropertyValue(mValue); + return false; + } + long deltaT = frameTime - mLastFrameTime; + mLastFrameTime = frameTime; + float durationScale = ValueAnimator.getDurationScale(); + deltaT = durationScale == 0.0f ? Integer.MAX_VALUE : (long) (deltaT / durationScale); + boolean finished = updateValueAndVelocity(deltaT); + // Clamp value & velocity. + mValue = Math.min(mValue, mMaxValue); + mValue = Math.max(mValue, mMinValue); + + setPropertyValue(mValue); + + if (finished) { + endAnimationInternal(false); + } + return finished; + } + + @Override + public void commitAnimationFrame(long frameTime) { + doAnimationFrame(frameTime); + } + + /** + * Updates the animation state (i.e. value and velocity). This method is package private, so + * subclasses can override this method to calculate the new value and velocity in their custom + * way. + * + * @param deltaT time elapsed in millisecond since last frame + * @return whether the animation has finished + */ + abstract boolean updateValueAndVelocity(long deltaT); + + /** + * Internal method to reset the animation states when animation is finished/canceled. + */ + private void endAnimationInternal(boolean canceled) { + mRunning = false; + getAnimationHandler().removeCallback(this); + mLastFrameTime = 0; + mStartValueIsSet = false; + for (int i = 0; i < mEndListeners.size(); i++) { + if (mEndListeners.get(i) != null) { + mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity); + } + } + removeNullEntries(mEndListeners); + } + + /** + * Updates the property value through the corresponding setter. + */ + @SuppressWarnings("unchecked") + void setPropertyValue(float value) { + mProperty.setValue(mTarget, value); + for (int i = 0; i < mUpdateListeners.size(); i++) { + if (mUpdateListeners.get(i) != null) { + mUpdateListeners.get(i).onAnimationUpdate(this, mValue, mVelocity); + } + } + removeNullEntries(mUpdateListeners); + } + + /** + * Returns the default threshold. + */ + float getValueThreshold() { + return mMinVisibleChange * THRESHOLD_MULTIPLIER; + } + + /** + * Obtain the property value through the corresponding getter. + */ + @SuppressWarnings("unchecked") + private float getPropertyValue() { + return (Float) mProperty.get(mTarget); + } + + /** + * Returns the {@link AnimationHandler} used to schedule updates for this animator. + * + * @return the {@link AnimationHandler} for this animator. + */ + @NonNull + public AnimationHandler getAnimationHandler() { + return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance(); + } + + /****************Sub class animations**************/ + /** + * Returns the acceleration at the given value with the given velocity. + **/ + abstract float getAcceleration(float value, float velocity); + + /** + * Returns whether the animation has reached equilibrium. + */ + abstract boolean isAtEquilibrium(float value, float velocity); + + /** + * Updates the default value threshold for the animation based on the property to be animated. + */ + abstract void setValueThreshold(float threshold); + + /** + * An animation listener that receives end notifications from an animation. + */ + public interface OnAnimationEndListener { + /** + * Notifies the end of an animation. Note that this callback will be invoked not only when + * an animation reach equilibrium, but also when the animation is canceled. + * + * @param animation animation that has ended or was canceled + * @param canceled whether the animation has been canceled + * @param value the final value when the animation stopped + * @param velocity the final velocity when the animation stopped + */ + void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity); + } + + /** + * Implementors of this interface can add themselves as update listeners + * to an <code>DynamicAnimation</code> instance to receive callbacks on every animation + * frame, after the current frame's values have been calculated for that + * <code>DynamicAnimation</code>. + */ + public interface OnAnimationUpdateListener { + + /** + * Notifies the occurrence of another frame of the animation. + * + * @param animation animation that the update listener is added to + * @param value the current value of the animation + * @param velocity the current velocity of the animation + */ + void onAnimationUpdate(DynamicAnimation animation, float value, float velocity); + } +} diff --git a/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java b/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java new file mode 100644 index 000000000000..c3a2cacd16ec --- /dev/null +++ b/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java @@ -0,0 +1,64 @@ +/* + * 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.internal.dynamicanimation.animation; + +/** + * <p>FloatValueHolder holds a float value. FloatValueHolder provides a setter and a getter ( + * i.e. {@link #setValue(float)} and {@link #getValue()}) to access this float value. Animations can + * be performed on a FloatValueHolder instance. During each frame of the animation, the + * FloatValueHolder will have its value updated via {@link #setValue(float)}. The caller can + * obtain the up-to-date animation value via {@link FloatValueHolder#getValue()}. + * + * @see SpringAnimation#SpringAnimation(FloatValueHolder) + */ + +public class FloatValueHolder { + private float mValue = 0.0f; + + /** + * Constructs a holder for a float value that is initialized to 0. + */ + public FloatValueHolder() { + } + + /** + * Constructs a holder for a float value that is initialized to the input value. + * + * @param value the value to initialize the value held in the FloatValueHolder + */ + public FloatValueHolder(float value) { + setValue(value); + } + + /** + * Sets the value held in the FloatValueHolder instance. + * + * @param value float value held in the FloatValueHolder instance + */ + public void setValue(float value) { + mValue = value; + } + + /** + * Returns the float value held in the FloatValueHolder instance. + * + * @return float value held in the FloatValueHolder instance + */ + public float getValue() { + return mValue; + } +} diff --git a/core/java/com/android/internal/dynamicanimation/animation/Force.java b/core/java/com/android/internal/dynamicanimation/animation/Force.java new file mode 100644 index 000000000000..fcb9c459fff3 --- /dev/null +++ b/core/java/com/android/internal/dynamicanimation/animation/Force.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.dynamicanimation.animation; + +/** + * Hide this for now, in case we want to change the API. + */ +interface Force { + // Acceleration based on position. + float getAcceleration(float position, float velocity); + + boolean isAtEquilibrium(float value, float velocity); +} diff --git a/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java b/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java new file mode 100644 index 000000000000..2f3b72c4f97d --- /dev/null +++ b/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java @@ -0,0 +1,314 @@ +/* + * 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.internal.dynamicanimation.animation; + +import android.util.AndroidRuntimeException; +import android.util.FloatProperty; + +/** + * SpringAnimation is an animation that is driven by a {@link SpringForce}. The spring force defines + * the spring's stiffness, damping ratio, as well as the rest position. Once the SpringAnimation is + * started, on each frame the spring force will update the animation's value and velocity. + * The animation will continue to run until the spring force reaches equilibrium. If the spring used + * in the animation is undamped, the animation will never reach equilibrium. Instead, it will + * oscillate forever. + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * </div> + * + * <p>To create a simple {@link SpringAnimation} that uses the default {@link SpringForce}:</p> + * <pre class="prettyprint"> + * // Create an animation to animate view's X property, set the rest position of the + * // default spring to 0, and start the animation with a starting velocity of 5000 (pixel/s). + * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.X, 0) + * .setStartVelocity(5000); + * anim.start(); + * </pre> + * + * <p>Alternatively, a {@link SpringAnimation} can take a pre-configured {@link SpringForce}, and + * use that to drive the animation. </p> + * <pre class="prettyprint"> + * // Create a low stiffness, low bounce spring at position 0. + * SpringForce spring = new SpringForce(0) + * .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) + * .setStiffness(SpringForce.STIFFNESS_LOW); + * // Create an animation to animate view's scaleY property, and start the animation using + * // the spring above and a starting value of 0.5. Additionally, constrain the range of value for + * // the animation to be non-negative, effectively preventing any spring overshoot. + * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.SCALE_Y) + * .setMinValue(0).setSpring(spring).setStartValue(1); + * anim.start(); + * </pre> + */ +public final class SpringAnimation extends DynamicAnimation<SpringAnimation> { + + private SpringForce mSpring = null; + private float mPendingPosition = UNSET; + private static final float UNSET = Float.MAX_VALUE; + private boolean mEndRequested = false; + + /** + * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During + * the animation, the {@link FloatValueHolder} instance will be updated via + * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date + * animation value via {@link FloatValueHolder#getValue()}. + * + * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via + * {@link FloatValueHolder#setValue(float)} outside of the animation during an + * animation run will not have any effect on the on-going animation. + * + * @param floatValueHolder the property to be animated + */ + public SpringAnimation(FloatValueHolder floatValueHolder) { + super(floatValueHolder); + } + + /** + * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During + * the animation, the {@link FloatValueHolder} instance will be updated via + * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date + * animation value via {@link FloatValueHolder#getValue()}. + * + * A Spring will be created with the given final position and default stiffness and damping + * ratio. This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}. + * + * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via + * {@link FloatValueHolder#setValue(float)} outside of the animation during an + * animation run will not have any effect on the on-going animation. + * + * @param floatValueHolder the property to be animated + * @param finalPosition the final position of the spring to be created. + */ + public SpringAnimation(FloatValueHolder floatValueHolder, float finalPosition) { + super(floatValueHolder); + mSpring = new SpringForce(finalPosition); + } + + /** + * This creates a SpringAnimation that animates the property of the given object. + * Note, a spring will need to setup through {@link #setSpring(SpringForce)} before + * the animation starts. + * + * @param object the Object whose property will be animated + * @param property the property to be animated + * @param <K> the class on which the Property is declared + */ + public <K> SpringAnimation(K object, FloatProperty<K> property) { + super(object, property); + } + + /** + * This creates a SpringAnimation that animates the property of the given object. A Spring will + * be created with the given final position and default stiffness and damping ratio. + * This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}. + * + * @param object the Object whose property will be animated + * @param property the property to be animated + * @param finalPosition the final position of the spring to be created. + * @param <K> the class on which the Property is declared + */ + public <K> SpringAnimation(K object, FloatProperty<K> property, + float finalPosition) { + super(object, property); + mSpring = new SpringForce(finalPosition); + } + + /** + * Returns the spring that the animation uses for animations. + * + * @return the spring that the animation uses for animations + */ + public SpringForce getSpring() { + return mSpring; + } + + /** + * Uses the given spring as the force that drives this animation. If this spring force has its + * parameters re-configured during the animation, the new configuration will be reflected in the + * animation immediately. + * + * @param force a pre-defined spring force that drives the animation + * @return the animation that the spring force is set on + */ + public SpringAnimation setSpring(SpringForce force) { + mSpring = force; + return this; + } + + @Override + public void start() { + sanityCheck(); + mSpring.setValueThreshold(getValueThreshold()); + super.start(); + } + + /** + * Updates the final position of the spring. + * <p/> + * When the animation is running, calling this method would assume the position change of the + * spring as a continuous movement since last frame, which yields more accurate results than + * changing the spring position directly through {@link SpringForce#setFinalPosition(float)}. + * <p/> + * If the animation hasn't started, calling this method will change the spring position, and + * immediately start the animation. + * + * @param finalPosition rest position of the spring + */ + public void animateToFinalPosition(float finalPosition) { + if (isRunning()) { + mPendingPosition = finalPosition; + } else { + if (mSpring == null) { + mSpring = new SpringForce(finalPosition); + } + mSpring.setFinalPosition(finalPosition); + start(); + } + } + + /** + * Cancels the on-going animation. If the animation hasn't started, no op. Note that this method + * should only be called on main thread. + * + * @throws AndroidRuntimeException if this method is not called on the main thread + */ + @Override + public void cancel() { + super.cancel(); + if (mPendingPosition != UNSET) { + if (mSpring == null) { + mSpring = new SpringForce(mPendingPosition); + } else { + mSpring.setFinalPosition(mPendingPosition); + } + mPendingPosition = UNSET; + } + } + + /** + * Skips to the end of the animation. If the spring is undamped, an + * {@link IllegalStateException} will be thrown, as the animation would never reach to an end. + * It is recommended to check {@link #canSkipToEnd()} before calling this method. If animation + * is not running, no-op. + * + * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler + * is created on the same thread as the first call to start/cancel an animation. All the + * subsequent animation lifecycle manipulations need to be on that same thread, until the + * AnimationHandler is reset (using [setAnimationHandler]). + * + * @throws IllegalStateException if the spring is undamped (i.e. damping ratio = 0) + * @throws AndroidRuntimeException if this method is not called on the same thread as the + * animation handler + */ + public void skipToEnd() { + if (!canSkipToEnd()) { + throw new UnsupportedOperationException("Spring animations can only come to an end" + + " when there is damping"); + } + if (!isCurrentThread()) { + throw new AndroidRuntimeException("Animations may only be started on the same thread " + + "as the animation handler"); + } + if (mRunning) { + mEndRequested = true; + } + } + + /** + * Queries whether the spring can eventually come to the rest position. + * + * @return {@code true} if the spring is damped, otherwise {@code false} + */ + public boolean canSkipToEnd() { + return mSpring.mDampingRatio > 0; + } + + /************************ Below are private APIs *************************/ + + private void sanityCheck() { + if (mSpring == null) { + throw new UnsupportedOperationException("Incomplete SpringAnimation: Either final" + + " position or a spring force needs to be set."); + } + double finalPosition = mSpring.getFinalPosition(); + if (finalPosition > mMaxValue) { + throw new UnsupportedOperationException("Final position of the spring cannot be greater" + + " than the max value."); + } else if (finalPosition < mMinValue) { + throw new UnsupportedOperationException("Final position of the spring cannot be less" + + " than the min value."); + } + } + + @Override + boolean updateValueAndVelocity(long deltaT) { + // If user had requested end, then update the value and velocity to end state and consider + // animation done. + if (mEndRequested) { + if (mPendingPosition != UNSET) { + mSpring.setFinalPosition(mPendingPosition); + mPendingPosition = UNSET; + } + mValue = mSpring.getFinalPosition(); + mVelocity = 0; + mEndRequested = false; + return true; + } + + if (mPendingPosition != UNSET) { + // Approximate by considering half of the time spring position stayed at the old + // position, half of the time it's at the new position. + MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT / 2); + mSpring.setFinalPosition(mPendingPosition); + mPendingPosition = UNSET; + + massState = mSpring.updateValues(massState.mValue, massState.mVelocity, deltaT / 2); + mValue = massState.mValue; + mVelocity = massState.mVelocity; + + } else { + MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT); + mValue = massState.mValue; + mVelocity = massState.mVelocity; + } + + mValue = Math.max(mValue, mMinValue); + mValue = Math.min(mValue, mMaxValue); + + if (isAtEquilibrium(mValue, mVelocity)) { + mValue = mSpring.getFinalPosition(); + mVelocity = 0f; + return true; + } + return false; + } + + @Override + float getAcceleration(float value, float velocity) { + return mSpring.getAcceleration(value, velocity); + } + + @Override + boolean isAtEquilibrium(float value, float velocity) { + return mSpring.isAtEquilibrium(value, velocity); + } + + @Override + void setValueThreshold(float threshold) { + } +} diff --git a/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java b/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java new file mode 100644 index 000000000000..36242ae2cf3d --- /dev/null +++ b/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java @@ -0,0 +1,323 @@ +/* + * 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.internal.dynamicanimation.animation; + +import android.annotation.FloatRange; + +/** + * Spring Force defines the characteristics of the spring being used in the animation. + * <p> + * By configuring the stiffness and damping ratio, callers can create a spring with the look and + * feel suits their use case. Stiffness corresponds to the spring constant. The stiffer the spring + * is, the harder it is to stretch it, the faster it undergoes dampening. + * <p> + * Spring damping ratio describes how oscillations in a system decay after a disturbance. + * When damping ratio > 1* (i.e. over-damped), the object will quickly return to the rest position + * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will + * return to equilibrium within the shortest amount of time. When damping ratio is less than 1 + * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without any + * damping (i.e. damping ratio = 0), the mass will oscillate forever. + */ +public final class SpringForce implements Force { + /** + * Stiffness constant for extremely stiff spring. + */ + public static final float STIFFNESS_HIGH = 10_000f; + /** + * Stiffness constant for medium stiff spring. This is the default stiffness for spring force. + */ + public static final float STIFFNESS_MEDIUM = 1500f; + /** + * Stiffness constant for a spring with low stiffness. + */ + public static final float STIFFNESS_LOW = 200f; + /** + * Stiffness constant for a spring with very low stiffness. + */ + public static final float STIFFNESS_VERY_LOW = 50f; + + /** + * Damping ratio for a very bouncy spring. Note for under-damped springs + * (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring. + */ + public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f; + /** + * Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring + * force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio, + * the more bouncy the spring. + */ + public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f; + /** + * Damping ratio for a spring with low bounciness. Note for under-damped springs + * (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness. + */ + public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f; + /** + * Damping ratio for a spring with no bounciness. This damping ratio will create a critically + * damped spring that returns to equilibrium within the shortest amount of time without + * oscillating. + */ + public static final float DAMPING_RATIO_NO_BOUNCY = 1f; + + // This multiplier is used to calculate the velocity threshold given a certain value threshold. + // The idea is that if it takes >= 1 frame to move the value threshold amount, then the velocity + // is a reasonable threshold. + private static final double VELOCITY_THRESHOLD_MULTIPLIER = 1000.0 / 16.0; + + // Natural frequency + double mNaturalFreq = Math.sqrt(STIFFNESS_MEDIUM); + // Damping ratio. + double mDampingRatio = DAMPING_RATIO_MEDIUM_BOUNCY; + + // Value to indicate an unset state. + private static final double UNSET = Double.MAX_VALUE; + + // Indicates whether the spring has been initialized + private boolean mInitialized = false; + + // Threshold for velocity and value to determine when it's reasonable to assume that the spring + // is approximately at rest. + private double mValueThreshold; + private double mVelocityThreshold; + + // Intermediate values to simplify the spring function calculation per frame. + private double mGammaPlus; + private double mGammaMinus; + private double mDampedFreq; + + // Final position of the spring. This must be set before the start of the animation. + private double mFinalPosition = UNSET; + + // Internal state to hold a value/velocity pair. + private final DynamicAnimation.MassState mMassState = new DynamicAnimation.MassState(); + + /** + * Creates a spring force. Note that final position of the spring must be set through + * {@link #setFinalPosition(float)} before the spring animation starts. + */ + public SpringForce() { + // No op. + } + + /** + * Creates a spring with a given final rest position. + * + * @param finalPosition final position of the spring when it reaches equilibrium + */ + public SpringForce(float finalPosition) { + mFinalPosition = finalPosition; + } + + /** + * Sets the stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * {@link #STIFFNESS_MEDIUM}. + * + * @param stiffness non-negative stiffness constant of a spring + * @return the spring force that the given stiffness is set on + * @throws IllegalArgumentException if the given spring stiffness is not positive + */ + public SpringForce setStiffness( + @FloatRange(from = 0.0, fromInclusive = false) float stiffness) { + if (stiffness <= 0) { + throw new IllegalArgumentException("Spring stiffness constant must be positive."); + } + mNaturalFreq = Math.sqrt(stiffness); + // All the intermediate values need to be recalculated. + mInitialized = false; + return this; + } + + /** + * Gets the stiffness of the spring. + * + * @return the stiffness of the spring + */ + public float getStiffness() { + return (float) (mNaturalFreq * mNaturalFreq); + } + + /** + * Spring damping ratio describes how oscillations in a system decay after a disturbance. + * <p> + * When damping ratio > 1 (over-damped), the object will quickly return to the rest position + * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will + * return to equilibrium within the shortest amount of time. When damping ratio is less than 1 + * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without + * any damping (i.e. damping ratio = 0), the mass will oscillate forever. + * <p> + * Default damping ratio is {@link #DAMPING_RATIO_MEDIUM_BOUNCY}. + * + * @param dampingRatio damping ratio of the spring, it should be non-negative + * @return the spring force that the given damping ratio is set on + * @throws IllegalArgumentException if the {@param dampingRatio} is negative. + */ + public SpringForce setDampingRatio(@FloatRange(from = 0.0) float dampingRatio) { + if (dampingRatio < 0) { + throw new IllegalArgumentException("Damping ratio must be non-negative"); + } + mDampingRatio = dampingRatio; + // All the intermediate values need to be recalculated. + mInitialized = false; + return this; + } + + /** + * Returns the damping ratio of the spring. + * + * @return damping ratio of the spring + */ + public float getDampingRatio() { + return (float) mDampingRatio; + } + + /** + * Sets the rest position of the spring. + * + * @param finalPosition rest position of the spring + * @return the spring force that the given final position is set on + */ + public SpringForce setFinalPosition(float finalPosition) { + mFinalPosition = finalPosition; + return this; + } + + /** + * Returns the rest position of the spring. + * + * @return rest position of the spring + */ + public float getFinalPosition() { + return (float) mFinalPosition; + } + + /*********************** Below are private APIs *********************/ + + @Override + public float getAcceleration(float lastDisplacement, float lastVelocity) { + + lastDisplacement -= getFinalPosition(); + + double k = mNaturalFreq * mNaturalFreq; + double c = 2 * mNaturalFreq * mDampingRatio; + + return (float) (-k * lastDisplacement - c * lastVelocity); + } + + @Override + public boolean isAtEquilibrium(float value, float velocity) { + if (Math.abs(velocity) < mVelocityThreshold + && Math.abs(value - getFinalPosition()) < mValueThreshold) { + return true; + } + return false; + } + + /** + * Initialize the string by doing the necessary pre-calculation as well as some sanity check + * on the setup. + * + * @throws IllegalStateException if the final position is not yet set by the time the spring + * animation has started + */ + private void init() { + if (mInitialized) { + return; + } + + if (mFinalPosition == UNSET) { + throw new IllegalStateException("Error: Final position of the spring must be" + + " set before the animation starts"); + } + + if (mDampingRatio > 1) { + // Over damping + mGammaPlus = -mDampingRatio * mNaturalFreq + + mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1); + mGammaMinus = -mDampingRatio * mNaturalFreq + - mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1); + } else if (mDampingRatio >= 0 && mDampingRatio < 1) { + // Under damping + mDampedFreq = mNaturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio); + } + + mInitialized = true; + } + + /** + * Internal only call for Spring to calculate the spring position/velocity using + * an analytical approach. + */ + DynamicAnimation.MassState updateValues(double lastDisplacement, double lastVelocity, + long timeElapsed) { + init(); + + double deltaT = timeElapsed / 1000d; // unit: seconds + lastDisplacement -= mFinalPosition; + double displacement; + double currentVelocity; + if (mDampingRatio > 1) { + // Overdamped + double coeffA = lastDisplacement - (mGammaMinus * lastDisplacement - lastVelocity) + / (mGammaMinus - mGammaPlus); + double coeffB = (mGammaMinus * lastDisplacement - lastVelocity) + / (mGammaMinus - mGammaPlus); + displacement = coeffA * Math.pow(Math.E, mGammaMinus * deltaT) + + coeffB * Math.pow(Math.E, mGammaPlus * deltaT); + currentVelocity = coeffA * mGammaMinus * Math.pow(Math.E, mGammaMinus * deltaT) + + coeffB * mGammaPlus * Math.pow(Math.E, mGammaPlus * deltaT); + } else if (mDampingRatio == 1) { + // Critically damped + double coeffA = lastDisplacement; + double coeffB = lastVelocity + mNaturalFreq * lastDisplacement; + displacement = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT); + currentVelocity = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT) + * -mNaturalFreq + coeffB * Math.pow(Math.E, -mNaturalFreq * deltaT); + } else { + // Underdamped + double cosCoeff = lastDisplacement; + double sinCoeff = (1 / mDampedFreq) * (mDampingRatio * mNaturalFreq + * lastDisplacement + lastVelocity); + displacement = Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT) + * (cosCoeff * Math.cos(mDampedFreq * deltaT) + + sinCoeff * Math.sin(mDampedFreq * deltaT)); + currentVelocity = displacement * -mNaturalFreq * mDampingRatio + + Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT) + * (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT) + + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT)); + } + + mMassState.mValue = (float) (displacement + mFinalPosition); + mMassState.mVelocity = (float) currentVelocity; + return mMassState; + } + + /** + * This threshold defines how close the animation value needs to be before the animation can + * finish. This default value is based on the property being animated, e.g. animations on alpha, + * scale, translation or rotation would have different thresholds. This value should be small + * enough to avoid visual glitch of "jumping to the end". But it shouldn't be so small that + * animations take seconds to finish. + * + * @param threshold the difference between the animation value and final spring position that + * is allowed to end the animation when velocity is very low + */ + void setValueThreshold(double threshold) { + mValueThreshold = Math.abs(threshold); + mVelocityThreshold = mValueThreshold * VELOCITY_THRESHOLD_MULTIPLIER; + } +} diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 5de72409133d..a05062b43d60 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -45,6 +45,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; @@ -223,6 +224,7 @@ public class InteractionJankMonitor { public static final int CUJ_SHADE_CLEAR_ALL = 62; public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63; public static final int CUJ_LOCKSCREEN_OCCLUSION = 64; + public static final int CUJ_RECENTS_SCROLLING = 65; private static final int NO_STATSD_LOGGING = -1; @@ -296,6 +298,7 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING, }; private static volatile InteractionJankMonitor sInstance; @@ -380,7 +383,8 @@ public class InteractionJankMonitor { CUJ_TASKBAR_COLLAPSE, CUJ_SHADE_CLEAR_ALL, CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION, - CUJ_LOCKSCREEN_OCCLUSION + CUJ_LOCKSCREEN_OCCLUSION, + CUJ_RECENTS_SCROLLING }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -893,6 +897,8 @@ public class InteractionJankMonitor { return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION"; case CUJ_LOCKSCREEN_OCCLUSION: return "LOCKSCREEN_OCCLUSION"; + case CUJ_RECENTS_SCROLLING: + return "RECENTS_SCROLLING"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 8b96597767d3..6fed26c4a81d 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -1326,6 +1326,13 @@ public class BatteryStatsImpl extends BatteryStats { LongSamplingCounter mMobileRadioActiveUnknownTime; LongSamplingCounter mMobileRadioActiveUnknownCount; + /** + * The soonest the Mobile Radio stats can be updated due to a mobile radio power state change + * after it was last updated. + */ + @VisibleForTesting + protected static final long MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS = 1000 * 60 * 10; + int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; @GuardedBy("this") @@ -6260,6 +6267,15 @@ public class BatteryStatsImpl extends BatteryStats { } else { mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs); mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs); + + if (mLastModemActivityInfo != null) { + if (elapsedRealtimeMs < mLastModemActivityInfo.getTimestampMillis() + + MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS) { + // Modem Activity info has been collected recently, don't bother + // triggering another update. + return false; + } + } // Tell the caller to collect radio network/power stats. return true; } diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index a7f2aa7cba69..be1c939f0ff8 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -24,6 +24,7 @@ android:gravity="center_vertical" android:orientation="horizontal" android:theme="@style/Theme.DeviceDefault.Notification" + android:importantForAccessibility="no" > <ImageView diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 7c439008622e..46f0d81dc2ac 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -178,7 +178,7 @@ <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"በጣም ብዙ <xliff:g id="CONTENT_TYPE">%s</xliff:g> ለመሰረዝ ተሞክሯል።"</string> <string name="low_memory" product="tablet" msgid="5557552311566179924">"የጡባዊ ተኮ ማከማቻ ሙሉ ነው! ቦታ ነፃ ለማድረግ አንዳንድ ፋይሎች ሰርዝ።"</string> <string name="low_memory" product="watch" msgid="3479447988234030194">"የእጅ ሰዓት ማከማቻ ሙሉ ነው። ቦታ ለማስለቀቅ አንዳንድ ፋይሎችን ይሰርዙ።"</string> - <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV መሣሪያ ማከማቻ ሙሉ ነው። ባዶ ቦታን ነጻ ለማድረግ አንዳንድ ፋይሎችን ይሰርዙ።"</string> + <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV መሣሪያ ማከማቻ ሙሉ ነው። ባዶ ቦታን ነፃ ለማድረግ አንዳንድ ፋይሎችን ይሰርዙ።"</string> <string name="low_memory" product="default" msgid="2539532364144025569">"የስልክ ማከማቻ ሙሉ ነው! ቦታ ነፃ ለማድረግ አንዳንድ ፋይሎች ሰርዝ።"</string> <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{የእውቅና ማረጋገጫ ባለስልጣን ተጭኗል}one{የእውቅና ማረጋገጫ ባለስልጣናት ተጭነዋል}other{የእውቅና ማረጋገጫ ባለስልጣናት ተጭነዋል}}"</string> <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"ባልታወቀ ሶስተኛ ወገን"</string> @@ -1156,7 +1156,7 @@ <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"የግቤት ስልትን ቀይር"</string> <string name="low_internal_storage_view_title" msgid="9024241779284783414">"የማከማቻ ቦታ እያለቀ ነው"</string> <string name="low_internal_storage_view_text" msgid="8172166728369697835">"አንዳንድ የስርዓት ተግባራት ላይሰሩ ይችላሉ"</string> - <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ለስርዓቱ የሚሆን በቂ ቦታ የለም። 250 ሜባ ነጻ ቦታ እንዳለዎት ያረጋግጡና ዳግም ያስጀምሩ።"</string> + <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ለስርዓቱ የሚሆን በቂ ቦታ የለም። 250 ሜባ ነፃ ቦታ እንዳለዎት ያረጋግጡና ዳግም ያስጀምሩ።"</string> <string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> እያሄደ ነው"</string> <string name="app_running_notification_text" msgid="5120815883400228566">"ተጨማሪ መረጃ ለማግኘት ወይም መተግበሪያውን ለማቆም መታ ያድርጉ።"</string> <string name="ok" msgid="2646370155170753815">"እሺ"</string> @@ -1211,7 +1211,7 @@ <string name="aerr_restart" msgid="2789618625210505419">"መተግበሪያውን እንደገና ክፈት"</string> <string name="aerr_report" msgid="3095644466849299308">"ግብረመልስ ይላኩ"</string> <string name="aerr_close" msgid="3398336821267021852">"ዝጋ"</string> - <string name="aerr_mute" msgid="2304972923480211376">"መሣሪያ ዳግም እስኪጀመር ድረስ ድምጽ ያጥፉ"</string> + <string name="aerr_mute" msgid="2304972923480211376">"መሣሪያ ዳግም እስኪጀመር ድረስ ድምፅ ያጥፉ"</string> <string name="aerr_wait" msgid="3198677780474548217">"ጠብቅ"</string> <string name="aerr_close_app" msgid="8318883106083050970">"መተግበሪያን ዝጋ"</string> <string name="anr_title" msgid="7290329487067300120"></string> @@ -1273,7 +1273,7 @@ <string name="dump_heap_ready_text" msgid="5849618132123045516">"የ<xliff:g id="PROC">%1$s</xliff:g> ሂደት ተራጋፊ ክምር ለማጋራት ለእርስዎ ይገኛል። ይጠንቀቁ፦ ይህ ተራጋፊ ክምር ሂደቱ ሊደርስባቸው የሚችለው ማንኛውም የግል መረጃ ሊኖረው ይችላል፣ ይህ እርስዎ የተየቧቸውን ነገሮች ሊያካትት ይችላል።"</string> <string name="sendText" msgid="493003724401350724">"ለፅሁፍ ድርጊት ምረጥ"</string> <string name="volume_ringtone" msgid="134784084629229029">"የስልክ ጥሪ ድምፅ"</string> - <string name="volume_music" msgid="7727274216734955095">"የማህደረ መረጃ ድምጽ መጠን"</string> + <string name="volume_music" msgid="7727274216734955095">"የማህደረ መረጃ ድምፅ መጠን"</string> <string name="volume_music_hint_playing_through_bluetooth" msgid="2614142915948898228">"በብሉቱዝ በኩል ማጫወት"</string> <string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"የፀጥታ የስልክ የደውል ድምፅ ተዘጋጅቷል"</string> <string name="volume_call" msgid="7625321655265747433">"የጥሪ ላይ ድም ፅ መጨመሪያ/መቀነሻ"</string> @@ -1284,7 +1284,7 @@ <string name="volume_icon_description_bluetooth" msgid="7540388479345558400">"የብሉቱዝ ድምፅ መጠን"</string> <string name="volume_icon_description_ringer" msgid="2187800636867423459">"የስልክ ጥሪ ድምፅ መጠን"</string> <string name="volume_icon_description_incall" msgid="4491255105381227919">"የስልክ ጥሪ ድምፅ መጠን"</string> - <string name="volume_icon_description_media" msgid="4997633254078171233">"የማህደረ መረጃ ድምጽ መጠን"</string> + <string name="volume_icon_description_media" msgid="4997633254078171233">"የማህደረ መረጃ ድምፅ መጠን"</string> <string name="volume_icon_description_notification" msgid="579091344110747279">"የማሳወቂያ ክፍልፍል"</string> <string name="ringtone_default" msgid="9118299121288174597">"ነባሪ የስልክ ላይ ጥሪ"</string> <string name="ringtone_default_with_actual" msgid="2709686194556159773">"ነባሪ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string> @@ -1617,7 +1617,7 @@ <string name="default_audio_route_name_headphones" msgid="6954070994792640762">"የጆሮ ማዳመጫዎች"</string> <string name="default_audio_route_name_usb" msgid="895668743163316932">"ዩ ኤስ ቢ"</string> <string name="default_audio_route_category_name" msgid="5241740395748134483">"ስርዓት"</string> - <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"የብሉቱዝ ድምጽ"</string> + <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"የብሉቱዝ ድምፅ"</string> <string name="wireless_display_route_description" msgid="8297563323032966831">"ገመድ አልባ ማሳያ"</string> <string name="media_route_button_content_description" msgid="2299223698196869956">"Cast"</string> <string name="media_route_chooser_title" msgid="6646594924991269208">"ከመሳሪያ ጋር ያገናኙ"</string> @@ -1674,7 +1674,7 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"የመክፈቻ ስርዓተ ጥለቱን <xliff:g id="NUMBER_0">%1$d</xliff:g> ጊዜ በትክክል አልሳሉትም። ከ<xliff:g id="NUMBER_1">%2$d</xliff:g> ተጨማሪ ያልተሳኩ ሙከራዎች በኋላ የኢሜይል መለያ ተጠቅመው ስልክዎን እንዲከፍቱ ይጠየቃሉ።\n\nእባክዎ ከ<xliff:g id="NUMBER_2">%3$d</xliff:g> ሰከንዶች በኋላ እንደገና ይሞክሩ።"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"አስወግድ"</string> - <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ድምጹ ከሚመከረው መጠን በላይ ከፍ ይበል?\n\nበከፍተኛ ድምጽ ለረጅም ጊዜ ማዳመጥ ጆሮዎን ሊጎዳው ይችላል።"</string> + <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ድምጹ ከሚመከረው መጠን በላይ ከፍ ይበል?\n\nበከፍተኛ ድምፅ ለረጅም ጊዜ ማዳመጥ ጆሮዎን ሊጎዳው ይችላል።"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"የተደራሽነት አቋራጭ ጥቅም ላይ ይዋል?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"አቋራጩ ሲበራ ሁለቱንም የድምጽ አዝራሮች ለ3 ሰከንዶች ተጭኖ መቆየት የተደራሽነት ባህሪን ያስጀምረዋል።"</string> <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"የተደራሽነት ባህሪዎች አቋራጭ ይብራ?"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 1280485cf04b..c2f24a541149 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -514,7 +514,7 @@ <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"للسماح للتطبيق بالحصول على قائمة بالحسابات التي يعرفها الهاتف. وقد يتضمن ذلك أي حسابات تم إنشاؤها بواسطة التطبيقات التي ثبتها."</string> <string name="permlab_accessNetworkState" msgid="2349126720783633918">"عرض اتصالات الشبكة"</string> <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"للسماح للتطبيق بعرض معلومات حول اتصالات الشبكة كعرض معلومات عن الشبكات المتوفرة والشبكات المتصلة."</string> - <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"حق الوصول الكامل إلى الشبكة"</string> + <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"الإذن بالوصول الكامل إلى الشبكة"</string> <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"للسماح للتطبيق بإنشاء مقابس شبكات واستخدام بروتوكولات شبكات مخصصة. ويوفر المتصفح وتطبيقات أخرى طرقًا لإرسال البيانات إلى الإنترنت، ولذلك لا يعد هذا الإذن مطلوبًا لإرسال البيانات إلى الإنترنت."</string> <string name="permlab_changeNetworkState" msgid="8945711637530425586">"تغيير اتصال الشبكة"</string> <string name="permdesc_changeNetworkState" msgid="649341947816898736">"للسماح للتطبيق بتغيير حالة اتصال الشبكة."</string> @@ -1272,7 +1272,7 @@ <string name="dump_heap_ready_notification" msgid="2302452262927390268">"نَسْخ الذاكرة <xliff:g id="PROC">%1$s</xliff:g> جاهز"</string> <string name="dump_heap_notification_detail" msgid="8431586843001054050">"تم جمع مقدار كبير من بيانات الذاكرة. انقر للمشاركة."</string> <string name="dump_heap_title" msgid="4367128917229233901">"هل تريد مشاركة نَسْخ الذاكرة؟"</string> - <string name="dump_heap_text" msgid="1692649033835719336">"تجاوزت عملية <xliff:g id="PROC">%1$s</xliff:g> حد الذاكرة المخصص لها وقدره <xliff:g id="SIZE">%2$s</xliff:g>، ويتوفر نَسْخ للذاكرة لمشاركته مع مطور برامج العملية ولكن توخ الحذر حيث قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية يملك التطبيق حق الوصول إليها."</string> + <string name="dump_heap_text" msgid="1692649033835719336">"تجاوزت عملية <xliff:g id="PROC">%1$s</xliff:g> حد الذاكرة المخصص لها وقدره <xliff:g id="SIZE">%2$s</xliff:g>، ويتوفر نَسْخ للذاكرة لمشاركته مع مطور برامج العملية ولكن توخ الحذر حيث قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية يملك التطبيق الإذن بالوصول إليها."</string> <string name="dump_heap_system_text" msgid="6805155514925350849">"تجاوزت عملية <xliff:g id="PROC">%1$s</xliff:g> القيد المفروض على الذاكرة الذي يبلغ <xliff:g id="SIZE">%2$s</xliff:g>. ويتوفّر نَسْخ ذاكرة يمكنك مشاركته. تحذير: قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية حسّاسة يمكن للعملية الوصول إليها، وقد يتضمن معلومات سبق لك كتابتها."</string> <string name="dump_heap_ready_text" msgid="5849618132123045516">"يتوفّر نَسْخ ذاكرة من عملية <xliff:g id="PROC">%1$s</xliff:g> حتى تتمكّن من مشاركته. تحذير: قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية حسّاسة يمكن للعملية الوصول إليها، وقد يتضمن معلومات سبق لك كتابتها."</string> <string name="sendText" msgid="493003724401350724">"اختيار إجراء للنص"</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index c9550c80f8c6..34cac8bfde03 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -171,7 +171,7 @@ <string name="httpErrorBadUrl" msgid="754447723314832538">"অমান্য URLৰ বাবে পৃষ্ঠাটো খুলিব পৰা নগ\'ল।"</string> <string name="httpErrorFile" msgid="3400658466057744084">"ফাইলত খুলিব পৰা নগ\'ল।"</string> <string name="httpErrorFileNotFound" msgid="5191433324871147386">"অনুৰোধ কৰা ফাইলটো বিচাৰি পোৱা নগ\'ল।"</string> - <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"বহুত বেছি অনুৰোধৰ প্ৰক্ৰিয়া চলি আছে৷ অনুগ্ৰহ কৰি পিছত আকৌ চেষ্টা কৰক৷"</string> + <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"বহুত বেছি অনুৰোধৰ প্ৰক্ৰিয়া চলি আছে৷ অনুগ্ৰহ কৰি পাছত আকৌ চেষ্টা কৰক৷"</string> <string name="notification_title" msgid="5783748077084481121">"<xliff:g id="ACCOUNT">%1$s</xliff:g>ত ছাইন ইন কৰাত আসোঁৱাহ"</string> <string name="contentServiceSync" msgid="2341041749565687871">"ছিংক ত্ৰুটি"</string> <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"ছিংক কৰিব নোৱাৰি"</string> @@ -203,7 +203,7 @@ <string name="device_policy_manager_service" msgid="5085762851388850332">"ডিভাইচৰ নীতিৰ পৰিচালক সেৱা"</string> <string name="music_recognition_manager_service" msgid="7481956037950276359">"সংগীত চিনাক্তকৰণ পৰিচালক সেৱা"</string> <string name="factory_reset_warning" msgid="6858705527798047809">"আপোনাৰ ডিভাইচৰ ডেটা মচা হ\'ব"</string> - <string name="factory_reset_message" msgid="2657049595153992213">"এই প্ৰশাসক এপটো ব্যৱহাৰ কৰিব নোৱাৰি। এতিয়া আপোনাৰ ডিভাইচটোৰ ডেটা মচা হ\'ব।\n\nআপোনাৰ কিবা প্ৰশ্ন থাকিলে আপোনাৰ প্ৰতিষ্ঠানৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক।"</string> + <string name="factory_reset_message" msgid="2657049595153992213">"এই প্ৰশাসক এপ্টো ব্যৱহাৰ কৰিব নোৱাৰি। এতিয়া আপোনাৰ ডিভাইচটোৰ ডেটা মচা হ\'ব।\n\nআপোনাৰ কিবা প্ৰশ্ন থাকিলে আপোনাৰ প্ৰতিষ্ঠানৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক।"</string> <string name="printing_disabled_by" msgid="3517499806528864633">"প্ৰিণ্ট কৰা কাৰ্য <xliff:g id="OWNER_APP">%s</xliff:g>এ অক্ষম কৰি ৰাখিছে।"</string> <string name="personal_apps_suspension_title" msgid="7561416677884286600">"কৰ্মস্থানৰ প্ৰ’ফাইলটো অন কৰক"</string> <string name="personal_apps_suspension_text" msgid="6115455688932935597">"আপুনি নিজৰ কৰ্মস্থানৰ প্ৰ’ফাইলটো অন নকৰালৈকে আপোনাৰ ব্যক্তিগত এপ্সমূহ অৱৰোধ কৰা থাকে"</string> @@ -342,23 +342,23 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"স্ক্ৰীনশ্বট লওক"</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="permdesc_statusBar" msgid="5809162768651019642">"স্থিতি দণ্ড অক্ষম কৰিবলৈ বা ছিষ্টেম আইকন আঁতৰাবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"স্থিতি দণ্ড হ\'ব পাৰে"</string> - <string name="permdesc_statusBarService" msgid="6652917399085712557">"নিজকে স্থিতি দণ্ডৰূপে দেখুওৱাবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_statusBarService" msgid="6652917399085712557">"নিজকে স্থিতি দণ্ডৰূপে দেখুওৱাবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_expandStatusBar" msgid="1184232794782141698">"স্থিতি দণ্ড সম্প্ৰসাৰিত বা সংকোচিত কৰক"</string> - <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"স্থিতি দণ্ড বিস্তাৰিত বা সংকুচিত কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"স্থিতি দণ্ড বিস্তাৰিত বা সংকুচিত কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_fullScreenIntent" msgid="4310888199502509104">"কোনো লক কৰি ৰখা ডিভাইচত জাননী পূৰ্ণ স্ক্ৰীনৰ কাৰ্যকলাপ হিচাপে প্ৰদৰ্শন কৰক"</string> <string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"এপ্টোক কোনো লক কৰি ৰখা ডিভাইচত জাননী পূৰ্ণ স্ক্ৰীনৰ কাৰ্যকলাপ হিচাপে প্ৰদৰ্শন কৰিবলৈ অনুমতি দিয়ে"</string> <string name="permlab_install_shortcut" msgid="7451554307502256221">"শ্বৰ্টকাট ইনষ্টল কৰিব পাৰে"</string> - <string name="permdesc_install_shortcut" msgid="4476328467240212503">"এটা এপ্লিকেশ্বনক ব্যৱহাৰকাৰীৰ হস্তক্ষেপৰ অবিহনে গৃহ স্ক্ৰীণ শ্বৰ্টকাট যোগ কৰিবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_install_shortcut" msgid="4476328467240212503">"এটা এপ্লিকেশ্বনক ব্যৱহাৰকাৰীৰ হস্তক্ষেপৰ অবিহনে গৃহ স্ক্ৰীন শ্বৰ্টকাট যোগ কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_uninstall_shortcut" msgid="295263654781900390">"শ্বৰ্টকাট আনইনষ্টল কৰিব পাৰে"</string> - <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"ব্যৱহাৰকাৰীৰ হস্তক্ষেপৰ অবিহনে গৃহ স্ক্ৰীণৰ শ্বৰ্টকাটসমূহ আঁতৰাবলৈ এপ্লিকেশ্বনক অনুমতি দিয়ে।"</string> + <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"ব্যৱহাৰকাৰীৰ হস্তক্ষেপৰ অবিহনে গৃহ স্ক্ৰীনৰ শ্বৰ্টকাটসমূহ আঁতৰাবলৈ এপ্লিকেশ্বনক অনুমতি দিয়ে।"</string> <string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"বহিৰ্গামী কলসমূহ অন্য ক\'ৰবালৈ পঠিয়াওক"</string> <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"এটা বৰ্হিগামী কল কৰি থকাৰ সময়ত ডায়েল কৰা নম্বৰ চাবলৈ আৰু লগতে এটা পৃথক নম্বৰলৈ কল সংযোগ কৰিবলৈ বা সকলোকে একেলগে বন্ধ কৰিবলৈ এপক অনুমতি দিয়ে।"</string> <string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"ফ\'ন কলৰ উত্তৰ দিব পাৰে"</string> - <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"এপটোক অন্তৰ্গামী ফ\'ন কলৰ উত্তৰ দিবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"এপ্টোক অন্তৰ্গামী ফ\'ন কলৰ উত্তৰ দিবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_receiveSms" msgid="505961632050451881">"পাঠ বার্তা (এছএমএছ) বোৰ লাভ কৰক"</string> - <string name="permdesc_receiveSms" msgid="1797345626687832285">"এপটোক এছএমএছ বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ ইয়াৰ অৰ্থ এইটোৱেই যে এপটোৱে আপোনাক বাৰ্তাবোৰ নেদেখুৱাকৈয়ে আপোনাৰ ডিভাইচলৈ পঠিওৱা বাৰ্তাবোৰ নিৰীক্ষণ কৰিব বা মচিব পাৰে৷"</string> + <string name="permdesc_receiveSms" msgid="1797345626687832285">"এপ্টোক এছএমএছ বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ ইয়াৰ অৰ্থ এইটোৱেই যে এপটোৱে আপোনাক বাৰ্তাবোৰ নেদেখুৱাকৈয়ে আপোনাৰ ডিভাইচলৈ পঠিওৱা বাৰ্তাবোৰ নিৰীক্ষণ কৰিব বা মচিব পাৰে৷"</string> <string name="permlab_receiveMms" msgid="4000650116674380275">"পাঠ বার্তা (এমএমএছ) বোৰ লাভ কৰক"</string> <string name="permdesc_receiveMms" msgid="958102423732219710">"এমএমএছ বার্তাবোৰ লাভ আৰু ইয়াৰ প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ এপক অনুমতি দিয়ে। ইয়াৰ অৰ্থ হৈছে এই এপে আপোনাৰ ডিভাইচলৈ প্ৰেৰণ কৰা বার্তাসমূহ আপোনাক নেদেখুৱাকৈয়ে পৰ্যবেক্ষণ আৰু মচিব পাৰে।"</string> <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"চেল সম্প্ৰচাৰ বাৰ্তাসমূহ ফৰৱাৰ্ড কৰক"</string> @@ -368,26 +368,26 @@ <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"চেল সম্প্ৰচাৰৰ বার্তাবোৰ পঢ়ক"</string> <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"আপোনাৰ ডিভাইচে লাভ কৰা চেল সম্প্ৰচাৰৰ বার্তাবোৰ পঢ়িবলৈ এপক অনুমতি দিয়ে। আপোনাক জৰুৰীকালীন পৰিস্থিতিবোৰত সর্তক কৰিবলৈ চেল সম্প্ৰচাৰৰ বার্তাবোৰ প্ৰেৰণ কৰা হয়। জৰুৰীকালীন চেল সম্প্ৰচাৰ লাভ কৰাৰ সময়ত আপোনাৰ ডিভাইচৰ কাৰ্যদক্ষতা বা কাৰ্যপ্ৰণালীত ক্ষতিকাৰক এপবোৰে হস্তক্ষেপ কৰিব পাৰে।"</string> <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"আপুনি সদস্যভুক্ত হোৱা ফীডসমূহ পঢ়ক"</string> - <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"বৰ্তমান ছিংক কৰা ফীডৰ সবিশেষ লাভ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"বৰ্তমান ছিংক কৰা ফীডৰ সবিশেষ লাভ কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_sendSms" msgid="7757368721742014252">"এছএমএছ ৰ বার্তাবোৰ প্ৰেৰণ কৰিব আৰু চাব পাৰে"</string> - <string name="permdesc_sendSms" msgid="6757089798435130769">"এপটোক এছএমএছ বাৰ্তা পঠিয়াবলৈ অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা হ\'ব পাৰে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে বাৰ্তা পঠিয়াই আপোনাৰ পৰা মাচুল কাটিব পাৰে৷"</string> + <string name="permdesc_sendSms" msgid="6757089798435130769">"এপ্টোক এছএমএছ বাৰ্তা পঠিয়াবলৈ অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা হ\'ব পাৰে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে বাৰ্তা পঠিয়াই আপোনাৰ পৰা মাচুল কাটিব পাৰে৷"</string> <string name="permlab_readSms" msgid="5164176626258800297">"আপোনাৰ পাঠ বার্তাবোৰ পঢ়ক (এছএমএছ বা এমএমএছ)"</string> <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"এই এপ্টোৱে আপোনাৰ টেবলেটটোত সংৰক্ষিত আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string> <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"এই এপ্টোৱে আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string> <string name="permdesc_readSms" product="default" msgid="774753371111699782">"এই এপ্টোৱে আপোনাৰ ফ\'নত সংৰক্ষিত আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string> <string name="permlab_receiveWapPush" msgid="4223747702856929056">"পাঠ বার্তা (WAP) বোৰ লাভ কৰক"</string> - <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"এপটোক WAP বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ এই অনুমতিত আপোনালৈ পঠিওৱা বাৰ্তাবোৰ আপোনাক নেদেখুৱাকৈয়ে নিৰীক্ষণ বা মচাৰ সক্ষমতা অন্তৰ্ভুক্ত থাকে৷"</string> + <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"এপ্টোক WAP বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ এই অনুমতিত আপোনালৈ পঠিওৱা বাৰ্তাবোৰ আপোনাক নেদেখুৱাকৈয়ে নিৰীক্ষণ বা মচাৰ সক্ষমতা অন্তৰ্ভুক্ত থাকে৷"</string> <string name="permlab_getTasks" msgid="7460048811831750262">"চলি থকা এপসমূহ বিচাৰি উলিয়াওক"</string> - <string name="permdesc_getTasks" msgid="7388138607018233726">"এপটোক বৰ্তমানে আৰু শেহতীয়াভাৱে চলি থকা কাৰ্যসমূহৰ বিষয়ে তথ্য পুনৰুদ্ধাৰ কৰিবলৈ অনুমতি দিয়ে৷ এইটোৱে এপটোক ডিভাইচটোত কোনবোৰ এপ্লিকেশ্বন ব্যৱহাৰ হৈ আছে তাৰ বিষয়ে তথ্য বিচাৰি উলিয়াবলৈ অনুমতি দিব পাৰে৷"</string> + <string name="permdesc_getTasks" msgid="7388138607018233726">"এপ্টোক বৰ্তমানে আৰু শেহতীয়াভাৱে চলি থকা কাৰ্যসমূহৰ বিষয়ে তথ্য পুনৰুদ্ধাৰ কৰিবলৈ অনুমতি দিয়ে৷ এইটোৱে এপ্টোক ডিভাইচটোত কোনবোৰ এপ্লিকেশ্বন ব্যৱহাৰ হৈ আছে তাৰ বিষয়ে তথ্য বিচাৰি উলিয়াবলৈ অনুমতি দিব পাৰে৷"</string> <string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"প্ৰ\'ফাইল আৰু ডিভাইচৰ গৰাকীসকলক পৰিচালনা কৰিব পাৰে"</string> - <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"প্ৰ\'ফাইলৰ গৰাকী আৰু ডিভাইচৰ গৰাকী ছেট কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"প্ৰ\'ফাইলৰ গৰাকী আৰু ডিভাইচৰ গৰাকী ছেট কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_reorderTasks" msgid="7598562301992923804">"চলি থকা এপসমূহক পুনৰাই ক্ৰমবদ্ধ কৰক"</string> <string name="permdesc_reorderTasks" msgid="8796089937352344183">"গতিবিধিক অগ্ৰভাগ আৰু নেপথ্যলৈ নিবলৈ এপক অনুমতি দিয়ে। এপে এই কার্য আপোনাৰ ইনপুট অবিহনেই কৰিব পাৰে।"</string> <string name="permlab_enableCarMode" msgid="893019409519325311">"গাড়ীৰ ম\'ড সক্ষম কৰক"</string> - <string name="permdesc_enableCarMode" msgid="56419168820473508">"গাড়ী ম\'ড সক্ষম কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string> + <string name="permdesc_enableCarMode" msgid="56419168820473508">"গাড়ী ম\'ড সক্ষম কৰিবলৈ এপ্টোক অনুমতি দিয়ে৷"</string> <string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"অন্য এপবোৰ বন্ধ কৰক"</string> - <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"এপটোক অন্য এপসমূহৰ নেপথ্যৰ প্ৰক্ৰিয়াসমূহ শেষ কৰিবলৈ অনুমতি দিয়ে৷ এই কার্যৰ বাবে অন্য এপসমূহ চলাটো বন্ধ হ\'ব পাৰে৷"</string> - <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"এই এপটো অইন এপৰ ওপৰত প্ৰদৰ্শিত হ\'ব পাৰে"</string> + <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"এপ্টোক অন্য এপসমূহৰ নেপথ্যৰ প্ৰক্ৰিয়াসমূহ শেষ কৰিবলৈ অনুমতি দিয়ে৷ এই কার্যৰ বাবে অন্য এপসমূহ চলাটো বন্ধ হ\'ব পাৰে৷"</string> + <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"এই এপ্টো অইন এপৰ ওপৰত প্ৰদৰ্শিত হ\'ব পাৰে"</string> <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"এই এপ্টো অন্য এপৰ ওপৰত বা স্ক্ৰীনৰ অন্য অংশত প্ৰদৰ্শিত হ\'ব পাৰে। এই কাৰ্যই এপৰ স্বাভাৱিক ব্যৱহাৰত ব্যাঘাত জন্মাব পাৰে আৰু অন্য এপ্সমূহক স্ক্ৰীনত কেনেকৈ দেখা পোৱা যায় সেইটো সলনি কৰিব পাৰে।"</string> <string name="permlab_runInBackground" msgid="541863968571682785">"নেপথ্যত চলিব পাৰে"</string> <string name="permdesc_runInBackground" msgid="4344539472115495141">"এই এপ্টো নেপথ্যত চলিব পাৰে। ইয়াৰ ফলত বেটাৰী সোনকালে শেষ হ’ব পাৰে।"</string> @@ -398,15 +398,15 @@ <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"এপ্টোক মেম’ৰীত নিজৰ বাবে প্ৰয়োজনীয় ঠাই পৃথক কৰিবলৈ অনুমতি দিয়ে। এই কার্যই আপোনাৰ Android TV ডিভাইচটোক লেহেমীয়া কৰি অন্য এপ্সমূহৰ বাবে উপলব্ধ মেম’ৰীক সীমাবদ্ধ কৰিব পাৰে।"</string> <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"মেম\'ৰিত নিজৰ বাবে প্ৰয়োজনীয় ঠাই পৃথক কৰিবলৈ এপক অনুমতি দিয়ে। এই কার্যই ফ\'নৰ কার্যক লেহেমীয়া কৰি অন্য এপবোৰৰ বাবে উপলব্ধ মেম\'ৰিক সীমাবদ্ধ কৰে।"</string> <string name="permlab_foregroundService" msgid="1768855976818467491">"অগ্ৰভূমিৰ সেৱা চলাব পাৰে"</string> - <string name="permdesc_foregroundService" msgid="8720071450020922795">"এপটোক অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_foregroundService" msgid="8720071450020922795">"এপ্টোক অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_getPackageSize" msgid="375391550792886641">"এপৰ ষ্ট’ৰেজৰ খালী ঠাই হিচাপ কৰক"</string> - <string name="permdesc_getPackageSize" msgid="742743530909966782">"এপটোক ইয়াৰ ক\'ড, ডেটা আৰু কেশ্বৰ আকাৰ বিচাৰি উলিয়াবলৈ অনুমতি দিয়ে"</string> + <string name="permdesc_getPackageSize" msgid="742743530909966782">"এপ্টোক ইয়াৰ ক\'ড, ডেটা আৰু কেশ্বৰ আকাৰ বিচাৰি উলিয়াবলৈ অনুমতি দিয়ে"</string> <string name="permlab_writeSettings" msgid="8057285063719277394">"ছিষ্টেম ছেটিংহ সংশোধন কৰক"</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="tablet" msgid="5565659082718177484">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপ্টোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপ্টো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string> <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"ছিষ্টেমে বুটিং সমাপ্ত কৰাৰ লগে লগে এই এপ্টোক নিজে নিজে আৰম্ভ হ’বলৈ অনুমতি দিয়ে। এই কাৰ্যৰ বাবে আপোনাৰ Android TV ডিভাইচটো আৰম্ভ হ’বলৈ দীঘলীয়া সময়ৰ প্ৰয়োজন হ’ব পাৰে আৰু সকলো সময়তে চলি থাকি এপ্টোক সামগ্ৰিকভাৱে ডিভাইচটো লেহেমীয়া কৰিবলৈ দিয়ে।"</string> - <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপটোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপটো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string> + <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপ্টোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপ্টো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string> <string name="permlab_broadcastSticky" msgid="4552241916400572230">"ষ্টিকী ব্ৰ\'ডকাষ্ট পঠিয়াওক"</string> <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"সম্প্ৰচাৰৰ শেষত বাকী ৰোৱা ষ্টিকী ব্ৰ\'ডকাষ্টবোৰ প্ৰেৰণ কৰিবলৈ এপক অনুমতি দিয়ে। ইয়াক অত্য়ধিক ব্যৱহাৰ কৰাৰ ফলত মেম\'ৰি অধিক খৰচ হোৱাৰ বাবে টেবলেট লেহেমীয়া বা অস্থিৰ হৈ পৰে।"</string> <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"এপ্টোক ব্ৰ’ডকাষ্ট শেষ হোৱাৰ পাছত বাকী থকা ষ্টিকী ব্ৰ’ডকাষ্টবোৰ পঠিয়াবলৈ অনুমতি দিয়ে। ইয়াক অত্যধিক ব্যৱহাৰ কৰিলে আপোনাৰ Android TV ডিভাইচটোক অতি বেছি পৰিমাণৰ মেম’ৰী খৰচ কৰাই লেহেমীয়া অথবা অস্থিৰ কৰিব পাৰে।"</string> @@ -464,62 +464,62 @@ <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"কোনো এপ্লিকেশ্বন অথবা সেৱাক কেমেৰা ডিভাইচসমূহ খোলা অথবা বন্ধ কৰাৰ বিষয়ে কলবেকসমূহ গ্ৰহণ কৰিবলৈ অনুমতি দিয়ক।"</string> <string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"যিকোনো কেমেৰা ডিভাইচ খুলি থকা অথবা বন্ধ কৰি থকাৰ সময়ত (কোনো এপ্লিকেশ্বনৰ দ্বাৰা) এই এপ্টোৱে কলবেক গ্ৰহণ কৰিব পাৰে।"</string> <string name="permlab_vibrate" msgid="8596800035791962017">"কম্পন নিয়ন্ত্ৰণ কৰক"</string> - <string name="permdesc_vibrate" msgid="8733343234582083721">"ভাইব্ৰেটৰ নিয়ন্ত্ৰণ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_vibrate" msgid="8733343234582083721">"ভাইব্ৰেটৰ নিয়ন্ত্ৰণ কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permdesc_vibrator_state" msgid="7050024956594170724">"এপ্টোক কম্পন স্থিতিটো এক্সেছ কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_callPhone" msgid="1798582257194643320">"পোনপটীয়াকৈ ফ\'ন নম্বৰলৈ কল কৰক"</string> - <string name="permdesc_callPhone" msgid="5439809516131609109">"আপোনাৰ কোনো ব্যাঘাত নোহোৱাকৈ ফ\'ন নম্বৰবোৰত কল কৰিবলৈ এপক অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা বা কলবোৰ কৰা হ\'ব পাৰে৷ মনত ৰাখিব যে ই এপটোক জৰুৰীকালীন নম্বৰবোৰত কল কৰিবলৈ অনুমতি নিদিয়ে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে কল কৰি আপোনাক টকা খৰছ কৰাব পাৰে৷"</string> + <string name="permdesc_callPhone" msgid="5439809516131609109">"আপোনাৰ কোনো ব্যাঘাত নোহোৱাকৈ ফ\'ন নম্বৰবোৰত কল কৰিবলৈ এপক অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা বা কলবোৰ কৰা হ\'ব পাৰে৷ মনত ৰাখিব যে ই এপ্টোক জৰুৰীকালীন নম্বৰবোৰত কল কৰিবলৈ অনুমতি নিদিয়ে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে কল কৰি আপোনাক টকা খৰছ কৰাব পাৰে৷"</string> <string name="permlab_accessImsCallService" msgid="442192920714863782">"আইএমএছ কল সেৱা ব্যৱহাৰ কৰিব পাৰে"</string> <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"আপোনাৰ হস্তক্ষেপৰ অবিহনে আইএমএছ সেৱা ব্যৱহাৰ কৰি কল কৰিবলৈ এপক অনুমতি দিয়ে।"</string> <string name="permlab_readPhoneState" msgid="8138526903259297969">"ফ\'নৰ স্থিতি আৰু পৰিচয় পঢ়ক"</string> - <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ডিভাইচত থকা ফ\'নৰ সুবিধাসমূহ ব্যৱহাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷ এই অনুমতিয়ে কোনো কল সক্ৰিয় হৈ থাককেই বা নাথাকক আৰু দূৰবৰ্তী নম্বৰটো কলৰ দ্বাৰা সংযোজিত হওকেই বা নহওক এপটোক ফ\'ন নম্বৰ আৰু ডিভাইচৰ পৰিচয় নিৰ্ধাৰণ কৰিবলৈ অনুমতি দিয়ে৷"</string> + <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ডিভাইচত থকা ফ\'নৰ সুবিধাসমূহ ব্যৱহাৰ কৰিবলৈ এপ্টোক অনুমতি দিয়ে৷ এই অনুমতিয়ে কোনো কল সক্ৰিয় হৈ থাককেই বা নাথাকক আৰু দূৰবৰ্তী নম্বৰটো কলৰ দ্বাৰা সংযোজিত হওকেই বা নহওক এপ্টোক ফ\'ন নম্বৰ আৰু ডিভাইচৰ পৰিচয় নিৰ্ধাৰণ কৰিবলৈ অনুমতি দিয়ে৷"</string> <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"প্ৰাথমিক টেলিফ\'নী স্থিতি আৰু পৰিচয় পঢ়ক"</string> <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"এপ্টোক ডিভাইচটোৰ প্ৰাথমিক টেলিফ’নী সুবিধাসমূহ এক্সেছ কৰাৰ অনুমতি দিয়ে।"</string> <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ছিষ্টেমৰ জৰিয়তে কল কৰিব পাৰে"</string> - <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"কল কৰাৰ অভিজ্ঞতাক উন্নত কৰিবলৈ এপটোক ছিষ্টেমৰ জৰিয়তে কলসমূহ কৰিবলৈ দিয়ে।"</string> + <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"কল কৰাৰ অভিজ্ঞতাক উন্নত কৰিবলৈ এপ্টোক ছিষ্টেমৰ জৰিয়তে কলসমূহ কৰিবলৈ দিয়ে।"</string> <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ছিষ্টেমৰ জৰিয়তে কলবোৰ চোৱা আৰু নিয়ন্ত্ৰণ কৰা।"</string> - <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"এপটোক ডিভাইচত চলি থকা কল চাবলৈ আৰু নিয়ন্ত্ৰণ কৰিবলৈ অনুমতি দিয়ে। কলৰ সংখ্যা আৰু কলবোৰৰ স্থিতি ইয়াত অন্তৰ্ভুক্ত হয়।"</string> + <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"এপ্টোক ডিভাইচত চলি থকা কল চাবলৈ আৰু নিয়ন্ত্ৰণ কৰিবলৈ অনুমতি দিয়ে। কলৰ সংখ্যা আৰু কলবোৰৰ স্থিতি ইয়াত অন্তৰ্ভুক্ত হয়।"</string> <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"অডিঅ’ ৰেকৰ্ড কৰাৰ প্ৰতিবন্ধকতাসমূহৰ পৰা ৰেহাই দিয়ক"</string> <string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"অডিঅ’ ৰেকৰ্ড কৰাৰ প্ৰতিবন্ধকতাসমূহৰ পৰা এপ্টোক ৰেহাই দিয়ক।"</string> <string name="permlab_acceptHandover" msgid="2925523073573116523">"অইন এটা এপত আৰম্ভ হোৱা কল এটা অব্যাহত ৰাখিব পাৰে"</string> - <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"এপটোক এনে কল কৰিবলৈ দিয়ে যিটোৰ আৰম্ভণি অইন এটা এপত হৈছিল।"</string> + <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"এপ্টোক এনে কল কৰিবলৈ দিয়ে যিটোৰ আৰম্ভণি অইন এটা এপত হৈছিল।"</string> <string name="permlab_readPhoneNumbers" msgid="5668704794723365628">"ফ\'ন নম্বৰসমূহ পঢ়ে"</string> - <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"এপটোক ডিভাইচটোৰ ফ\'ন নম্বৰসমূহ চাবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"এপ্টোক ডিভাইচটোৰ ফ\'ন নম্বৰসমূহ চাবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_wakeLock" product="automotive" msgid="1904736682319375676">"গাড়ীৰ স্ক্রীনখন অন কৰি ৰখা"</string> <string name="permlab_wakeLock" product="tablet" msgid="1527660973931694000">"টে\'বলেট সুপ্ত অৱস্থালৈ যোৱাত বাধা দিয়ক"</string> <string name="permlab_wakeLock" product="tv" msgid="2856941418123343518">"আপোনাৰ Android TV ডিভাইচটো সুপ্ত অৱস্থালৈ যোৱাত বাধা দিয়ক"</string> <string name="permlab_wakeLock" product="default" msgid="569409726861695115">"ফ\'ন সুপ্ত অৱস্থালৈ যোৱাত বাধা দিয়ক"</string> <string name="permdesc_wakeLock" product="automotive" msgid="5995045369683254571">"এপ্টোক গাড়ীৰ স্ক্রীনখন অন কৰি ৰাখিবলৈ অনুমতি দিয়ে।"</string> - <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"টে\'বলেট সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"টে\'বলেট সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"এপ্টোক আপোনাৰ Android TV ডিভাইচটো সুপ্ত অৱস্থালৈ যোৱাত বাধা দিবলৈ অনুমতি দিয়ে।"</string> - <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"ফ\'ন সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"ফ\'ন সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_transmitIr" msgid="8077196086358004010">"ইনফ্ৰাৰেড ট্ৰান্সমিট কৰিব পাৰে"</string> <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"টে\'বলেটৰ ইনফ্ৰাৰেড ট্ৰান্সমিটাৰ ব্যৱহাৰ কৰিবলৈ এপক অনুমতি দিয়ে।"</string> <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"এপ্টোক আপোনাৰ Android TV ডিভাইচৰ ইনফ্ৰাৰেড ট্ৰান্সমিটাৰ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"ফ\'নৰ ইনফ্ৰাৰেড ট্ৰান্সমিটাৰ ব্যৱহাৰ কৰিবলৈ এপক অনুমতি দিয়ে।"</string> <string name="permlab_setWallpaper" msgid="6959514622698794511">"ৱালপেপাৰ ছেট কৰক"</string> - <string name="permdesc_setWallpaper" msgid="2973996714129021397">"ছিষ্টেমৰ ৱালপেপাৰ ছেট কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_setWallpaper" msgid="2973996714129021397">"ছিষ্টেমৰ ৱালপেপাৰ ছেট কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_setWallpaperHints" msgid="1153485176642032714">"আপোনাৰ ৱালপেপাৰৰ আকাৰ মিলাওক"</string> - <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"ছিষ্টেমৰ ৱালপেপাৰৰ আকাৰ হিণ্ট ছেট কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"ছিষ্টেমৰ ৱালপেপাৰৰ আকাৰ হিণ্ট ছেট কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_setTimeZone" msgid="7922618798611542432">"সময় মণ্ডল ছেট কৰক"</string> - <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"টে\'বলেটৰ সময় মণ্ডল সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"টে\'বলেটৰ সময় মণ্ডল সলনি কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"এপ্টোক আপোনাৰ Android TV ডিভাইচটোৰ সময় মণ্ডল সলনি কৰিবলৈ অনুমতি দিয়ে।"</string> - <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"ফ\'নৰ সময় মণ্ডল সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"ফ\'নৰ সময় মণ্ডল সলনি কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_getAccounts" msgid="5304317160463582791">"ডিভাইচত একাউণ্টবোৰ বিচাৰক"</string> - <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"এপটোক টেবলেটটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string> + <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"এপ্টোক টেবলেটটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string> <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"আপোনাৰ Android TV ডিভাইচটোৰ পৰিচিত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে। আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্ট অন্তৰ্ভুক্ত হ’ব পাৰে।"</string> - <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"এপটোক ফ\'নটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string> + <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"এপ্টোক ফ\'নটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string> <string name="permlab_accessNetworkState" msgid="2349126720783633918">"নেটৱৰ্কৰ সংযোগবোৰ চাওক"</string> - <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"মজুত থকা আৰু সংযোগ হৈ থকা নেটৱৰ্ক সংযোগসমূহৰ বিষয়ে তথ্য চাবলৈ এপটোক অনুমতি দিয়ে৷"</string> + <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"মজুত থকা আৰু সংযোগ হৈ থকা নেটৱৰ্ক সংযোগসমূহৰ বিষয়ে তথ্য চাবলৈ এপ্টোক অনুমতি দিয়ে৷"</string> <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"সম্পূর্ণ নেটৱর্কৰ সুবিধা লাভ কৰিব পাৰে"</string> - <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"এপটোক নেটৱৰ্ক ছ\'কেটবোৰ সৃষ্টি কৰিবলৈ আৰু কাষ্টম নেটৱৰ্ক প্ৰ\'ট\'কল ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে৷ ব্ৰাউজাৰ আৰু অন্য এপ্লিকেশ্বনসমূহে ইণ্টাৰনেটলৈ ডেটা পঠিওৱা মাধ্য়ম প্ৰদান কৰে, গতিকে ইণ্টাৰনেটলৈ ডেটা পঠিয়াবলৈ এই অনুমতিৰ প্ৰয়োজন নাই৷"</string> + <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"এপ্টোক নেটৱৰ্ক ছ\'কেটবোৰ সৃষ্টি কৰিবলৈ আৰু কাষ্টম নেটৱৰ্ক প্ৰ\'ট\'কল ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে৷ ব্ৰাউজাৰ আৰু অন্য এপ্লিকেশ্বনসমূহে ইণ্টাৰনেটলৈ ডেটা পঠিওৱা মাধ্য়ম প্ৰদান কৰে, গতিকে ইণ্টাৰনেটলৈ ডেটা পঠিয়াবলৈ এই অনুমতিৰ প্ৰয়োজন নাই৷"</string> <string name="permlab_changeNetworkState" msgid="8945711637530425586">"নেটৱৰ্কৰ সংযোগ সলনি কৰক"</string> - <string name="permdesc_changeNetworkState" msgid="649341947816898736">"নেটৱৰ্ক সংযোগৰ অৱস্থাটো সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_changeNetworkState" msgid="649341947816898736">"নেটৱৰ্ক সংযোগৰ অৱস্থাটো সলনি কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_changeTetherState" msgid="9079611809931863861">"টেডাৰিং সংযোগ সলনি কৰক"</string> - <string name="permdesc_changeTetherState" msgid="3025129606422533085">"টেডাৰ হৈ থকা ইণ্টাৰনেট সংযোগৰ অৱস্থা সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string> + <string name="permdesc_changeTetherState" msgid="3025129606422533085">"টেডাৰ হৈ থকা ইণ্টাৰনেট সংযোগৰ অৱস্থা সলনি কৰিবলৈ এপ্টোক অনুমতি দিয়ে৷"</string> <string name="permlab_accessWifiState" msgid="5552488500317911052">"ৱাই-ফাইৰ সংযোগবোৰ চাওক"</string> <string name="permdesc_accessWifiState" msgid="6913641669259483363">"ৱাই-ফাই সক্ষম কৰা হ’ল নে নাই আৰু সংযোগ হৈ থকা ৱাই-ফাই ডিভাইচসমূহৰ নামবোৰৰ দৰে ৱাই-ফাইৰ ইণ্টাৰনেট সম্পর্কীয় তথ্য চাবলৈ এপক অনুমতি দিয়ে।"</string> <string name="permlab_changeWifiState" msgid="7947824109713181554">"ৱাই-ফাই সংযোগ কৰক আৰু ইয়াৰ সংযোগ বিচ্ছিন্ন কৰক"</string> - <string name="permdesc_changeWifiState" msgid="7170350070554505384">"এপটোক ৱাই-ফাই এক্সেছ পইণ্টলৈ সংযোগ কৰিবলৈ আৰু তাৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ আৰু ৱাই-ফাই নেটৱৰ্কসমূহৰ বাবে ডিভাইচ কনফিগাৰেশ্বনত সাল-সলনি কৰিবলৈ অনুমতি দিয়ে৷"</string> + <string name="permdesc_changeWifiState" msgid="7170350070554505384">"এপ্টোক ৱাই-ফাই এক্সেছ পইণ্টলৈ সংযোগ কৰিবলৈ আৰু তাৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ আৰু ৱাই-ফাই নেটৱৰ্কসমূহৰ বাবে ডিভাইচ কনফিগাৰেশ্বনত সাল-সলনি কৰিবলৈ অনুমতি দিয়ে৷"</string> <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"ৱাই-ফাই মাল্টিকাষ্ট প্ৰচাৰৰ অনুমতি দিয়ক"</string> <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"কেৱল আপোনাৰ টেবলেটটোৱেই নহয়, মাল্টিকাষ্ট ঠিকনা ব্যৱহাৰ কৰি এটা ৱাই-ফাই নেটৱর্কত থকা আটাইবোৰ ডিভাইচলৈ পঠিওৱা পেকেট লাভ কৰিবলৈ এপ্টোক অনুমতি দিয়ে। এই কার্যই নন মাল্টিকাষ্ট ম\'ডতকৈ অধিক বেটাৰী ব্যৱহাৰ কৰে।"</string> <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"কেৱল আপোনাৰ Android TV ডিভাইচটোৱেই নহয়, মাল্টিকাষ্ট ঠিকনাবোৰ ব্যৱহাৰ কৰি এটা ৱাই-ফাই নেটৱর্কত থকা আটাইবোৰ ডিভাইচলৈ পঠিওৱা পেকেট লাভ কৰিবলৈ এপ্টোক অনুমতি দিয়ে। এই কার্যই নন-মাল্টিকাষ্ট ম’ডতকৈ অধিক পাৱাৰ ব্যৱহাৰ কৰে।"</string> @@ -529,15 +529,15 @@ <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"এপ্টোক আপোনাৰ Android TV ডিভাইচটোত ব্লুটুথ কনফিগাৰ কৰিবলৈ আৰু ৰিম’ট ডিভাইচসমূহ বিচাৰি উলিয়াবলৈ আৰু পেয়াৰ কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"স্থানীয় ব্লুটুথ ফ’ন কনফিগাৰ কৰিবলৈ আৰু দূৰৱৰ্তী ডিভাইচসমূহৰ সৈতে পেয়াৰ কৰিবলৈ আৰু বিচাৰি উলিয়াবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_accessWimaxState" msgid="7029563339012437434">"WiMAXৰ লগত সংযোগ কৰক আৰু ইয়াৰ পৰা সংযোগ বিচ্ছিন্ন কৰক"</string> - <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"WiMAX সক্ষম হৈ আছেনে নাই আৰু সংযোজিত যিকোনো WiMAX নেটৱৰ্কৰ বিষয়ে তথ্য নিৰ্ধাৰণ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string> + <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"WiMAX সক্ষম হৈ আছেনে নাই আৰু সংযোজিত যিকোনো WiMAX নেটৱৰ্কৰ বিষয়ে তথ্য নিৰ্ধাৰণ কৰিবলৈ এপ্টোক অনুমতি দিয়ে৷"</string> <string name="permlab_changeWimaxState" msgid="6223305780806267462">"WiMAXৰ স্থিতি সলনি কৰক"</string> - <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"এপটোক টেবলেটলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা টেবলেটৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string> + <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"এপ্টোক টেবলেটলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা টেবলেটৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string> <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"এপ্টোক আপোনাৰ Android TV ডিভাইচৰ সৈতে সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা আপোনাৰ Android TV ডিভাইচৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে।"</string> - <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"এপটোক ফ\'নলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা ফ\'নৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string> + <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"এপ্টোক ফ\'নলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা ফ\'নৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string> <string name="permlab_bluetooth" msgid="586333280736937209">"ব্লুটুথ ডিভাইচবোৰৰ সৈতে পেয়াৰ কৰক"</string> - <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"টেবলেটত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string> + <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"টেবলেটত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপ্টোক অনুমতি দিয়ে৷"</string> <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"এপ্টোক আপোনাৰ Android TV ডিভাইচটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু পেয়াৰ কৰি থোৱা ডিভাইচসমূহৰ সৈতে সংযোগ কৰিবলৈ আৰু গ্ৰহণ কৰিবলৈ অনুমতি দিয়ে।"</string> - <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ফ\'নটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string> + <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ফ\'নটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপ্টোক অনুমতি দিয়ে৷"</string> <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"নিকটৱৰ্তী ব্লুটুথ ডিভাইচ বিচাৰক আৰু তাৰ সৈতে পেয়াৰ কৰক"</string> <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"এপ্টোক নিকটৱৰ্তী ব্লুটুথ ডিভাইচ বিচাৰি উলিয়াবলৈ আৰু সেইসমূহৰ সৈতে পেয়াৰ কৰিবলৈ অনুমতি দিয়ে"</string> <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"পেয়াৰ কৰা ব্লুটুথ ডিভাইচৰ সৈতে সংযোগ কৰক"</string> @@ -551,9 +551,9 @@ <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"অগ্ৰাধিকাৰ দিয়া NFC পৰিশোধ সেৱাৰ তথ্য"</string> <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"এপ্টোক অগ্ৰাধিকাৰ দিয়া nfc পৰিশোধ সেৱাৰ পঞ্জীকৃত সহায়কসমূহ আৰু পৰিশোধ কৰিব লগা লক্ষ্যস্থান দৰে তথ্য পাবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_nfc" msgid="1904455246837674977">"নিয়েৰ ফিল্ড কমিউনিকেশ্বন নিয়ন্ত্ৰণ কৰক"</string> - <string name="permdesc_nfc" msgid="8352737680695296741">"এপটোক নিয়েৰ ফিল্ড কমিউনিকেশ্বন (NFC) টেগ, কাৰ্ড আৰু ৰিডাৰসমূহৰ সৈতে যোগাযোগ কৰিবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_nfc" msgid="8352737680695296741">"এপ্টোক নিয়েৰ ফিল্ড কমিউনিকেশ্বন (NFC) টেগ, কাৰ্ড আৰু ৰিডাৰসমূহৰ সৈতে যোগাযোগ কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_disableKeyguard" msgid="3605253559020928505">"আপোনাৰ স্ক্ৰীন লক অক্ষম কৰক"</string> - <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"এপটোক কী ল\'ক আৰু জড়িত হোৱা যিকোনো পাছৱৰ্ডৰ সুৰক্ষা অক্ষম কৰিব দিয়ে৷ উদাহৰণস্বৰূপে, কোনো অন্তৰ্গামী ফ\'ন কল উঠোৱাৰ সময়ত ফ\'নটোৱে কী-লকটো অক্ষম কৰে, তাৰ পিছত কল শেষ হ\'লেই কী লকটো পুনৰ সক্ষম কৰে৷"</string> + <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"এপ্টোক কী ল\'ক আৰু জড়িত হোৱা যিকোনো পাছৱৰ্ডৰ সুৰক্ষা অক্ষম কৰিব দিয়ে৷ উদাহৰণস্বৰূপে, কোনো অন্তৰ্গামী ফ\'ন কল উঠোৱাৰ সময়ত ফ\'নটোৱে কী-লকটো অক্ষম কৰে, তাৰ পাছত কল শেষ হ\'লেই কী লকটো পুনৰ সক্ষম কৰে৷"</string> <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"স্ক্ৰীন লকৰ জটিলতাৰ অনুৰোধ"</string> <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"এপ্টোক স্ক্ৰীন লকৰ জটিলতাৰ স্তৰ (উচ্চ, মধ্যম, নিম্ন বা একেবাৰে নাই)ৰ বিষয়ে জানিবলৈ অনুমতি দিয়ে, যিয়ে স্ক্ৰীন লকৰ সম্ভাব্য দৈৰ্ঘ্য বা স্ক্ৰীন লকৰ প্ৰকাৰ দৰ্শায়। লগতে এপ্টোৱে ব্যৱহাৰকাৰীক স্ক্ৰীন লকটো এটা নিৰ্দিষ্ট স্তৰলৈ আপডে’ট কৰিবলৈ পৰামৰ্শ দিব পাৰে যিটো ব্যৱহাৰকাৰীয়ে অৱজ্ঞা কৰি পৰৱর্তী পৃষ্ঠালৈ যাব পাৰে। মনত ৰাখিব যে স্ক্ৰীন লকটো সাধাৰণ পাঠ হিচাপে ষ্ট\'ৰ কৰা নহয়; সেয়েহে, এপ্টোৱে সঠিক পাছৱৰ্ডটো জানিব নোৱাৰে।"</string> <string name="permlab_postNotification" msgid="4875401198597803658">"জাননী দেখুৱাওক"</string> @@ -561,9 +561,9 @@ <string name="permlab_useBiometric" msgid="6314741124749633786">"বায়োমেট্ৰিক হাৰ্ডৱেৰ ব্যৱহাৰ কৰক"</string> <string name="permdesc_useBiometric" msgid="7502858732677143410">"বিশ্বাসযোগ্য়তা প্ৰমাণীকৰণৰ বাবে এপক বায়োমেট্ৰিক হাৰ্ডৱেৰ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string> <string name="permlab_manageFingerprint" msgid="7432667156322821178">"ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ পৰিচালনা কৰিব পাৰে"</string> - <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"ফিংগাৰপ্ৰিণ্ট টেম্প্লেটসমূহ যোগ কৰা বা মচাৰ পদ্ধতিসমূহ কামত লগাবলৈ নিৰ্দেশ দিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"ফিংগাৰপ্ৰিণ্ট টেম্প্লেটসমূহ যোগ কৰা বা মচাৰ পদ্ধতিসমূহ কামত লগাবলৈ নিৰ্দেশ দিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_useFingerprint" msgid="1001421069766751922">"ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ ব্যৱহাৰ কৰিব পাৰে"</string> - <string name="permdesc_useFingerprint" msgid="412463055059323742">"প্ৰমাণীকৰণৰ বাবে ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ ব্যৱহাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে"</string> + <string name="permdesc_useFingerprint" msgid="412463055059323742">"প্ৰমাণীকৰণৰ বাবে ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ ব্যৱহাৰ কৰিবলৈ এপ্টোক অনুমতি দিয়ে"</string> <string name="permlab_audioWrite" msgid="8501705294265669405">"আপোনাৰ সংগীত সংগ্ৰহ সালসলনি কৰিবলৈ"</string> <string name="permdesc_audioWrite" msgid="8057399517013412431">"এপক আপোনাৰ সংগীত সংগ্ৰহ সালসলনি কৰিবলৈ দিয়ে।"</string> <string name="permlab_videoWrite" msgid="5940738769586451318">"আপোনাৰ ভিডিঅ’ সংগ্ৰহ সালসলনি কৰিবলৈ"</string> @@ -689,7 +689,7 @@ <string name="permlab_readSyncSettings" msgid="6250532864893156277">"ছিংকৰ ছেটিং পঢ়ক"</string> <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"একাউণ্টৰ ছিংক ছেটিংবোৰ পঢ়িবলৈ এপক অনুমতি দিয়ে। যেনে, People এপ্টো কোনো একাউণ্টৰ সৈতে ছিংক কৰা হৈছে নে নাই সেয়া নির্ধাৰণ কৰিব পাৰে।"</string> <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"ছিংকক অন আৰু অফ ট\'গল কৰক"</string> - <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"এপটোক কোনো একাউণ্টৰ ছিংক সম্পৰ্কীয় ছেটিংসমূহ সংশোধন কৰিবলৈ অনুমতি দিয়ে৷ উদাহৰণস্বৰূপে, এই কাৰ্যক কোনো একাউণ্টৰ জৰিয়তে People এপটোৰ ছিংক সক্ষম কৰিবলৈ ব্যৱহাৰ কৰিব পাৰি৷"</string> + <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"এপ্টোক কোনো একাউণ্টৰ ছিংক সম্পৰ্কীয় ছেটিংসমূহ সংশোধন কৰিবলৈ অনুমতি দিয়ে৷ উদাহৰণস্বৰূপে, এই কাৰ্যক কোনো একাউণ্টৰ জৰিয়তে People এপ্টোৰ ছিংক সক্ষম কৰিবলৈ ব্যৱহাৰ কৰিব পাৰি৷"</string> <string name="permlab_readSyncStats" msgid="3747407238320105332">"ছিংকৰ পৰিসংখ্যা পঢ়ক"</string> <string name="permdesc_readSyncStats" msgid="3867809926567379434">"ছিংকৰ কাৰ্যক্ৰমসমূহৰ ইতিহাস আৰু ছিংক কৰা ডেটাৰ পৰিমাণসহ কোনো একাউণ্টৰ ছিংকৰ তথ্য পঢ়িবলৈ এপক অনুমতি দিয়ে।"</string> <string name="permlab_sdcardRead" msgid="5791467020950064920">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল পঢ়িব পাৰে"</string> @@ -703,23 +703,23 @@ <string name="permlab_sdcardWrite" msgid="4863021819671416668">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল সংশোধন কৰিব বা মচিব পাৰে"</string> <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল লিখিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_use_sip" msgid="8250774565189337477">"SIP কল কৰা/পোৱা"</string> - <string name="permdesc_use_sip" msgid="3590270893253204451">"এপটোক SIP কলসমূহ কৰিবলৈ আৰু পাবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_use_sip" msgid="3590270893253204451">"এপ্টোক SIP কলসমূহ কৰিবলৈ আৰু পাবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_register_sim_subscription" msgid="1653054249287576161">"নতুন টেলিকম ছিম সংযোগসমূহ পঞ্জীয়ন কৰা"</string> - <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"এপটোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"এপ্টোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_register_call_provider" msgid="6135073566140050702">"নতুন টেলিকম সংযোগসমূহ পঞ্জীয়ন কৰা"</string> - <string name="permdesc_register_call_provider" msgid="4201429251459068613">"এপটোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_register_call_provider" msgid="4201429251459068613">"এপ্টোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_connection_manager" msgid="3179365584691166915">"টেলিকম সংযোগ পৰিচালনা কৰা"</string> - <string name="permdesc_connection_manager" msgid="1426093604238937733">"এপটোক টেলিকম সংযোগ পৰিচালনা কৰিবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_connection_manager" msgid="1426093604238937733">"এপ্টোক টেলিকম সংযোগ পৰিচালনা কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_bind_incall_service" msgid="5990625112603493016">"ইন-কল স্ক্ৰীনৰ সৈতে সংযোগ স্থাপন"</string> <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"ব্যৱহাৰকাৰীগৰাকীয়ে কেতিয়া আৰু কেনেদৰে ইন-কল-স্ক্ৰীন চায় সেয়া নিয়ন্ত্ৰণ কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_bind_connection_service" msgid="5409268245525024736">"টেলিফ\'নী সেৱাসমূহৰ সৈতে সংযোগ স্থাপন"</string> <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"কল কৰিবলৈ/লাভ কৰিবলৈ টেলিফ\'নী সেৱাসমূহৰ সৈতে এপক সংযোগ স্থাপনৰ বাবে অনুমতি দিয়ে।"</string> <string name="permlab_control_incall_experience" msgid="6436863486094352987">"ইন-কল ব্যৱহাৰকাৰীৰ অভিজ্ঞতা প্ৰদান কৰা"</string> - <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"এপটোক ইন-কল ব্যৱহাৰকাৰীৰ অভিজ্ঞতা প্ৰদান কৰিবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"এপ্টোক ইন-কল ব্যৱহাৰকাৰীৰ অভিজ্ঞতা প্ৰদান কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"নেটৱর্কৰ পূৰ্বতে হোৱা ব্যৱহাৰৰ বিষয়ে পঢ়ক"</string> - <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"এপটোক বিশেষ নেটৱৰ্কবিলাকৰ আৰু এপ্সমূহৰ নেটৱৰ্ক ব্যৱহাৰৰ ইতিহাস পঢ়িবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"এপ্টোক বিশেষ নেটৱৰ্কবিলাকৰ আৰু এপ্সমূহৰ নেটৱৰ্ক ব্যৱহাৰৰ ইতিহাস পঢ়িবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"নেটৱর্কৰ নীতি পৰিচালনা কৰক"</string> - <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"এপটোক নেটৱৰ্ক সংযোগৰ নীতিসমূহ পৰিচালনা কৰিবলৈ আৰু এপ্-বিশেষ নিয়ম সংজ্ঞাবদ্ধ কৰিবলৈ অনুমতি দিয়ে।"</string> + <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"এপ্টোক নেটৱৰ্ক সংযোগৰ নীতিসমূহ পৰিচালনা কৰিবলৈ আৰু এপ্-বিশেষ নিয়ম সংজ্ঞাবদ্ধ কৰিবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"নেটৱর্ক ব্যৱহাৰৰ হিচাপ সলনি কৰক"</string> <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"এপ অনুসুৰি নেটৱর্কৰ ব্যৱহাৰৰ হিচাপ সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। এয়া সাধাৰণ এপবোৰৰ ব্যৱহাৰৰ বাবে নহয়।"</string> <string name="permlab_accessNotifications" msgid="7130360248191984741">"জাননীসমূহ এক্সেছ কৰে"</string> @@ -747,7 +747,7 @@ <string name="permlab_bindCarrierServices" msgid="2395596978626237474">"বাহক সেৱাসমূহৰ সৈতে সংযুক্ত হ\'ব পাৰে"</string> <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"বাহক সেৱাৰ সৈতে সংযুক্ত হ\'বলৈ ধাৰকক অনুমতি দিয়ে। সাধাৰণ এপসমূহৰ বাবে সাধাৰণতে প্ৰয়োজন হ\'ব নালাগে।"</string> <string name="permlab_access_notification_policy" msgid="5524112842876975537">"অসুবিধা নিদিব চাব পাৰে"</string> - <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"অসুবিধা নিদিবৰ কনফিগাৰেশ্বনক পঢ়িবলৈ আৰু সালসলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"অসুবিধা নিদিবৰ কনফিগাৰেশ্বনক পঢ়িবলৈ আৰু সালসলনি কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"চোৱাৰ অনুমতিৰ ব্যৱহাৰ আৰম্ভ কৰক"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ধাৰকক কোনো এপৰ বাবে অনুমতিৰ ব্যৱহাৰ আৰম্ভ কৰিবলৈ দিয়ে। সাধাৰণ এপ্সমূহৰ বাবে কেতিয়াও প্ৰয়োজন হ’ব নালাগে।"</string> <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"অনুমতিৰ সিদ্ধান্তসমূহ চোৱা আৰম্ভ কৰক"</string> @@ -914,7 +914,7 @@ <string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"আনলক কৰিবলৈ পাছৱৰ্ড লিখক"</string> <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"আনলক কৰিবলৈ পিন লিখক"</string> <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"ভুল পিন ক\'ড।"</string> - <string name="keyguard_label_text" msgid="3841953694564168384">"আনলক কৰিবলৈ মেনু টিপাৰ পিছত ০ টিপক।"</string> + <string name="keyguard_label_text" msgid="3841953694564168384">"আনলক কৰিবলৈ মেনু টিপাৰ পাছত ০ টিপক।"</string> <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"জৰুৰীকালীন নম্বৰ"</string> <string name="lockscreen_carrier_default" msgid="6192313772955399160">"কোনো সেৱা নাই"</string> <string name="lockscreen_screen_locked" msgid="7364905540516041817">"স্ক্ৰীন লক কৰা হ’ল।"</string> @@ -949,12 +949,12 @@ <string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"ব্যৱহাৰকাৰীৰ নিৰ্দেশনা চাওক বা গ্ৰাহক সেৱা কেন্দ্ৰৰ সৈতে যোগাযোগ কৰক।"</string> <string name="lockscreen_sim_locked_message" msgid="3160196135801185938">"ছিম কাৰ্ড লক কৰা হৈছে।"</string> <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"ছিম কার্ড আনলক কৰি থকা হৈছে…"</string> - <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"আপুনি অশুদ্ধভাৱে আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিছে। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক।"</string> - <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"আপুনি অশুদ্ধভাৱে আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক।"</string> - <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"আপুনি অশুদ্ধভাৱে আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক।"</string> - <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পিছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক৷"</string> + <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"আপুনি অশুদ্ধভাৱে আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিছে। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string> + <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"আপুনি অশুদ্ধভাৱে আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string> + <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"আপুনি অশুদ্ধভাৱে আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string> + <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পাছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক৷"</string> <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"আপুনি নিজৰ আনলক আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ দিলে। আকৌ <xliff:g id="NUMBER_1">%2$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাক নিজৰ Google ছাইন ইন ব্যৱহাৰ কৰি আপোনাৰ Android TV ডিভাইচটো আনলক কৰিবলৈ কোৱা হ’ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g>ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string> - <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পিছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক৷"</string> + <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পাছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক৷"</string> <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"আপুনি টে\'বলেটটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আনলক কৰিবলৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g> বাৰতকৈ বেছি প্ৰয়াস কৰিলে টে\'বলেটটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰী ডেটা হেৰুৱাব।"</string> <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাৰ Android TV ডিভাইচটো ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব আৰু ব্যৱহাৰকাৰীৰ সকলো ডেটা হেৰুৱাব।"</string> <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"আপুনি ফ\'নটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আনলক কৰিবলৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g> বাৰতকৈ বেছি প্ৰয়াস কৰিলে ফ\'নটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰী ডেটা হেৰুৱাব।"</string> @@ -1049,11 +1049,11 @@ <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"এপ্টোক আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ ইতিহাস আৰু বুকমার্কবোৰ সংশোধন কৰিবলৈ অনুমতি দিয়ে। ব্ৰাউজাৰ ডেটা মোহাৰিবলৈ অথবা সংশোধন কৰিবলৈ ই এপ্টোক অনুমতি দিব পাৰে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ অথবা ৱেব ব্ৰাউজিঙৰ ক্ষমতা থকা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ কৰা নহ’বও পাৰে।"</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="permdesc_setAlarm" msgid="2185033720060109640">"এপ্টোক ইনষ্টল হৈ থকা এলাৰ্ম ক্লক এপত এলাৰ্ম ছেট কৰিবলৈ অনুমতি দিয়ে। কিছুমান এলাৰ্ম ক্লক এপত এই সুবিধাটো প্ৰযোজ্য নহ’ব পাৰে।"</string> <string name="permlab_addVoicemail" msgid="4770245808840814471">"ভইচমেইল যোগ কৰক"</string> - <string name="permdesc_addVoicemail" msgid="5470312139820074324">"আপোনাৰ ভইচমেইল ইনবক্সত বাৰ্তাবোৰ যোগ কৰিবলৈ এপটোক অনুমতি দিয়ক।"</string> + <string name="permdesc_addVoicemail" msgid="5470312139820074324">"আপোনাৰ ভইচমেইল ইনবক্সত বাৰ্তাবোৰ যোগ কৰিবলৈ এপ্টোক অনুমতি দিয়ক।"</string> <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"ব্ৰাউজাৰৰ জিঅ\'লোকেশ্বনৰ অনুমতিসমূহ সংশোধন কৰক"</string> - <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"ব্ৰাউজাৰৰ জিঅ\'লোকেশ্বন বিষয়ক অনুমতিসমূহ সংশোধন কৰিবলৈ এপটোক অনুমতি দিয়ে৷ ক্ষতিকাৰক এপবোৰে একপক্ষীয় ৱেবছাইটসমূহলৈ অৱস্থান সেৱাৰ তথ্য পঠিয়াবলৈ ইয়াক ব্যৱহাৰ কৰিব পাৰে৷"</string> + <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"ব্ৰাউজাৰৰ জিঅ\'লোকেশ্বন বিষয়ক অনুমতিসমূহ সংশোধন কৰিবলৈ এপ্টোক অনুমতি দিয়ে৷ ক্ষতিকাৰক এপবোৰে একপক্ষীয় ৱেবছাইটসমূহলৈ অৱস্থান সেৱাৰ তথ্য পঠিয়াবলৈ ইয়াক ব্যৱহাৰ কৰিব পাৰে৷"</string> <string name="save_password_message" msgid="2146409467245462965">"ব্ৰাউজাৰে এই পাছৱর্ডটো মনত ৰখাটো বিচাৰেনে?"</string> <string name="save_password_notnow" msgid="2878327088951240061">"এতিয়াই নহয়"</string> <string name="save_password_remember" msgid="6490888932657708341">"মনত ৰাখিব"</string> @@ -1208,12 +1208,12 @@ <string name="aerr_process" msgid="4268018696970966407">"<xliff:g id="PROCESS">%1$s</xliff:g> বন্ধ হ’ল"</string> <string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> বাৰে বাৰে বন্ধ হৈ গৈছে"</string> <string name="aerr_process_repeated" msgid="1153152413537954974">"<xliff:g id="PROCESS">%1$s</xliff:g> বাৰে বাৰে বন্ধ হৈ গৈছে"</string> - <string name="aerr_restart" msgid="2789618625210505419">"আকৌ এপটো খোলক"</string> + <string name="aerr_restart" msgid="2789618625210505419">"আকৌ এপ্টো খোলক"</string> <string name="aerr_report" msgid="3095644466849299308">"আপোনাৰ প্ৰতিক্ৰিয়া পঠিয়াওক"</string> <string name="aerr_close" msgid="3398336821267021852">"বন্ধ কৰক"</string> <string name="aerr_mute" msgid="2304972923480211376">"ডিভাইচ ৰিষ্টাৰ্ট নোহোৱালৈ মিউট কৰক"</string> <string name="aerr_wait" msgid="3198677780474548217">"অপেক্ষা কৰক"</string> - <string name="aerr_close_app" msgid="8318883106083050970">"এপটো বন্ধ কৰক"</string> + <string name="aerr_close_app" msgid="8318883106083050970">"এপ্টো বন্ধ কৰক"</string> <string name="anr_title" msgid="7290329487067300120"></string> <string name="anr_activity_application" msgid="8121716632960340680">"<xliff:g id="APPLICATION">%2$s</xliff:g>য়ে সঁহাৰি দিয়া নাই"</string> <string name="anr_activity_process" msgid="3477362583767128667">"<xliff:g id="ACTIVITY">%1$s</xliff:g>য়ে সঁহাৰি দিয়া নাই"</string> @@ -1231,7 +1231,7 @@ <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> + <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক এটা খাপ নোখোৱা Android OS সংস্কৰণৰ বাবে তৈয়াৰ কৰা হৈছিল, যাৰ ফলত ই অস্বাভাৱিকধৰণে আচৰণ কৰিব পাৰে। এপ্টোৰ শেহতীয়া সংস্কৰণ উপলব্ধ হ\'ব পাৰে।"</string> <string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"সদায় দেখুৱাওক"</string> <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"আপডে’ট আছে নেকি চাওক"</string> <string name="smv_application" msgid="3775183542777792638">"এপটোৱে <xliff:g id="APPLICATION">%1$s</xliff:g> (প্ৰক্ৰিয়াটোৱে <xliff:g id="PROCESS">%2$s</xliff:g>) নিজে বলবৎ কৰা StrictMode নীতি ভংগ কৰিলে।"</string> @@ -1328,7 +1328,7 @@ <string name="sms_short_code_confirm_allow" msgid="920477594325526691">"পঠিয়াওক"</string> <string name="sms_short_code_confirm_deny" msgid="1356917469323768230">"বাতিল কৰক"</string> <string name="sms_short_code_remember_choice" msgid="1374526438647744862">"মোৰ পচন্দ মনত ৰাখিব"</string> - <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"আপুনি ইয়াক পিছত ছেটিং > এপত সলনি কৰিব পাৰে"</string> + <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"আপুনি ইয়াক পাছত ছেটিং > এপত সলনি কৰিব পাৰে"</string> <string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"যিকোনো সময়ত অনুমতি দিয়ক"</string> <string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"কেতিয়াও অনুমতি নিদিব"</string> <string name="sim_removed_title" msgid="5387212933992546283">"ছিম কাৰ্ড আঁতৰোৱা হ’ল"</string> @@ -1338,8 +1338,8 @@ <string name="sim_added_message" msgid="6602906609509958680">"ম\'বাইলৰ নেটৱর্ক ব্যৱহাৰ কৰিবলৈ আপোনাৰ ডিভাইচটো ৰিষ্টার্ট কৰক।"</string> <string name="sim_restart_button" msgid="8481803851341190038">"ৰিষ্টাৰ্ট কৰক"</string> <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"ম’বাইল সেৱা সক্ৰিয় কৰক"</string> - <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ বাহকৰ এপটো ডাউনল’ড কৰক"</string> - <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ <xliff:g id="APP_NAME">%1$s</xliff:g> এপটো ডাউনল’ড কৰক"</string> + <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ বাহকৰ এপ্টো ডাউনল’ড কৰক"</string> + <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ <xliff:g id="APP_NAME">%1$s</xliff:g> এপ্টো ডাউনল’ড কৰক"</string> <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"এপ্ ডাউনল’ড কৰক"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"নতুন ছিম ভৰোৱা হৈছে"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"ছেট আপ কৰিবলৈ টিপক"</string> @@ -1452,9 +1452,9 @@ <string name="permlab_readInstallSessions" msgid="7279049337895583621">"ইনষ্টল কৰা ছেশ্বনসমূহ পঢ়িব পাৰে"</string> <string name="permdesc_readInstallSessions" msgid="4012608316610763473">"এটা এপ্লিকেশ্বনক ইনষ্টল কৰা ছেশ্বনসমূহ পঢ়িবলৈ অনুমতি দিয়ে। এই কাৰ্যই সক্ৰিয় পেকেজ ইনষ্টলেশ্বনৰ বিষয়ে চাবলৈ অনুমতি দিয়ে।"</string> <string name="permlab_requestInstallPackages" msgid="7600020863445351154">"পেকেজ ইনষ্টলৰ বাবে অনুৰোধ কৰিব পাৰে"</string> - <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"পেকেজ ইনষ্টল কৰাৰ অনুৰোধ প্ৰেৰণ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> + <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"পেকেজ ইনষ্টল কৰাৰ অনুৰোধ প্ৰেৰণ কৰিবলৈ এপ্টোক অনুমতি দিয়ে।"</string> <string name="permlab_requestDeletePackages" msgid="2541172829260106795">"পেকেজ মচাৰ অনুৰোধ কৰিব পাৰে"</string> - <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"এপটোক পেকেজবোৰ মচাৰ অনুৰোধ কৰিবলৈ দিয়ে।"</string> + <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"এপ্টোক পেকেজবোৰ মচাৰ অনুৰোধ কৰিবলৈ দিয়ে।"</string> <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"বেটাৰী অপ্টিমাইজেশ্বন উপেক্ষা কৰিবলৈ বিচাৰক"</string> <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"কোনো এপক সেই এপ্টোৰ বাবে বেটাৰী অপ্টিমাইজেশ্বন উপেক্ষা কৰিবলৈ অনুমতি বিচাৰিবলৈ দিয়ে।"</string> <string name="permlab_queryAllPackages" msgid="2928450604653281650">"আটাইবোৰ পেকেজত প্ৰশ্ন সোধক"</string> @@ -1478,8 +1478,8 @@ <string name="permission_request_notification_title" msgid="1810025922441048273">"অনুমতি বিচাৰি অনুৰোধ কৰা হৈছে"</string> <string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"<xliff:g id="ACCOUNT">%s</xliff:g> একাউণ্টৰ বাবে\nঅনুমতি বিচাৰি অনুৰোধ কৰা হৈছে।"</string> <string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"<xliff:g id="APP">%1$s</xliff:g>এ <xliff:g id="ACCOUNT">%2$s</xliff:g> একাউণ্টটো এক্সেছৰ \nঅনুমতি বিচাৰি অনুৰোধ জনাইছে।"</string> - <string name="forward_intent_to_owner" msgid="4620359037192871015">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ বাহিৰত এই এপটো ব্যৱহাৰ কৰি আছে"</string> - <string name="forward_intent_to_work" msgid="3620262405636021151">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ ভিতৰত এই এপটো ব্যৱহাৰ কৰি আছে"</string> + <string name="forward_intent_to_owner" msgid="4620359037192871015">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ বাহিৰত এই এপ্টো ব্যৱহাৰ কৰি আছে"</string> + <string name="forward_intent_to_work" msgid="3620262405636021151">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ ভিতৰত এই এপ্টো ব্যৱহাৰ কৰি আছে"</string> <string name="input_method_binding_label" msgid="1166731601721983656">"ইনপুট পদ্ধতি"</string> <string name="sync_binding_label" msgid="469249309424662147">"ছিংক"</string> <string name="accessibility_binding_label" msgid="1974602776545801715">"সাধ্য সুবিধাসমূহ"</string> @@ -1560,7 +1560,7 @@ <string name="shareactionprovider_share_with" msgid="2753089758467748982">"ইয়াৰ জৰিয়তে শ্বেয়াৰ কৰক"</string> <string name="shareactionprovider_share_with_application" msgid="4902832247173666973">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>ৰ জৰিয়তে শ্বেয়াৰ কৰক"</string> <string name="content_description_sliding_handle" msgid="982510275422590757">"শ্লাইড কৰা হেণ্ডেল৷ স্পৰ্শ কৰক আৰু ধৰি ৰাখক৷"</string> - <string name="description_target_unlock_tablet" msgid="7431571180065859551">"স্ক্ৰীণ আনলক কৰিবলৈ ছোৱাইপ কৰক৷"</string> + <string name="description_target_unlock_tablet" msgid="7431571180065859551">"স্ক্ৰীন আনলক কৰিবলৈ ছোৱাইপ কৰক৷"</string> <string name="action_bar_home_description" msgid="1501655419158631974">"গৃহ পৃষ্ঠালৈ যাওক"</string> <string name="action_bar_up_description" msgid="6611579697195026932">"ওপৰলৈ যাওক"</string> <string name="action_menu_overflow_description" msgid="4579536843510088170">"অধিক বিকল্প"</string> @@ -1660,18 +1660,18 @@ <string name="kg_login_invalid_input" msgid="8292367491901220210">"ব্যৱহাৰকাৰীৰ অমান্য নাম বা পাছৱর্ড।"</string> <string name="kg_login_account_recovery_hint" msgid="4892466171043541248">"নিজৰ ব্যৱহাৰকাৰী নাম আৰু পাছৱর্ড পাহৰিলেনে?\n"<b>"google.com/accounts/recovery"</b>" লৈ যাওক।"</string> <string name="kg_login_checking_password" msgid="4676010303243317253">"একাউণ্ট পৰীক্ষা কৰি থকা হৈছে…"</string> - <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> - <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> - <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> + <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string> + <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string> + <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string> <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"আপুনি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ টেবলেটৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল প্ৰয়াস কৰিলে টেবলেটটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰীৰ ডেটা হেৰুৱাব।"</string> <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাৰ Android TV ডিভাইচটো ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব আৰু ব্যৱহাৰকাৰীৰ আটাইবোৰ ডেটা হেৰুৱাব।"</string> <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"আপুনি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ ফ\'নৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল প্ৰয়াস কৰিলে ফ\'নটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু ব্যৱহাৰকাৰীৰ আটাইবোৰ ডেটা হেৰুৱাব।"</string> <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"আপুনি <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ টেবলেটৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। টেবলেটটো এতিয়া ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব।"</string> <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। আপোনাৰ Android TV ডিভাইচটো এতিয়া ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব।"</string> <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"আপুনি <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ ফ\'নৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। ফ\'নটো এতিয়া ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব।"</string> - <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ টেবলেটটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> + <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ টেবলেটটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string> <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"আপুনি নিজৰ আনলক আর্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ দিয়ে। আকৌ <xliff:g id="NUMBER_1">%2$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাক এটা ইমেইল একাউণ্ট ব্যৱহাৰ কৰি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ কোৱা হ’ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g>ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string> - <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ ফ\'নটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> + <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ ফ\'নটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"আঁতৰাওক"</string> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"অনুমোদিত স্তৰতকৈ ওপৰলৈ ভলিউম বঢ়াব নেকি?\n\nদীৰ্ঘ সময়ৰ বাবে উচ্চ ভলিউমত শুনাৰ ফলত শ্ৰৱণ ক্ষমতাৰ ক্ষতি হ\'ব পাৰে।"</string> @@ -1835,7 +1835,7 @@ <string name="restr_pin_create_pin" msgid="917067613896366033">"সীমাবদ্ধতা সংশোধন কৰিবলৈ এটা পিন সৃষ্টি কৰক"</string> <string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"পিনবোৰ মিলা নাই। আকৌ চেষ্টা কৰক।"</string> <string name="restr_pin_error_too_short" msgid="1547007808237941065">"পিনটো অতি চুটি। কমেও ৪টা সংখ্যাৰ হ\'ব লাগিব।"</string> - <string name="restr_pin_try_later" msgid="5897719962541636727">"পিছত আকৌ চেষ্টা কৰক"</string> + <string name="restr_pin_try_later" msgid="5897719962541636727">"পাছত আকৌ চেষ্টা কৰক"</string> <string name="immersive_cling_title" msgid="2307034298721541791">"স্ক্ৰীন পূৰ্ণৰূপত চাই আছে"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"বাহিৰ হ\'বলৈ ওপৰৰপৰা তললৈ ছোৱাইপ কৰক।"</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"বুজি পালোঁ"</string> @@ -1931,7 +1931,7 @@ <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="app_suspended_title" msgid="888873445010322650">"এপটো নাই"</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> <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"এপ্ আনপজ কৰক"</string> @@ -1958,7 +1958,7 @@ <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"এইটো আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ Android TV ডিভাইচত চেষ্টা কৰি চাওক।"</string> <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"এইটো আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ টেবলেটত চেষ্টা কৰি চাওক।"</string> <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"এইটো আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ ফ’নত চেষ্টা কৰি চাওক।"</string> - <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই এপটো Androidৰ এটা পুৰণা সংস্কৰণৰ বাবে প্ৰস্তুত কৰা হৈছিল, আৰু ই বিচৰাধৰণে কাম নকৰিবও পাৰে। ইয়াৰ আপডে’ট আছে নেকি চাওক, বা বিকাশকৰ্তাৰ সৈতে যোগাযোগ কৰক।"</string> + <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই এপ্টো Androidৰ এটা পুৰণা সংস্কৰণৰ বাবে প্ৰস্তুত কৰা হৈছিল, আৰু ই বিচৰাধৰণে কাম নকৰিবও পাৰে। ইয়াৰ আপডে’ট আছে নেকি চাওক, বা বিকাশকৰ্তাৰ সৈতে যোগাযোগ কৰক।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"আপডে’ট আছে নেকি চাওক"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"আপুনি নতুন বার্তা লাভ কৰিছে"</string> <string name="new_sms_notification_content" msgid="3197949934153460639">"চাবলৈ এছএমএছ এপ্ খোলক"</string> @@ -1997,7 +1997,7 @@ <string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"সময়ৰ ইনপুটৰ বাবে পাঠৰ ইনপুট ম\'ডলৈ যাওক।"</string> <string name="time_picker_radial_mode_description" msgid="1222342577115016953">"সময়ৰ ইনপুটৰ বাবে ঘড়ী ম\'ডলৈ যাওক।"</string> <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"স্বয়ংপূৰ্তিৰ বিকল্পসমূহ"</string> - <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"পিছত স্বয়ংপূৰ্তি কৰিবলৈ ছেভ কৰক"</string> + <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"পাছত স্বয়ংপূৰ্তি কৰিবলৈ ছেভ কৰক"</string> <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"সমলসমূহ স্বয়ংপূৰ্তি কৰিব নোৱাৰি"</string> <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"কোনো স্বয়ংপূৰ্তি পৰামৰ্শ নাই"</string> <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{এটা স্বয়ংপূৰ্তি পৰামৰ্শ}one{# টা স্বয়ংপূৰ্তি পৰামৰ্শ}other{# টা স্বয়ংপূৰ্তি পৰামৰ্শ}}"</string> @@ -2040,7 +2040,7 @@ <string name="popup_window_default_title" msgid="6907717596694826919">"পপআপ ৱিণ্ড\'"</string> <string name="slice_more_content" msgid="3377367737876888459">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string> <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"এপৰ সংস্কৰণ অৱনমিত কৰা হৈছে, বা ই এই শ্বৰ্টকাটটোৰ লগত খাপ নাখায়"</string> - <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"এপটোত বেকআপ আৰু পুনঃস্থাপন সুবিধা নথকাৰ বাবে শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string> + <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"এপ্টোত বেকআপ আৰু পুনঃস্থাপন সুবিধা নথকাৰ বাবে শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string> <string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"এপৰ স্বাক্ষৰৰ অমিল হোৱাৰ বাবে শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string> <string name="shortcut_restore_unknown_issue" msgid="2478146134395982154">"শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string> <string name="shortcut_disabled_reason_unknown" msgid="753074793553599166">"শ্বৰ্টকাট অক্ষম কৰি থোৱা হৈছে"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index b52e57a57a4d..4f6d0c7f9d11 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1949,15 +1949,15 @@ <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"La configuració d\'Android TV no està disponible"</string> <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"La configuració de la tauleta no està disponible"</string> <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Configuració del telèfon no disponible"</string> - <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"En aquests moments, no es pot accedir a aquesta aplicació al dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string> - <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"En aquests moments, no es pot accedir a aquesta aplicació al dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string> - <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"No es pot accedir a aquesta aplicació al teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string> + <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"En aquests moments, No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string> + <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"En aquests moments, No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string> + <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho al dispositiu Android TV."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho a la tauleta."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho al telèfon."</string> - <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No s\'hi pot accedir des del dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string> - <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"No s\'hi pot accedir des del dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string> - <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No s\'hi pot accedir des del dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string> + <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string> + <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string> + <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aquesta aplicació es va crear per a una versió antiga d\'Android i pot ser que no funcioni correctament. Prova de cercar actualitzacions o contacta amb el desenvolupador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Cerca actualitzacions"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tens missatges nous"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 90f1006e6e50..fcbe2b8b62b3 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1956,9 +1956,9 @@ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguridad adicional. Prueba en tu dispositivo Android TV."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta aplicación solicita seguridad adicional. Prueba en tu tablet."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta aplicación solicita seguridad adicional. Prueba en tu teléfono."</string> - <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu dispositivo Android TV."</string> + <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No se puede acceder desde tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu dispositivo Android TV."</string> <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"No se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu tablet."</string> - <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu teléfono."</string> + <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No se puede acceder desde tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu teléfono."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación se ha diseñado para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o ponte en contacto con el desarrollador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualizaciones"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 15b25adf9ec7..681cc914383f 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -763,10 +763,10 @@ <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Kontrolatu zenbat aldiz idatzi duzun oker pasahitza pantaila desblokeatzen saiatzean, eta blokeatu Android TV gailua edo ezabatu bertako datu guztiak pasahitza gehiegitan idazten baduzu oker."</string> <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Kontrolatu zenbatetan idazten duzun pasahitza oker pantaila desblokeatzen saiatzean eta, gehiegitan idazten bada oker, blokeatu informazio- eta aisia-sistema edo ezabatu hango eduki guztia."</string> <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu telefonoa edo ezabatu bere datuak pasahitza gehiegitan oker idazten bada."</string> - <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu tableta edo ezabatu erabiltzailearen datuak pasahitza gehiegitan oker idazten bada."</string> - <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Kontrolatu zenbat aldiz idatzi duzun oker pasahitza pantaila desblokeatzen saiatzean, eta blokeatu Android TV gailua edo ezabatu erabiltzailearen datuak pasahitza gehiegitan idazten baduzu oker."</string> + <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu tableta edo ezabatu erabiltzaile-datuak pasahitza gehiegitan oker idazten bada."</string> + <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Kontrolatu zenbat aldiz idatzi duzun oker pasahitza pantaila desblokeatzen saiatzean, eta blokeatu Android TV gailua edo ezabatu erabiltzaile-datuak pasahitza gehiegitan idazten baduzu oker."</string> <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Kontrolatu zenbatetan idazten duzun pasahitza oker pantaila desblokeatzen saiatzean eta, gehiegitan idazten bada oker, blokeatu informazio- eta aisia-sistema edo ezabatu profil honetako eduki guztia."</string> - <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu telefonoa edo ezabatu erabiltzailearen datuak pasahitza gehiegitan oker idazten bada."</string> + <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu telefonoa edo ezabatu erabiltzaile-datuak pasahitza gehiegitan oker idazten bada."</string> <string name="policylab_resetPassword" msgid="214556238645096520">"Aldatu pantailaren blokeoa"</string> <string name="policydesc_resetPassword" msgid="4626419138439341851">"Aldatu pantailaren blokeoa."</string> <string name="policylab_forceLock" msgid="7360335502968476434">"Blokeatu pantaila"</string> @@ -777,7 +777,7 @@ <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Berrezarri informazio- eta aisia-sistemako jatorrizko datuak abisatu gabe, bertan zegoen eduki guztia ezabatzeko."</string> <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Ezabatu telefonoaren datuak abisatu gabe, jatorrizko datuak berrezarrita."</string> <string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Ezabatu profileko eduki guztia"</string> - <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Ezabatu erabiltzailearen datuak"</string> + <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Ezabatu erabiltzaile-datuak"</string> <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Ezabatu erabiltzaileak tabletan dituen datuak abisatu gabe."</string> <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Ezabatu erabiltzaileak Android TV gailuan dituen datuak abisatu gabe."</string> <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Ezabatu informazio- eta aisia-sisteman dagoen profil honetako eduki guztia abisatu gabe."</string> @@ -1134,7 +1134,7 @@ <string name="Midnight" msgid="8176019203622191377">"Gauerdia"</string> <string name="elapsed_time_short_format_mm_ss" msgid="8689459651807876423">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="2302144714803345056">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> - <string name="selectAll" msgid="1532369154488982046">"Hautatu guztiak"</string> + <string name="selectAll" msgid="1532369154488982046">"Hautatu dena"</string> <string name="cut" msgid="2561199725874745819">"Ebaki"</string> <string name="copy" msgid="5472512047143665218">"Kopiatu"</string> <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Ezin izan da kopiatu arbelean"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 104808b1f8b9..b86ce04e5b24 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1403,7 +1403,7 @@ <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Analyse de l\'espace de stockage sur le support en cours…"</string> <string name="ext_media_new_notification_title" msgid="3517407571407687677">"Nouveau périphérique <xliff:g id="NAME">%s</xliff:g>"</string> <string name="ext_media_new_notification_title" product="automotive" msgid="9085349544984742727">"<xliff:g id="NAME">%s</xliff:g> ne fonctionne pas"</string> - <string name="ext_media_new_notification_message" msgid="6095403121990786986">"Toucher pour configurer"</string> + <string name="ext_media_new_notification_message" msgid="6095403121990786986">"Touchez pour configurer"</string> <string name="ext_media_new_notification_message" product="tv" msgid="216863352100263668">"Sélectionnez pour configurer"</string> <string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"Vous devrez peut-être reformater l\'appareil. Touchez pour l\'éjecter."</string> <string name="ext_media_ready_notification_message" msgid="7509496364380197369">"Pour stocker des photos, des vidéos, de la musique et plus encore"</string> @@ -1415,7 +1415,7 @@ <string name="ext_media_unmountable_notification_message" product="automotive" msgid="2274596120715020680">"Vous devrez peut-être reformater l\'appareil. Touchez pour l\'éjecter."</string> <string name="ext_media_unsupported_notification_title" msgid="3487534182861251401">"<xliff:g id="NAME">%s</xliff:g> détecté"</string> <string name="ext_media_unsupported_notification_title" product="automotive" msgid="6004193172658722381">"<xliff:g id="NAME">%s</xliff:g> ne fonctionne pas"</string> - <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Toucher pour configurer ."</string> + <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Touchez pour configurer ."</string> <string name="ext_media_unsupported_notification_message" product="tv" msgid="1595482802187036532">"Sélectionner pour configurer <xliff:g id="NAME">%s</xliff:g> dans un format pris en charge."</string> <string name="ext_media_unsupported_notification_message" product="automotive" msgid="3412494732736336330">"Vous devrez peut-être reformater l\'appareil"</string> <string name="ext_media_badremoval_notification_title" msgid="4114625551266196872">"Retrait inattendu de la mémoire « <xliff:g id="NAME">%s</xliff:g> »"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 7f099b056f82..f8e9efb58ecc 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -580,7 +580,7 @@ <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Utilisez la biométrie ou le verrouillage de l\'écran pour continuer"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Matériel biométrique indisponible"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentification annulée"</string> - <string name="biometric_not_recognized" msgid="5106687642694635888">"Non reconnu"</string> + <string name="biometric_not_recognized" msgid="5106687642694635888">"Non reconnue"</string> <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentification annulée"</string> <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Aucun code, schéma ni mot de passe n\'est défini"</string> <string name="biometric_error_generic" msgid="6784371929985434439">"Erreur d\'authentification"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index fb95a1f1acca..73cbf9343bd4 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1210,7 +1210,7 @@ <string name="aerr_application_repeated" msgid="7804378743218496566">"Aplikacija <xliff:g id="APPLICATION">%1$s</xliff:g> neprekidno se ruši"</string> <string name="aerr_process_repeated" msgid="1153152413537954974">"Postupak <xliff:g id="PROCESS">%1$s</xliff:g> neprekidno se ruši"</string> <string name="aerr_restart" msgid="2789618625210505419">"Ponovo otvori aplikaciju"</string> - <string name="aerr_report" msgid="3095644466849299308">"Pošalji povratne informacije"</string> + <string name="aerr_report" msgid="3095644466849299308">"Pošaljite povratne informacije"</string> <string name="aerr_close" msgid="3398336821267021852">"Zatvori"</string> <string name="aerr_mute" msgid="2304972923480211376">"Zanemari do ponovnog pokretanja uređaja"</string> <string name="aerr_wait" msgid="3198677780474548217">"Čekaj"</string> @@ -1951,14 +1951,14 @@ <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"Postavke tableta nisu dostupne"</string> <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Postavke telefona nisu dostupne"</string> <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na Android TV uređaju."</string> - <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem tabletu."</string> - <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem telefonu."</string> + <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na tabletu."</string> + <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na telefonu."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na Android TV uređaju."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na tabletu."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na telefonu."</string> <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na Android TV uređaju."</string> - <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem tabletu."</string> - <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem telefonu."</string> + <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na tabletu."</string> + <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na telefonu."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova je aplikacija razvijena za stariju verziju Androida i možda neće funkcionirati pravilno. Potražite ažuriranja ili se obratite razvojnom programeru."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri ažuriranja"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 688d6b244d12..13487f77b7a4 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1958,7 +1958,7 @@ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Šī lietotne pieprasa papildu drošību. Mēģiniet tai piekļūt savā tālrunī."</string> <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) nevar piekļūt tālvadībai. Mēģiniet tai piekļūt savā Android TV ierīcē."</string> <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) nevar piekļūt tālvadībai. Mēģiniet tai piekļūt savā planšetdatorā."</string> - <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) nevar piekļūt tālvadībai. Mēģiniet tai piekļūt savā tālrunī."</string> + <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Ierīcē <xliff:g id="DEVICE">%1$s</xliff:g> nevar piekļūt šai funkcijai. Mēģiniet tai piekļūt tālrunī."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Šī lietotne tika izstrādāta vecākai Android versijai un var nedarboties pareizi. Meklējiet atjauninājumus vai sazinieties ar izstrādātāju."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Meklēt atjauninājumu"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Jums ir jaunas īsziņas."</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index abc586770796..eab1b3eb4b71 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -124,7 +124,7 @@ <string name="roamingTextSearching" msgid="5323235489657753486">"Leter etter tjeneste"</string> <string name="wfcRegErrorTitle" msgid="3193072971584858020">"Kunne ikke konfigurere wifi-anrop"</string> <string-array name="wfcOperatorErrorAlertMessages"> - <item msgid="468830943567116703">"For å ringe og sende meldinger over Wi-Fi, må du først be operatøren om å konfigurere denne tjenesten. Deretter slår du på wifi-anrop igjen fra Innstillinger. (Feilkode: <xliff:g id="CODE">%1$s</xliff:g>)"</item> + <item msgid="468830943567116703">"For å ringe og sende meldinger over Wifi, må du først be operatøren om å konfigurere denne tjenesten. Deretter slår du på wifi-anrop igjen fra Innstillinger. (Feilkode: <xliff:g id="CODE">%1$s</xliff:g>)"</item> </string-array> <string-array name="wfcOperatorErrorNotificationMessages"> <item msgid="4795145070505729156">"Problem med å registrere wifi-anrop med operatøren din: <xliff:g id="CODE">%1$s</xliff:g>"</item> @@ -135,7 +135,7 @@ <string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g>-Wifi-anrop"</string> <string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN-anrop"</string> <string name="wfcSpnFormat_spn_wlan_call" msgid="255919245825481510">"<xliff:g id="SPN">%s</xliff:g> WLAN-anrop"</string> - <string name="wfcSpnFormat_spn_wifi" msgid="7232899594327126970">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi"</string> + <string name="wfcSpnFormat_spn_wifi" msgid="7232899594327126970">"<xliff:g id="SPN">%s</xliff:g> Wifi"</string> <string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"Wifi-anrop | <xliff:g id="SPN">%s</xliff:g>"</string> <string name="wfcSpnFormat_spn_vowifi" msgid="6865214948822061486">"<xliff:g id="SPN">%s</xliff:g> VoWifi"</string> <string name="wfcSpnFormat_wifi_calling" msgid="6178935388378661755">"Wifi-anrop"</string> @@ -143,9 +143,9 @@ <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Wifi-anrop"</string> <string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string> <string name="wifi_calling_off_summary" msgid="5626710010766902560">"Av"</string> - <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Ring via Wi-Fi"</string> + <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Ring via Wifi"</string> <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"Ring over mobilnettverk"</string> - <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Bare Wi-Fi"</string> + <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Bare Wifi"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-reserve for anrop"</string> @@ -516,14 +516,14 @@ <string name="permdesc_changeNetworkState" msgid="649341947816898736">"Lar appen endre innstillingene for nettverkstilknytning."</string> <string name="permlab_changeTetherState" msgid="9079611809931863861">"endre tilknytningsoppsett"</string> <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Lar appen endre innstillingene for delt nettforbindelse."</string> - <string name="permlab_accessWifiState" msgid="5552488500317911052">"se Wi-Fi-tilkoblinger"</string> - <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Lar appen se informasjon om Wi-Fi-nettverk, f.eks. hvorvidt Wi-Fi er aktivert og navn på tilkoblede Wi-Fi-enheter."</string> - <string name="permlab_changeWifiState" msgid="7947824109713181554">"koble til og fra Wi-Fi"</string> - <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Lar appen koble til og fra Wi-Fi-tilgangspunkter, og å gjøre endringer i enhetens konfigurasjon for Wi-Fi-nettverk."</string> + <string name="permlab_accessWifiState" msgid="5552488500317911052">"se Wifi-tilkoblinger"</string> + <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Lar appen se informasjon om Wifi-nettverk, f.eks. hvorvidt Wifi er aktivert og navn på tilkoblede Wifi-enheter."</string> + <string name="permlab_changeWifiState" msgid="7947824109713181554">"koble til og fra wifi"</string> + <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Lar appen koble til og fra wifi-tilgangspunkter, og å gjøre endringer i enhetens konfigurasjon for wifi-nettverk."</string> <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"tillate multicast for trådløse nettverk"</string> - <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Lar appen motta pakker som sendes til alle enhetene på et Wi-Fi-nettverk ved hjelp av multikastingsadresser, Dette bruker mer strøm enn modusen uten multikasting."</string> - <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Lar appen motta pakker som sendes til alle enhetene på et Wi-Fi-nettverk ved hjelp av multikastingsadresser, ikke bare Android TV-enheten din. Dette bruker mer strøm enn modus uten multikasting."</string> - <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Lar appen motta pakker som sendes til alle enhetene på et Wi-Fi-nettverk ved hjelp av multikastingsadresser, Dette bruker mer strøm enn modusen uten multikasting."</string> + <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Lar appen motta pakker som sendes til alle enhetene på et Wifi-nettverk ved hjelp av multikastingsadresser, Dette bruker mer strøm enn modusen uten multikasting."</string> + <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Lar appen motta pakker som sendes til alle enhetene på et Wifi-nettverk ved hjelp av multikastingsadresser, ikke bare Android TV-enheten din. Dette bruker mer strøm enn modus uten multikasting."</string> + <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Lar appen motta pakker som sendes til alle enhetene på et Wifi-nettverk ved hjelp av multikastingsadresser, Dette bruker mer strøm enn modusen uten multikasting."</string> <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"endre Bluetooth-innstillinger"</string> <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Lar appen konfigurere det lokale Bluetooth-nettbrettet, samt oppdage og koble sammen med eksterne enheter."</string> <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Lar appen konfigurere Bluetooth på Android TV-enheten din samt oppdage og koble sammen med eksterne enheter."</string> @@ -546,8 +546,8 @@ <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Lar appen vise annonser til Bluetooth-enheter i nærheten"</string> <string name="permlab_uwb_ranging" msgid="8141915781475770665">"fastslå relativ posisjon mellom enheter som bruker ultrabredbånd"</string> <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"tillate at appen fastslår den relative posisjonen mellom enheter i nærheten som bruker ultrabredbånd"</string> - <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"samhandle med Wi-Fi-enheter i nærheten"</string> - <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Lar appen annonsere, koble til og fastslå den relative posisjonen til Wi-Fi-enheter i nærheten"</string> + <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"samhandle med wifi-enheter i nærheten"</string> + <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Lar appen annonsere, koble til og fastslå den relative posisjonen til wifi-enheter i nærheten"</string> <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Informasjon om prioritert NFC-betalingstjeneste"</string> <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Gir appen tilgang til informasjon om prioritert NFC-betalingstjeneste, for eksempel registrerte hjelpemidler og destinasjon."</string> <string name="permlab_nfc" msgid="1904455246837674977">"kontroller overføring av data med NFC-teknologi"</string> @@ -1293,7 +1293,7 @@ <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Alarmlyder"</string> <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Varsellyder"</string> <string name="ringtone_unknown" msgid="5059495249862816475">"Ukjent"</string> - <string name="wifi_available_sign_in" msgid="381054692557675237">"Logg på Wi-Fi-nettverket"</string> + <string name="wifi_available_sign_in" msgid="381054692557675237">"Logg på Wifi-nettverket"</string> <string name="network_available_sign_in" msgid="1520342291829283114">"Logg på nettverk"</string> <!-- no translation found for network_available_sign_in_detailed (7520423801613396556) --> <skip /> @@ -1576,10 +1576,10 @@ <string name="data_usage_warning_title" msgid="9034893717078325845">"Varsel om databruk"</string> <string name="data_usage_warning_body" msgid="1669325367188029454">"Du har brukt <xliff:g id="APP">%s</xliff:g> med data"</string> <string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"Grensen for mobildata er nådd"</string> - <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Datagrensen for Wi-Fi er nådd"</string> + <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Datagrensen for wifi er nådd"</string> <string name="data_usage_limit_body" msgid="3567699582000085710">"Data er på pause i resten av syklusen"</string> <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"Over grensen for mobildata"</string> - <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"Over grensen din for Wi-Fi-data"</string> + <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"Over grensen din for wifi-data"</string> <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Du er <xliff:g id="SIZE">%s</xliff:g> over den angitte grensen din"</string> <string name="data_usage_restricted_title" msgid="126711424380051268">"Bakgrunnsdata er begrenset"</string> <string name="data_usage_restricted_body" msgid="5338694433686077733">"Trykk for å fjerne begrensningen."</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index c1c0d5440304..73a59789130e 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -296,7 +296,7 @@ <string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string> <string name="safeMode" msgid="8974401416068943888">"Modo de segurança"</string> <string name="android_system_label" msgid="5974767339591067210">"Sistema Android"</string> - <string name="user_owner_label" msgid="8628726904184471211">"Deslize até o perfil pessoal"</string> + <string name="user_owner_label" msgid="8628726904184471211">"Mudar para o perfil pessoal"</string> <string name="managed_profile_label" msgid="7316778766973512382">"Perfil de trabalho"</string> <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contatos"</string> <string name="permgroupdesc_contacts" msgid="9163927941244182567">"acesse seus contatos"</string> @@ -749,7 +749,7 @@ <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Permite que o proprietário use serviços da operadora. Não deve ser necessário para apps comuns."</string> <string name="permlab_access_notification_policy" msgid="5524112842876975537">"acessar \"Não perturbe\""</string> <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permitir que o app leia e grave a configuração \"Não perturbe\"."</string> - <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso da permissão para visualização"</string> + <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"começar a usar a permissão para ver"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite que o sistema inicie o uso de permissão para um app. Não deve ser necessário para apps comuns."</string> <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"decisões de permissão da visualização inicial"</string> <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Autoriza o detentor a iniciar a tela para revisar as decisões de permissão. Não deve ser necessário para apps normais."</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 1fe6e1c2c3af..0efbc734c254 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -164,7 +164,7 @@ <string name="httpErrorAuth" msgid="469553140922938968">"Não foi possível autenticar."</string> <string name="httpErrorProxyAuth" msgid="7229662162030113406">"A autenticação através do servidor proxy falhou."</string> <string name="httpErrorConnect" msgid="3295081579893205617">"Não foi possível ligar ao servidor."</string> - <string name="httpErrorIO" msgid="3860318696166314490">"Não foi possível comunicar com o servidor. Tente novamente mais tarde."</string> + <string name="httpErrorIO" msgid="3860318696166314490">"Não foi possível comunicar com o servidor. Tente mais tarde."</string> <string name="httpErrorTimeout" msgid="7446272815190334204">"Esgotou o tempo limite da ligação ao servidor."</string> <string name="httpErrorRedirectLoop" msgid="8455757777509512098">"A página contém demasiados redireccionamentos do servidor."</string> <string name="httpErrorUnsupportedScheme" msgid="2664108769858966374">"O protocolo não é suportado."</string> @@ -172,7 +172,7 @@ <string name="httpErrorBadUrl" msgid="754447723314832538">"Não foi possível abrir a página porque o URL é inválido."</string> <string name="httpErrorFile" msgid="3400658466057744084">"Não foi possível aceder ao ficheiro."</string> <string name="httpErrorFileNotFound" msgid="5191433324871147386">"Não foi possível localizar o ficheiro solicitado."</string> - <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Existem demasiados pedidos em processamento. Tente novamente mais tarde."</string> + <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Existem demasiados pedidos em processamento. Tente mais tarde."</string> <string name="notification_title" msgid="5783748077084481121">"Erro de início de sessão de <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> <string name="contentServiceSync" msgid="2341041749565687871">"Sincronização"</string> <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Não é possível sincronizar"</string> @@ -671,7 +671,7 @@ <string name="face_error_no_space" msgid="5649264057026021723">"Não pode guardar novos dados de rostos. Elimine um antigo."</string> <string name="face_error_canceled" msgid="2164434737103802131">"Operação de rosto cancelada."</string> <string name="face_error_user_canceled" msgid="5766472033202928373">"Desbloqueio facial cancelado pelo utilizador"</string> - <string name="face_error_lockout" msgid="7864408714994529437">"Demasiadas tentativas. Tente novamente mais tarde."</string> + <string name="face_error_lockout" msgid="7864408714994529437">"Demasiadas tentativas. Tente mais tarde."</string> <string name="face_error_lockout_permanent" msgid="3277134834042995260">"Demasiadas tentativas. O Desbloqueio facial foi desativado."</string> <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Demasiadas tentativas. Em alternativa, introduza o bloqueio de ecrã."</string> <string name="face_error_unable_to_process" msgid="5723292697366130070">"Não é possível validar o rosto. Tente novamente."</string> @@ -1836,7 +1836,7 @@ <string name="restr_pin_create_pin" msgid="917067613896366033">"Crie um PIN para modificar as restrições"</string> <string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"Os PINs não correspondem. Tente novamente."</string> <string name="restr_pin_error_too_short" msgid="1547007808237941065">"O PIN é demasiado pequeno. Deve ter, no mínimo, 4 dígitos."</string> - <string name="restr_pin_try_later" msgid="5897719962541636727">"Tente novamente mais tarde"</string> + <string name="restr_pin_try_later" msgid="5897719962541636727">"Tente mais tarde"</string> <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> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index c1c0d5440304..73a59789130e 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -296,7 +296,7 @@ <string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string> <string name="safeMode" msgid="8974401416068943888">"Modo de segurança"</string> <string name="android_system_label" msgid="5974767339591067210">"Sistema Android"</string> - <string name="user_owner_label" msgid="8628726904184471211">"Deslize até o perfil pessoal"</string> + <string name="user_owner_label" msgid="8628726904184471211">"Mudar para o perfil pessoal"</string> <string name="managed_profile_label" msgid="7316778766973512382">"Perfil de trabalho"</string> <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contatos"</string> <string name="permgroupdesc_contacts" msgid="9163927941244182567">"acesse seus contatos"</string> @@ -749,7 +749,7 @@ <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Permite que o proprietário use serviços da operadora. Não deve ser necessário para apps comuns."</string> <string name="permlab_access_notification_policy" msgid="5524112842876975537">"acessar \"Não perturbe\""</string> <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permitir que o app leia e grave a configuração \"Não perturbe\"."</string> - <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso da permissão para visualização"</string> + <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"começar a usar a permissão para ver"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite que o sistema inicie o uso de permissão para um app. Não deve ser necessário para apps comuns."</string> <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"decisões de permissão da visualização inicial"</string> <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Autoriza o detentor a iniciar a tela para revisar as decisões de permissão. Não deve ser necessário para apps normais."</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 86115d5a34ff..c2fe6ddd103a 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1953,13 +1953,13 @@ <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Настройки телефона недоступны"</string> <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте Android TV."</string> <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте планшет."</string> - <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте телефон."</string> + <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"На устройстве <xliff:g id="DEVICE">%1$s</xliff:g> эта функция пока недоступна. Используйте телефон."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Это приложение запрашивает дополнительные меры защиты. Используйте Android TV."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Это приложение запрашивает дополнительные меры защиты. Используйте планшет."</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Это приложение запрашивает дополнительные меры защиты. Используйте телефон."</string> <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Эта функция недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте Android TV."</string> <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Эта функция недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте планшет."</string> - <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Эта функция недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте телефон."</string> + <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"На устройстве <xliff:g id="DEVICE">%1$s</xliff:g> эта функция недоступна. Используйте телефон."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Это приложение было создано для более ранней версии Android и может работать со сбоями. Проверьте наличие обновлений или свяжитесь с разработчиком."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверить обновления"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Новые сообщения"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 131ccd04426d..46a5ca4bb1de 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -499,7 +499,7 @@ <string name="permlab_setWallpaper" msgid="6959514622698794511">"వాల్పేపర్ను సెట్ చేయడం"</string> <string name="permdesc_setWallpaper" msgid="2973996714129021397">"సిస్టమ్ వాల్పేపర్ను సెట్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_setWallpaperHints" msgid="1153485176642032714">"మీ వాల్పేపర్ పరిమాణాన్ని సర్దుబాటు చేయడం"</string> - <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"సిస్టమ్ వాల్పేపర్ పరిమాణం సూచనలను సెట్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string> + <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"సిస్టమ్ వాల్పేపర్ సైజ్ సూచనలను సెట్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_setTimeZone" msgid="7922618798611542432">"సమయ మండలిని సెట్ చేయడం"</string> <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"టాబ్లెట్ యొక్క సమయ మండలిని మార్చడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"మీ Android TV పరికరం సమయ మండలిని మార్చడానికి యాప్ని అనుమతిస్తుంది."</string> @@ -560,7 +560,7 @@ <string name="permdesc_postNotification" msgid="5974977162462877075">"నోటిఫికేషన్లను చూపించడానికి యాప్ను అనుమతించండి"</string> <string name="permlab_useBiometric" msgid="6314741124749633786">"బయోమెట్రిక్ హార్డ్వేర్ని ఉపయోగించు"</string> <string name="permdesc_useBiometric" msgid="7502858732677143410">"ప్రమాణీకరణ కోసం బయోమెట్రిక్ హార్డ్వేర్ను ఉపయోగించడానికి యాప్ని అనుమతిస్తుంది"</string> - <string name="permlab_manageFingerprint" msgid="7432667156322821178">"వేలిముద్ర హార్డ్వేర్ని నిర్వహించడానికి అనుమతి"</string> + <string name="permlab_manageFingerprint" msgid="7432667156322821178">"వేలిముద్ర హార్డ్వేర్ని మేనేజ్ చేయడానికి అనుమతి"</string> <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"వినియోగం కోసం వేలిముద్ర టెంప్లేట్లను జోడించే, తొలగించే పద్ధతులను అమలు చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_useFingerprint" msgid="1001421069766751922">"వేలిముద్ర హార్డ్వేర్ని ఉపయోగించడానికి అనుమతి"</string> <string name="permdesc_useFingerprint" msgid="412463055059323742">"ప్రామాణీకరణ కోసం వేలిముద్ర హార్డ్వేర్ను ఉపయోగించడానికి యాప్ను అనుమతిస్తుంది"</string> @@ -709,7 +709,7 @@ <string name="permlab_register_call_provider" msgid="6135073566140050702">"కొత్త టెలికామ్ కనెక్షన్లను నమోదు చేయడం"</string> <string name="permdesc_register_call_provider" msgid="4201429251459068613">"కొత్త టెలికామ్ కనెక్షన్లను నమోదు చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_connection_manager" msgid="3179365584691166915">"టెలికామ్ కనెక్షన్లను నిర్వహించడం"</string> - <string name="permdesc_connection_manager" msgid="1426093604238937733">"టెలికామ్ కనెక్షన్లను నిర్వహించడానికి యాప్ను అనుమతిస్తుంది."</string> + <string name="permdesc_connection_manager" msgid="1426093604238937733">"టెలికామ్ కనెక్షన్లను మేనేజ్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_bind_incall_service" msgid="5990625112603493016">"ఇన్-కాల్ స్క్రీన్తో పరస్పర చర్య చేయడం"</string> <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"వినియోగదారునికి ఇన్-కాల్ స్క్రీన్ ఎప్పుడు, ఎలా కనిపించాలనే దాన్ని నియంత్రించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_bind_connection_service" msgid="5409268245525024736">"టెలిఫోన్ సేవలతో పరస్పర చర్య చేయడం"</string> @@ -719,7 +719,7 @@ <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"చారిత్రక నెట్వర్క్ వినియోగాన్ని చదవడం"</string> <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"నిర్దిష్ట నెట్వర్క్లు మరియు యాప్ల కోసం చారిత్రాత్మక నెట్వర్క్ వినియోగాన్ని చదవడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"నెట్వర్క్ విధానాన్ని నిర్వహించడం"</string> - <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"నెట్వర్క్ విధానాలను నిర్వహించడానికి మరియు యాప్-నిర్దిష్ట నిబంధనలను నిర్వచించడానికి యాప్ను అనుమతిస్తుంది."</string> + <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"నెట్వర్క్ విధానాలను మేనేజ్ చేయడానికి మరియు యాప్-నిర్దిష్ట నిబంధనలను నిర్వచించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"నెట్వర్క్ వినియోగ అకౌంటింగ్ను ఎడిట్ చేయడం"</string> <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"యాప్లలో నెట్వర్క్ వినియోగం ఎలా గణించాలనే దాన్ని ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. సాధారణ యాప్ల ద్వారా ఉపయోగించడానికి ఉద్దేశించినది కాదు."</string> <string name="permlab_accessNotifications" msgid="7130360248191984741">"నోటిఫికేషన్లను యాక్సెస్ చేయడం"</string> @@ -1446,7 +1446,7 @@ <string name="ext_media_status_ejecting" msgid="7532403368044013797">"తొలగిస్తోంది…"</string> <string name="ext_media_status_formatting" msgid="774148701503179906">"ఫార్మాట్ చేస్తోంది..."</string> <string name="ext_media_status_missing" msgid="6520746443048867314">"చొప్పించబడలేదు"</string> - <string name="activity_list_empty" msgid="4219430010716034252">"సరిపోలే కార్యాచరణలు కనుగొనబడలేదు."</string> + <string name="activity_list_empty" msgid="4219430010716034252">"మ్యాచ్ అయ్యే కార్యాచరణలు కనుగొనబడలేదు."</string> <string name="permlab_route_media_output" msgid="8048124531439513118">"మీడియా అవుట్పుట్ను మళ్లించడం"</string> <string name="permdesc_route_media_output" msgid="1759683269387729675">"మీడియా అవుట్పుట్ను ఇతర బాహ్య పరికరాలకు మళ్లించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_readInstallSessions" msgid="7279049337895583621">"ఇన్స్టాల్ సెషన్లను చదవడం"</string> @@ -1491,8 +1491,8 @@ <string name="notification_ranker_binding_label" msgid="432708245635563763">"నోటిఫికేషన్ ర్యాంకర్ సేవ"</string> <string name="vpn_title" msgid="5906991595291514182">"VPN సక్రియం చేయబడింది"</string> <string name="vpn_title_long" msgid="6834144390504619998">"<xliff:g id="APP">%s</xliff:g> ద్వారా VPN సక్రియం చేయబడింది"</string> - <string name="vpn_text" msgid="2275388920267251078">"నెట్వర్క్ను నిర్వహించడానికి నొక్కండి."</string> - <string name="vpn_text_long" msgid="278540576806169831">"<xliff:g id="SESSION">%s</xliff:g>కు కనెక్ట్ చేయబడింది. నెట్వర్క్ను నిర్వహించడానికి నొక్కండి."</string> + <string name="vpn_text" msgid="2275388920267251078">"నెట్వర్క్ను మేనేజ్ చేయడానికి నొక్కండి."</string> + <string name="vpn_text_long" msgid="278540576806169831">"<xliff:g id="SESSION">%s</xliff:g>కు కనెక్ట్ చేయబడింది. నెట్వర్క్ను మేనేజ్ చేయడానికి నొక్కండి."</string> <string name="vpn_lockdown_connecting" msgid="6096725311950342607">"ఎల్లప్పుడూ-ఆన్లో ఉండే VPN కనెక్ట్ చేయబడుతోంది…"</string> <string name="vpn_lockdown_connected" msgid="2853127976590658469">"ఎల్లప్పుడూ-ఆన్లో ఉండే VPN కనెక్ట్ చేయబడింది"</string> <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"ఎల్లప్పుడూ ఆన్లో ఉండే VPN నుండి డిస్కనెక్ట్ చేయబడింది"</string> @@ -1723,7 +1723,7 @@ <string name="guest_name" msgid="8502103277839834324">"గెస్ట్"</string> <string name="error_message_title" msgid="4082495589294631966">"ఎర్రర్"</string> <string name="error_message_change_not_allowed" msgid="843159705042381454">"ఈ మార్పును మీ నిర్వాహకులు అనుమతించలేదు"</string> - <string name="app_not_found" msgid="3429506115332341800">"ఈ చర్యను నిర్వహించడానికి యాప్ ఏదీ కనుగొనబడలేదు"</string> + <string name="app_not_found" msgid="3429506115332341800">"ఈ చర్యను మేనేజ్ చేయడానికి యాప్ ఏదీ కనుగొనబడలేదు"</string> <string name="revoke" msgid="5526857743819590458">"ఉపసంహరించండి"</string> <string name="mediasize_iso_a0" msgid="7039061159929977973">"ISO A0"</string> <string name="mediasize_iso_a1" msgid="4063589931031977223">"ISO A1"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 7bb0ba9c91da..ea26a150d1fb 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -988,7 +988,7 @@ <string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"Pinaliit ang bahagi ng pag-unlock."</string> <string name="keyguard_accessibility_widget" msgid="6776892679715699875">"<xliff:g id="WIDGET_INDEX">%1$s</xliff:g> widget."</string> <string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Tagapili ng user"</string> - <string name="keyguard_accessibility_status" msgid="6792745049712397237">"Katayuan"</string> + <string name="keyguard_accessibility_status" msgid="6792745049712397237">"Status"</string> <string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Camera"</string> <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Mga kontrol ng media"</string> <string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Nagsimula na ang pagbabago ng ayos ng widget."</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index d46bdc8c3f2f..d0d585a006d8 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1562,7 +1562,7 @@ <string name="content_description_sliding_handle" msgid="982510275422590757">"滑动手柄。触摸并按住。"</string> <string name="description_target_unlock_tablet" msgid="7431571180065859551">"滑动解锁。"</string> <string name="action_bar_home_description" msgid="1501655419158631974">"导航首页"</string> - <string name="action_bar_up_description" msgid="6611579697195026932">"向上导航"</string> + <string name="action_bar_up_description" msgid="6611579697195026932">"返回"</string> <string name="action_menu_overflow_description" msgid="4579536843510088170">"更多选项"</string> <string name="action_bar_home_description_format" msgid="5087107531331621803">"%1$s:%2$s"</string> <string name="action_bar_home_subtitle_description_format" msgid="4346835454749569826">"%1$s - %2$s:%3$s"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 43de5ba95aa0..8f720503a06c 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1950,13 +1950,13 @@ <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"無法使用平板電腦設定"</string> <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"無法使用手機設定"</string> <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用 Android TV 裝置。"</string> - <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用平板電腦。"</string> + <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個項目,請改用平板電腦。"</string> <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用手機。"</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"這個應用程式要求進行額外的安全性驗證,請改用 Android TV 裝置。"</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"這個應用程式要求進行額外的安全性驗證,請改用平板電腦。"</string> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"這個應用程式要求進行額外的安全性驗證,請改用手機。"</string> <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用 Android TV 裝置。"</string> - <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用平板電腦。"</string> + <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個項目,請改用平板電腦。"</string> <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用手機。"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"這個應用程式是專為舊版 Android 所打造,因此可能無法正常運作。請嘗試檢查更新,或是與開發人員聯絡。"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 84280605862f..4191ab9691d1 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -701,7 +701,7 @@ <!-- Indicates the time needed to time out the fold animation if the device stops in half folded mode. --> - <integer name="config_unfoldTransitionHalfFoldedTimeout">600</integer> + <integer name="config_unfoldTransitionHalfFoldedTimeout">1000</integer> <!-- Indicates that the device supports having more than one internal display on at the same time. Only applicable to devices with more than one internal display. If this option is @@ -2479,6 +2479,8 @@ <integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes. --> <!-- Is the system user the only user allowed to dream. --> <bool name="config_dreamsOnlyEnabledForSystemUser">false</bool> + <!-- Whether dreams are disabled when ambient mode is suppressed. --> + <bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool> <!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to assistant activities (ACTIVITY_TYPE_ASSISTANT) --> @@ -3575,9 +3577,9 @@ config_sidefpsSkipWaitForPowerVendorAcquireMessage --> <integer name="config_sidefpsSkipWaitForPowerAcquireMessage">6</integer> - <!-- This vendor acquired message that will cause the sidefpsKgPowerPress window to be skipped. - config_sidefpsSkipWaitForPowerOnFingerUp must be true and - config_sidefpsSkipWaitForPowerAcquireMessage must be BIOMETRIC_ACQUIRED_VENDOR == 6. --> + <!-- This vendor acquired message will cause the sidefpsKgPowerPress window to be skipped + when config_sidefpsSkipWaitForPowerAcquireMessage == 6 (VENDOR) and the vendor acquire + message equals this constant --> <integer name="config_sidefpsSkipWaitForPowerVendorAcquireMessage">2</integer> <!-- This config is used to force VoiceInteractionService to start on certain low ram devices. @@ -5956,4 +5958,8 @@ TODO(b/236022708) Move rear display state to device state config file --> <integer name="config_deviceStateRearDisplay">-1</integer> + + <!-- Whether the lock screen is allowed to run its own live wallpaper, + different from the home screen wallpaper. --> + <bool name="config_independentLockscreenLiveWallpaper">false</bool> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 97105d80bf79..5897e3af67fc 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2235,6 +2235,7 @@ <java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenNotPowered" /> <java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" /> <java-symbol type="string" name="config_dreamsDefaultComponent" /> + <java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" /> <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" /> <java-symbol type="array" name="config_supportedDreamComplications" /> <java-symbol type="array" name="config_disabledDreamComponents" /> @@ -4846,6 +4847,7 @@ <java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" /> <java-symbol type="array" name="config_serviceStateLocationAllowedPackages" /> <java-symbol type="integer" name="config_deviceStateRearDisplay"/> + <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/> <!-- For app language picker --> <java-symbol type="string" name="system_locale_title" /> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index f9f3b4c8ead1..0b8b29b9dda9 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -59,6 +59,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import android.annotation.Nullable; +import android.app.Notification.CallStyle; import android.content.Context; import android.content.Intent; import android.content.LocusId; @@ -92,6 +93,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) @@ -531,6 +533,108 @@ public class NotificationTest { } @Test + public void testCallStyle_getSystemActions_forIncomingCall() { + PendingIntent answerIntent = createPendingIntent("answer"); + PendingIntent declineIntent = createPendingIntent("decline"); + Notification.CallStyle style = Notification.CallStyle.forIncomingCall( + new Person.Builder().setName("A Caller").build(), + declineIntent, + answerIntent + ); + style.setBuilder(new Notification.Builder(mContext, "Channel")); + + List<Notification.Action> actions = style.getActionsListWithSystemActions(); + + assertEquals(2, actions.size()); + assertEquals(declineIntent, actions.get(0).actionIntent); + assertEquals(answerIntent, actions.get(1).actionIntent); + } + + @Test + public void testCallStyle_getSystemActions_forOngoingCall() { + PendingIntent hangUpIntent = createPendingIntent("hangUp"); + Notification.CallStyle style = Notification.CallStyle.forOngoingCall( + new Person.Builder().setName("A Caller").build(), + hangUpIntent + ); + style.setBuilder(new Notification.Builder(mContext, "Channel")); + + List<Notification.Action> actions = style.getActionsListWithSystemActions(); + + assertEquals(1, actions.size()); + assertEquals(hangUpIntent, actions.get(0).actionIntent); + } + + @Test + public void testCallStyle_getSystemActions_forIncomingCallWithOtherActions() { + PendingIntent answerIntent = createPendingIntent("answer"); + PendingIntent declineIntent = createPendingIntent("decline"); + Notification.CallStyle style = Notification.CallStyle.forIncomingCall( + new Person.Builder().setName("A Caller").build(), + declineIntent, + answerIntent + ); + Notification.Action actionToKeep = makeNotificationAction(null); + Notification.Action actionToDrop = makeNotificationAction(null); + Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel") + .addAction(actionToKeep) + .addAction(actionToDrop); //expect to move this action to the end + style.setBuilder(notifBuilder); //add a builder with actions + + List<Notification.Action> actions = style.getActionsListWithSystemActions(); + + assertEquals(4, actions.size()); + assertEquals(declineIntent, actions.get(0).actionIntent); + assertEquals(actionToKeep, actions.get(1)); + assertEquals(answerIntent, actions.get(2).actionIntent); + assertEquals(actionToDrop, actions.get(3)); + } + + @Test + public void testCallStyle_getSystemActions_forOngoingCallWithOtherActions() { + PendingIntent hangUpIntent = createPendingIntent("hangUp"); + Notification.CallStyle style = Notification.CallStyle.forOngoingCall( + new Person.Builder().setName("A Caller").build(), + hangUpIntent + ); + Notification.Action firstAction = makeNotificationAction(null); + Notification.Action secondAction = makeNotificationAction(null); + Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel") + .addAction(firstAction) + .addAction(secondAction); + style.setBuilder(notifBuilder); //add a builder with actions + + List<Notification.Action> actions = style.getActionsListWithSystemActions(); + + assertEquals(3, actions.size()); + assertEquals(hangUpIntent, actions.get(0).actionIntent); + assertEquals(firstAction, actions.get(1)); + assertEquals(secondAction, actions.get(2)); + } + + @Test + public void testCallStyle_getSystemActions_dropsOldSystemActions() { + PendingIntent hangUpIntent = createPendingIntent("decline"); + Notification.CallStyle style = Notification.CallStyle.forOngoingCall( + new Person.Builder().setName("A Caller").build(), + hangUpIntent + ); + Bundle actionExtras = new Bundle(); + actionExtras.putBoolean("key_action_priority", true); + Notification.Action oldSystemAction = makeNotificationAction( + builder -> builder.addExtras(actionExtras) + ); + Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel") + .addAction(oldSystemAction); + style.setBuilder(notifBuilder); //add a builder with actions + + List<Notification.Action> actions = style.getActionsListWithSystemActions(); + + assertFalse("Old versions of system actions should be dropped.", + actions.contains(oldSystemAction)); + } + + @Test public void testBuild_ensureSmallIconIsNotTooBig_resizesIcon() { Icon hugeIcon = Icon.createWithBitmap( Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888)); @@ -788,7 +892,7 @@ public class NotificationTest { @Test public void testRestoreFromExtras_Call_invalidExtra_noCrash() { - Notification.Style style = new Notification.CallStyle(); + Notification.Style style = new CallStyle(); Bundle fakeTypes = new Bundle(); fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle()); fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle()); @@ -962,4 +1066,12 @@ public class NotificationTest { } return actionBuilder.build(); } + + /** + * Creates a PendingIntent with the given action. + */ + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, new Intent(action), + PendingIntent.FLAG_MUTABLE); + } } diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt new file mode 100644 index 000000000000..8218b9869b5d --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt @@ -0,0 +1,184 @@ +/* + * 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.internal.app + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.os.Bundle +import android.service.chooser.ChooserTarget +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.internal.R +import com.android.internal.app.ChooserListAdapter.LoadDirectShareIconTask +import com.android.internal.app.chooser.SelectableTargetInfo +import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator +import com.android.internal.app.chooser.TargetInfo +import com.android.server.testutils.any +import com.android.server.testutils.mock +import com.android.server.testutils.whenever +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +@RunWith(AndroidJUnit4::class) +class ChooserListAdapterTest { + private val packageManager = mock<PackageManager> { + whenever(resolveActivity(any(), anyInt())).thenReturn(mock()) + } + private val context = InstrumentationRegistry.getInstrumentation().getContext() + private val resolverListController = mock<ResolverListController>() + private val chooserListCommunicator = mock<ChooserListAdapter.ChooserListCommunicator> { + whenever(maxRankedTargets).thenReturn(0) + } + private val selectableTargetInfoCommunicator = + mock<SelectableTargetInfoCommunicator> { + whenever(targetIntent).thenReturn(mock()) + } + private val chooserActivityLogger = mock<ChooserActivityLogger>() + + private fun createChooserListAdapter( + taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask + ) = + ChooserListAdapterOverride( + context, + emptyList(), + emptyArray(), + emptyList(), + false, + resolverListController, + chooserListCommunicator, + selectableTargetInfoCommunicator, + packageManager, + chooserActivityLogger, + taskProvider + ) + + @Test + fun testDirectShareTargetLoadingIconIsStarted() { + val view = createView() + val viewHolder = ResolverListAdapter.ViewHolder(view) + view.tag = viewHolder + val targetInfo = createSelectableTargetInfo() + val iconTask = mock<LoadDirectShareIconTask>() + val testSubject = createChooserListAdapter { iconTask } + testSubject.testViewBind(view, targetInfo, 0) + + verify(iconTask, times(1)).loadIcon() + } + + @Test + fun testOnlyOneTaskPerTarget() { + val view = createView() + val viewHolderOne = ResolverListAdapter.ViewHolder(view) + view.tag = viewHolderOne + val targetInfo = createSelectableTargetInfo() + val iconTaskOne = mock<LoadDirectShareIconTask>() + val testTaskProvider = mock<() -> LoadDirectShareIconTask> { + whenever(invoke()).thenReturn(iconTaskOne) + } + val testSubject = createChooserListAdapter { testTaskProvider.invoke() } + testSubject.testViewBind(view, targetInfo, 0) + + val viewHolderTwo = ResolverListAdapter.ViewHolder(view) + view.tag = viewHolderTwo + whenever(testTaskProvider()).thenReturn(mock()) + + testSubject.testViewBind(view, targetInfo, 0) + + verify(iconTaskOne, times(1)).loadIcon() + verify(testTaskProvider, times(1)).invoke() + } + + private fun createSelectableTargetInfo(): SelectableTargetInfo = + SelectableTargetInfo( + context, + null, + createChooserTarget(), + 1f, + selectableTargetInfoCommunicator, + null + ) + + private fun createChooserTarget(): ChooserTarget = + ChooserTarget( + "Title", + null, + 1f, + ComponentName("package", "package.Class"), + Bundle() + ) + + private fun createView(): View { + val view = FrameLayout(context) + TextView(context).apply { + id = R.id.text1 + view.addView(this) + } + TextView(context).apply { + id = R.id.text2 + view.addView(this) + } + ImageView(context).apply { + id = R.id.icon + view.addView(this) + } + return view + } +} + +private class ChooserListAdapterOverride( + context: Context?, + payloadIntents: List<Intent>?, + initialIntents: Array<out Intent>?, + rList: List<ResolveInfo>?, + filterLastUsed: Boolean, + resolverListController: ResolverListController?, + chooserListCommunicator: ChooserListCommunicator?, + selectableTargetInfoCommunicator: SelectableTargetInfoCommunicator?, + packageManager: PackageManager?, + chooserActivityLogger: ChooserActivityLogger?, + private val taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask +) : ChooserListAdapter( + context, + payloadIntents, + initialIntents, + rList, + filterLastUsed, + resolverListController, + chooserListCommunicator, + selectableTargetInfoCommunicator, + packageManager, + chooserActivityLogger +) { + override fun createLoadDirectShareIconTask( + info: SelectableTargetInfo? + ): LoadDirectShareIconTask = + taskProvider.invoke(info) + + fun testViewBind(view: View?, info: TargetInfo?, position: Int) { + onBindView(view, info, position) + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java index d19f9f5ea58f..52feac5a585a 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -47,6 +47,7 @@ import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.util.Log; import android.util.MutableInt; import android.util.SparseIntArray; import android.util.SparseLongArray; @@ -82,6 +83,7 @@ import java.util.function.IntConsumer; * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner */ public class BatteryStatsNoteTest extends TestCase { + private static final String TAG = BatteryStatsNoteTest.class.getSimpleName(); private static final int UID = 10500; private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23; @@ -2031,6 +2033,115 @@ public class BatteryStatsNoteTest extends TestCase { noRadioProcFlags, lastProcStateChangeFlags.value); } + + + @SmallTest + public void testNoteMobileRadioPowerStateLocked() { + long curr; + boolean update; + final MockClock clocks = new MockClock(); // holds realtime and uptime in ms + final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setOnBatteryInternal(true); + + // Note mobile radio is on. + curr = 1000L * (clocks.realtime = clocks.uptime = 1001); + bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr, + UID); + + // Note mobile radio is still on. + curr = 1000L * (clocks.realtime = clocks.uptime = 2001); + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, + curr, UID); + assertFalse( + "noteMobileRadioPowerStateLocked should not request an update when the power " + + "state does not change from HIGH.", + update); + + // Note mobile radio is off. + curr = 1000L * (clocks.realtime = clocks.uptime = 3001); + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + curr, UID); + assertTrue( + "noteMobileRadioPowerStateLocked should request an update when the power state " + + "changes from HIGH to LOW.", + update); + + // Note mobile radio is still off. + curr = 1000L * (clocks.realtime = clocks.uptime = 4001); + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + curr, UID); + assertFalse( + "noteMobileRadioPowerStateLocked should not request an update when the power " + + "state does not change from LOW.", + update); + + // Note mobile radio is on. + curr = 1000L * (clocks.realtime = clocks.uptime = 5001); + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, + curr, UID); + assertFalse( + "noteMobileRadioPowerStateLocked should not request an update when the power " + + "state changes from LOW to HIGH.", + update); + } + + @SmallTest + public void testNoteMobileRadioPowerStateLocked_rateLimited() { + long curr; + boolean update; + final MockClock clocks = new MockClock(); // holds realtime and uptime in ms + final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setPowerProfile(mock(PowerProfile.class)); + + final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels(); + final ModemActivityInfo mai = new ModemActivityInfo(0L, 0L, 0L, new int[txLevelCount], 0L); + + final long rateLimit = bi.getMobileRadioPowerStateUpdateRateLimit(); + if (rateLimit < 0) { + Log.w(TAG, "Skipping testNoteMobileRadioPowerStateLocked_rateLimited, rateLimit = " + + rateLimit); + return; + } + bi.setOnBatteryInternal(true); + + // Note mobile radio is on. + curr = 1000L * (clocks.realtime = clocks.uptime = 1001); + bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr, + UID); + + clocks.realtime = clocks.uptime = 2001; + mai.setTimestamp(clocks.realtime); + bi.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, + clocks.realtime, clocks.uptime, mNetworkStatsManager); + + // Note mobile radio is off within the rate limit duration. + clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7); + curr = 1000L * clocks.realtime; + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + curr, UID); + assertFalse( + "noteMobileRadioPowerStateLocked should not request an update when the power " + + "state so soon after a noteModemControllerActivity", + update); + + // Note mobile radio is on. + clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7); + curr = 1000L * clocks.realtime; + bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr, + UID); + + // Note mobile radio is off much later + clocks.realtime = clocks.uptime = clocks.realtime + rateLimit; + curr = 1000L * clocks.realtime; + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + curr, UID); + assertTrue( + "noteMobileRadioPowerStateLocked should request an update when the power state " + + "changes from HIGH to LOW much later after a " + + "noteModemControllerActivity.", + update); + } + private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) { // Note that noteUidProcessStateLocked uses ActivityManager process states. if (fgOn) { diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index edeb5e9f4834..5ea4f069f026 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -114,6 +114,10 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase; } + public long getMobileRadioPowerStateUpdateRateLimit() { + return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS; + } + public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) { mNetworkStats = networkStats; return this; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index fb0a9db6a20b..7e9c4189dabb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -41,7 +41,7 @@ public class WindowExtensionsImpl implements WindowExtensions { // TODO(b/241126279) Introduce constants to better version functionality @Override public int getVendorApiLevel() { - return 1; + return 2; } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 1174b685e92c..bf7326a5b30e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -20,10 +20,11 @@ import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; -import static android.window.TaskFragmentOrganizer.getTransitionType; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; @@ -76,6 +77,7 @@ import androidx.annotation.Nullable; import androidx.window.common.CommonFoldingFeature; import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.extensions.WindowExtensionsProvider; +import androidx.window.extensions.embedding.TransactionManager.TransactionRecord; import androidx.window.extensions.layout.WindowLayoutComponentImpl; import com.android.internal.annotations.VisibleForTesting; @@ -100,6 +102,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") final SplitPresenter mPresenter; + @VisibleForTesting + @GuardedBy("mLock") + final TransactionManager mTransactionManager; + // Currently applied split configuration. @GuardedBy("mLock") private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); @@ -150,6 +156,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final MainThreadExecutor executor = new MainThreadExecutor(); mHandler = executor.mHandler; mPresenter = new SplitPresenter(executor, this); + mTransactionManager = new TransactionManager(mPresenter); final ActivityThread activityThread = ActivityThread.currentActivityThread(); final Application application = activityThread.getApplication(); // Register a callback to be notified about activities being created. @@ -167,7 +174,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void accept(List<CommonFoldingFeature> foldingFeatures) { synchronized (mLock) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); + final TransactionRecord transactionRecord = mTransactionManager + .startNewTransaction(); + final WindowContainerTransaction wct = transactionRecord.getTransaction(); for (int i = 0; i < mTaskContainers.size(); i++) { final TaskContainer taskContainer = mTaskContainers.valueAt(i); if (!taskContainer.isVisible()) { @@ -186,7 +195,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen updateContainersInTask(wct, taskContainer); updateAnimationOverride(taskContainer); } - mPresenter.applyTransaction(wct); + // The WCT should be applied and merged to the device state change transition if + // there is one. + transactionRecord.apply(false /* shouldApplyIndependently */); } } } @@ -256,7 +267,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { synchronized (mLock) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); + final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction( + transaction.getTransactionToken()); + final WindowContainerTransaction wct = transactionRecord.getTransaction(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); for (TaskFragmentTransaction.Change change : changes) { final int taskId = change.getTaskId(); @@ -307,8 +320,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Notify the server, and the server should apply and merge the // WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction. - mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct, - getTransitionType(wct), false /* shouldApplyIndependently */); + transactionRecord.apply(false /* shouldApplyIndependently */); updateCallbackIfNecessary(); } } @@ -333,6 +345,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen container.setInfo(wct, taskFragmentInfo); if (container.isFinished()) { + mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else { // Update with the latest Task configuration. @@ -368,15 +381,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Do not finish the dependents if the last activity is reparented to PiP. // Instead, the original split should be cleanup, and the dependent may be // expanded to fullscreen. + mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); cleanupForEnterPip(wct, container); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else if (taskFragmentInfo.isTaskClearedForReuse()) { // Do not finish the dependents if this TaskFragment was cleared due to // launching activity in the Task. + mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else if (!container.isWaitingActivityAppear()) { // Do not finish the container before the expected activity appear until // timeout. + mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */); } } else if (wasInPip && isInPip) { @@ -571,6 +587,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen container.setInfo(wct, taskFragmentInfo); container.clearPendingAppearedActivities(); if (container.isEmpty()) { + mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } break; @@ -1009,11 +1026,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @GuardedBy("mLock") void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - onTaskFragmentAppearEmptyTimeout(wct, container); + final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); + onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container); // Can be applied independently as a timeout callback. - mPresenter.applyTransaction(wct, getTransitionType(wct), - true /* shouldApplyIndependently */); + transactionRecord.apply(true /* shouldApplyIndependently */); } /** @@ -1023,6 +1039,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { + mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } @@ -1562,6 +1579,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * @param isOnCreated whether this happens during the primary activity onCreated. */ @VisibleForTesting + @GuardedBy("mLock") @Nullable Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) { // Setting avoid move to front will also skip the animation. We only want to do that when @@ -1569,6 +1587,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Check if the primary is resumed or if this is called when the primary is onCreated // (not resumed yet). if (isOnCreated || primaryActivity.isResumed()) { + // Only set trigger type if the launch happens in foreground. + mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_OPEN); return null; } final ActivityOptions options = ActivityOptions.makeBasic(); @@ -1595,6 +1615,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (SplitPresenter.shouldShowSplit(splitAttributes)) { return false; } + + mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(), false /* shouldFinishDependent */); return true; @@ -1905,23 +1927,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // that we don't launch it if an activity itself already requested something to be // launched to side. synchronized (mLock) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - SplitController.this.onActivityCreated(wct, activity); + final TransactionRecord transactionRecord = mTransactionManager + .startNewTransaction(); + transactionRecord.setOriginType(TRANSIT_OPEN); + SplitController.this.onActivityCreated(transactionRecord.getTransaction(), + activity); // The WCT should be applied and merged to the activity launch transition. - mPresenter.applyTransaction(wct, getTransitionType(wct), - false /* shouldApplyIndependently */); + transactionRecord.apply(false /* shouldApplyIndependently */); } } @Override public void onActivityConfigurationChanged(@NonNull Activity activity) { synchronized (mLock) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - SplitController.this.onActivityConfigurationChanged(wct, activity); + final TransactionRecord transactionRecord = mTransactionManager + .startNewTransaction(); + SplitController.this.onActivityConfigurationChanged( + transactionRecord.getTransaction(), activity); // The WCT should be applied and merged to the Task change transition so that the // placeholder is launched in the same transition. - mPresenter.applyTransaction(wct, getTransitionType(wct), - false /* shouldApplyIndependently */); + transactionRecord.apply(false /* shouldApplyIndependently */); } } @@ -1977,7 +2002,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } synchronized (mLock) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); + final TransactionRecord transactionRecord = mTransactionManager + .startNewTransaction(); + transactionRecord.setOriginType(TRANSIT_OPEN); + final WindowContainerTransaction wct = transactionRecord.getTransaction(); final TaskFragmentContainer launchedInTaskFragment; if (launchingActivity != null) { final int taskId = getTaskId(launchingActivity); @@ -1990,13 +2018,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (launchedInTaskFragment != null) { // Make sure the WCT is applied immediately instead of being queued so that the // TaskFragment will be ready before activity attachment. - mPresenter.applyTransaction(wct, getTransitionType(wct), - false /* shouldApplyIndependently */); + transactionRecord.apply(false /* shouldApplyIndependently */); // Amend the request to let the WM know that the activity should be placed in // the dedicated container. options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, launchedInTaskFragment.getTaskFragmentToken()); mCurrentIntent = intent; + } else { + transactionRecord.abort(); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java new file mode 100644 index 000000000000..0071fea41aa8 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_NONE; + +import android.os.IBinder; +import android.view.WindowManager.TransitionType; +import android.window.TaskFragmentOrganizer; +import android.window.WindowContainerTransaction; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Responsible for managing the current {@link WindowContainerTransaction} as a response to device + * state changes and app interactions. + * + * A typical use flow: + * 1. Call {@link #startNewTransaction} to start tracking the changes. + * 2. Use {@link TransactionRecord#setOriginType(int)} (int)} to record the type of operation that + * will start a new transition on system server. + * 3. Use {@link #getCurrentTransactionRecord()} to get current {@link TransactionRecord} for + * changes. + * 4. Call {@link TransactionRecord#apply(boolean)} to request the system server to apply changes in + * the current {@link WindowContainerTransaction}, or call {@link TransactionRecord#abort()} to + * dispose the current one. + * + * Note: + * There should be only one transaction at a time. The caller should not call + * {@link #startNewTransaction} again before calling {@link TransactionRecord#apply(boolean)} or + * {@link TransactionRecord#abort()} to the previous transaction. + */ +class TransactionManager { + + @NonNull + private final TaskFragmentOrganizer mOrganizer; + + @Nullable + private TransactionRecord mCurrentTransaction; + + TransactionManager(@NonNull TaskFragmentOrganizer organizer) { + mOrganizer = organizer; + } + + @NonNull + TransactionRecord startNewTransaction() { + return startNewTransaction(null /* taskFragmentTransactionToken */); + } + + /** + * Starts tracking the changes in a new {@link WindowContainerTransaction}. Caller can call + * {@link #getCurrentTransactionRecord()} later to continue adding change to the current + * transaction until {@link TransactionRecord#apply(boolean)} or + * {@link TransactionRecord#abort()} is called. + * @param taskFragmentTransactionToken {@link android.window.TaskFragmentTransaction + * #getTransactionToken()} if this is a response to a + * {@link android.window.TaskFragmentTransaction}. + */ + @NonNull + TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) { + if (mCurrentTransaction != null) { + mCurrentTransaction = null; + throw new IllegalStateException( + "The previous transaction has not been applied or aborted,"); + } + mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken); + return mCurrentTransaction; + } + + /** + * Gets the current {@link TransactionRecord} started from {@link #startNewTransaction}. + */ + @NonNull + TransactionRecord getCurrentTransactionRecord() { + if (mCurrentTransaction == null) { + throw new IllegalStateException("startNewTransaction() is not invoked before calling" + + " getCurrentTransactionRecord()."); + } + return mCurrentTransaction; + } + + /** The current transaction. The manager should only handle one transaction at a time. */ + class TransactionRecord { + /** + * {@link WindowContainerTransaction} containing the current change. + * @see #startNewTransaction(IBinder) + * @see #apply (boolean) + */ + @NonNull + private final WindowContainerTransaction mTransaction = new WindowContainerTransaction(); + + /** + * If the current transaction is a response to a + * {@link android.window.TaskFragmentTransaction}, this is the + * {@link android.window.TaskFragmentTransaction#getTransactionToken()}. + * @see #startNewTransaction(IBinder) + */ + @Nullable + private final IBinder mTaskFragmentTransactionToken; + + /** + * To track of the origin type of the current {@link #mTransaction}. When + * {@link #apply (boolean)} to start a new transition, this is the type to request. + * @see #setOriginType(int) + * @see #getTransactionTransitionType() + */ + @TransitionType + private int mOriginType = TRANSIT_NONE; + + TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) { + mTaskFragmentTransactionToken = taskFragmentTransactionToken; + } + + @NonNull + WindowContainerTransaction getTransaction() { + ensureCurrentTransaction(); + return mTransaction; + } + + /** + * Sets the {@link TransitionType} that triggers this transaction. If there are multiple + * calls, only the first call will be respected as the "origin" type. + */ + void setOriginType(@TransitionType int type) { + ensureCurrentTransaction(); + if (mOriginType != TRANSIT_NONE) { + // Skip if the origin type has already been set. + return; + } + mOriginType = type; + } + + /** + * Requests the system server to apply the current transaction started from + * {@link #startNewTransaction}. + * @param shouldApplyIndependently If {@code true}, the {@link #mCurrentTransaction} will + * request a new transition, which will be queued until the + * sync engine is free if there is any other active sync. + * If {@code false}, the {@link #startNewTransaction} will + * be directly applied to the active sync. + */ + void apply(boolean shouldApplyIndependently) { + ensureCurrentTransaction(); + if (mTaskFragmentTransactionToken != null) { + // If this is a response to a TaskFragmentTransaction. + mOrganizer.onTransactionHandled(mTaskFragmentTransactionToken, mTransaction, + getTransactionTransitionType(), shouldApplyIndependently); + } else { + mOrganizer.applyTransaction(mTransaction, getTransactionTransitionType(), + shouldApplyIndependently); + } + dispose(); + } + + /** Called when there is no need to {@link #apply(boolean)} the current transaction. */ + void abort() { + ensureCurrentTransaction(); + dispose(); + } + + private void dispose() { + TransactionManager.this.mCurrentTransaction = null; + } + + private void ensureCurrentTransaction() { + if (TransactionManager.this.mCurrentTransaction != this) { + throw new IllegalStateException( + "This transaction has already been apply() or abort()."); + } + } + + /** + * Gets the {@link TransitionType} that we will request transition with for the + * current {@link WindowContainerTransaction}. + */ + @VisibleForTesting + @TransitionType + int getTransactionTransitionType() { + // Use TRANSIT_CHANGE as default if there is not opening/closing window. + return mOriginType != TRANSIT_NONE ? mOriginType : TRANSIT_CHANGE; + } + } +} diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 25d034756265..a40303150079 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -132,6 +132,7 @@ public class SplitControllerTest { private SplitController mSplitController; private SplitPresenter mSplitPresenter; + private TransactionManager mTransactionManager; @Before public void setUp() { @@ -140,8 +141,10 @@ public class SplitControllerTest { .getCurrentWindowLayoutInfo(anyInt(), any()); mSplitController = new SplitController(mWindowLayoutComponent); mSplitPresenter = mSplitController.mPresenter; + mTransactionManager = mSplitController.mTransactionManager; spyOn(mSplitController); spyOn(mSplitPresenter); + spyOn(mTransactionManager); doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean()); final Configuration activityConfig = new Configuration(); activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); @@ -212,6 +215,8 @@ public class SplitControllerTest { @Test public void testOnTaskFragmentAppearEmptyTimeout() { + // Setup to make sure a transaction record is started. + mTransactionManager.startNewTransaction(); final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any()); mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf); @@ -615,6 +620,8 @@ public class SplitControllerTest { @Test public void testResolveActivityToContainer_placeholderRule_notInTaskFragment() { + // Setup to make sure a transaction record is started. + mTransactionManager.startNewTransaction(); setupPlaceholderRule(mActivity); final SplitPlaceholderRule placeholderRule = (SplitPlaceholderRule) mSplitController.getSplitRules().get(0); @@ -647,6 +654,8 @@ public class SplitControllerTest { @Test public void testResolveActivityToContainer_placeholderRule_inTopMostTaskFragment() { + // Setup to make sure a transaction record is started. + mTransactionManager.startNewTransaction(); setupPlaceholderRule(mActivity); final SplitPlaceholderRule placeholderRule = (SplitPlaceholderRule) mSplitController.getSplitRules().get(0); @@ -679,6 +688,8 @@ public class SplitControllerTest { @Test public void testResolveActivityToContainer_placeholderRule_inSecondarySplit() { + // Setup to make sure a transaction record is started. + mTransactionManager.startNewTransaction(); setupPlaceholderRule(mActivity); final SplitPlaceholderRule placeholderRule = (SplitPlaceholderRule) mSplitController.getSplitRules().get(0); @@ -961,6 +972,8 @@ public class SplitControllerTest { @Test public void testGetPlaceholderOptions() { + // Setup to make sure a transaction record is started. + mTransactionManager.startNewTransaction(); doReturn(true).when(mActivity).isResumed(); assertNull(mSplitController.getPlaceholderOptions(mActivity, false /* isOnCreated */)); @@ -1147,8 +1160,6 @@ public class SplitControllerTest { + "of other properties", SplitController.haveSamePresentation(splitRule1, splitRule2, new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED))); - - } /** Creates a mock activity in the organizer process. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java new file mode 100644 index 000000000000..62006bd51399 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.clearInvocations; + +import android.os.Binder; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.window.TaskFragmentOrganizer; +import android.window.WindowContainerTransaction; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.window.extensions.embedding.TransactionManager.TransactionRecord; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for {@link TransactionManager}. + * + * Build/Install/Run: + * atest WMJetpackUnitTests:TransactionManagerTest + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TransactionManagerTest { + + @Mock + private TaskFragmentOrganizer mOrganizer; + private TransactionManager mTransactionManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mTransactionManager = new TransactionManager(mOrganizer); + } + + @Test + public void testStartNewTransaction() { + mTransactionManager.startNewTransaction(); + + // Throw exception if #startNewTransaction is called twice without #apply() or #abort(). + assertThrows(IllegalStateException.class, mTransactionManager::startNewTransaction); + + // Allow to start new after #apply() the last transaction. + TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); + transactionRecord.apply(false /* shouldApplyIndependently */); + transactionRecord = mTransactionManager.startNewTransaction(); + + // Allow to start new after #abort() the last transaction. + transactionRecord.abort(); + mTransactionManager.startNewTransaction(); + } + + @Test + public void testSetTransactionOriginType() { + // Return TRANSIT_CHANGE if there is no trigger type set. + TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); + + assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType()); + + // Return the first set type. + mTransactionManager.getCurrentTransactionRecord().abort(); + transactionRecord = mTransactionManager.startNewTransaction(); + transactionRecord.setOriginType(TRANSIT_OPEN); + + assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType()); + + transactionRecord.setOriginType(TRANSIT_CLOSE); + + assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType()); + + // Reset when #startNewTransaction(). + transactionRecord.abort(); + transactionRecord = mTransactionManager.startNewTransaction(); + + assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType()); + } + + @Test + public void testGetCurrentTransactionRecord() { + // Throw exception if #getTransaction is called without calling #startNewTransaction(). + assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord); + + TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); + assertNotNull(transactionRecord); + + // Same WindowContainerTransaction should be returned. + assertSame(transactionRecord, mTransactionManager.getCurrentTransactionRecord()); + + // Reset after #abort(). + transactionRecord.abort(); + assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord); + + // New WindowContainerTransaction after #startNewTransaction(). + mTransactionManager.startNewTransaction(); + assertNotEquals(transactionRecord, mTransactionManager.getCurrentTransactionRecord()); + + // Reset after #apply(). + mTransactionManager.getCurrentTransactionRecord().apply( + false /* shouldApplyIndependently */); + assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord); + } + + @Test + public void testApply() { + // #applyTransaction(false) + TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); + int transitionType = transactionRecord.getTransactionTransitionType(); + WindowContainerTransaction wct = transactionRecord.getTransaction(); + transactionRecord.apply(false /* shouldApplyIndependently */); + + verify(mOrganizer).applyTransaction(wct, transitionType, + false /* shouldApplyIndependently */); + + // #applyTransaction(true) + clearInvocations(mOrganizer); + transactionRecord = mTransactionManager.startNewTransaction(); + transitionType = transactionRecord.getTransactionTransitionType(); + wct = transactionRecord.getTransaction(); + transactionRecord.apply(true /* shouldApplyIndependently */); + + verify(mOrganizer).applyTransaction(wct, transitionType, + true /* shouldApplyIndependently */); + + // #onTransactionHandled(false) + clearInvocations(mOrganizer); + IBinder token = new Binder(); + transactionRecord = mTransactionManager.startNewTransaction(token); + transitionType = transactionRecord.getTransactionTransitionType(); + wct = transactionRecord.getTransaction(); + transactionRecord.apply(false /* shouldApplyIndependently */); + + verify(mOrganizer).onTransactionHandled(token, wct, transitionType, + false /* shouldApplyIndependently */); + + // #onTransactionHandled(true) + clearInvocations(mOrganizer); + token = new Binder(); + transactionRecord = mTransactionManager.startNewTransaction(token); + transitionType = transactionRecord.getTransactionTransitionType(); + wct = transactionRecord.getTransaction(); + transactionRecord.apply(true /* shouldApplyIndependently */); + + verify(mOrganizer).onTransactionHandled(token, wct, transitionType, + true /* shouldApplyIndependently */); + + // Throw exception if there is any more interaction. + final TransactionRecord record = transactionRecord; + assertThrows(IllegalStateException.class, + () -> record.apply(false /* shouldApplyIndependently */)); + assertThrows(IllegalStateException.class, + () -> record.apply(true /* shouldApplyIndependently */)); + assertThrows(IllegalStateException.class, + record::abort); + } + + @Test + public void testAbort() { + final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); + transactionRecord.abort(); + + // Throw exception if there is any more interaction. + verifyNoMoreInteractions(mOrganizer); + assertThrows(IllegalStateException.class, + () -> transactionRecord.apply(false /* shouldApplyIndependently */)); + assertThrows(IllegalStateException.class, + () -> transactionRecord.apply(true /* shouldApplyIndependently */)); + assertThrows(IllegalStateException.class, + transactionRecord::abort); + } +} diff --git a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml index 66e5b43d76af..5ecba380fb60 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml @@ -22,8 +22,8 @@ > <group android:scaleX="0.5" android:scaleY="0.5" - android:translateX="8.0" - android:translateY="8.0" > + android:translateX="4.0" + android:translateY="4.0" > <path android:fillColor="@android:color/black" android:pathData="MM24,40.3 L7.7,24 24,7.7 26.8,10.45 15.3,22H40.3V26H15.3L26.8,37.5Z"/> diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml index 53a8bb18537c..416287d2cbb3 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml @@ -17,4 +17,5 @@ <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/decor_close_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml index 851cbf26afc3..cf9e632f6941 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml @@ -21,8 +21,8 @@ android:viewportHeight="32.0"> <group android:scaleX="0.5" android:scaleY="0.5" - android:translateX="8.0" - android:translateY="8.0" > + android:translateX="4.0" + android:translateY="4.0" > <path android:fillColor="@android:color/black" android:pathData="M12.45,38.35 L9.65,35.55 21.2,24 9.65,12.45 12.45,9.65 24,21.2 35.55,9.65 38.35,12.45 26.8,24 38.35,35.55 35.55,38.35 24,26.8Z"/> diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml index ee0f4663c940..c9f262398f68 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml @@ -18,6 +18,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - <path - android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/> + <group android:translateY="8.0"> + <path + android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/> + </group> </vector> diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index cc600741d1b7..f9d153fd7408 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Terug"</string> + <string name="handle_text" msgid="1766582106752184456">"Handvatsel"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index 7aa877041d53..0fdfed86b29e 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string> <string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string> <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index 2d0d9705cab1..351e1928a260 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string> <string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string> <string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index 42153ff1ad96..765aaba4d23c 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -37,14 +37,14 @@ <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string> <string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাওঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string> - <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"বাওঁফালৰ স্ক্ৰীণখন ৭০% কৰক"</string> - <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীণখন ৫০% কৰক"</string> - <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"বাওঁফালৰ স্ক্ৰীণখন ৩০% কৰক"</string> + <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"বাওঁফালৰ স্ক্ৰীনখন ৭০% কৰক"</string> + <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীনখন ৫০% কৰক"</string> + <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"বাওঁফালৰ স্ক্ৰীনখন ৩০% কৰক"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"সোঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"শীৰ্ষ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string> - <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ স্ক্ৰীণখন ৭০% কৰক"</string> - <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীণখন ৫০% কৰক"</string> - <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীণখন ৩০% কৰক"</string> + <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ স্ক্ৰীনখন ৭০% কৰক"</string> + <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীনখন ৫০% কৰক"</string> + <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীনখন ৩০% কৰক"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"তলৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string> @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string> <string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string> <string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index 5da5a97ff076..0ec5db185e34 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> 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 aff2b9f686c8..4dda1dbf3906 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string> + <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index e325e511596c..bd4a079f54c6 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string> <string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string> <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index 3bedc86a79ac..a04a50da7089 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string> <string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string> <string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index 35e5b38233cc..b652d383a49a 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string> <string name="minimize_button_text" msgid="271592547935841753">"ছোট করুন"</string> <string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string> + <string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string> + <string name="handle_text" msgid="1766582106752184456">"হাতল"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index 34b282b24666..2cc0ab321b4a 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Natrag"</string> + <string name="handle_text" msgid="1766582106752184456">"Pokazivač"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index 0279917272a5..a2badaf06989 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index d58100b5bb9b..17f7374d9d00 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Zpět"</string> + <string name="handle_text" msgid="1766582106752184456">"Úchyt"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index abb9cea6c6f3..084ea865f769 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index a579394818b9..195c3355df2d 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index 248cff818da6..2f929466343c 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string> <string name="minimize_button_text" msgid="271592547935841753">"Ελαχιστοποίηση"</string> <string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string> + <string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string> + <string name="handle_text" msgid="1766582106752184456">"Λαβή"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml index 9c798b84c814..9c88e78616e7 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Back"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml index 9c798b84c814..9c88e78616e7 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Back"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml index 9c798b84c814..9c88e78616e7 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Back"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml index 9c798b84c814..9c88e78616e7 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Back"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml index 5ff5c586b38f..646b3c545c00 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Back"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index 08a7f49f3dfd..0ad387bbd39c 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index 139232cc808f..4b85a22a12aa 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index d6f35807d290..cc9057815ca2 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index e0efadd6f735..d1fca9b6d4b9 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Atzera"</string> + <string name="handle_text" msgid="1766582106752184456">"Kontu-izena"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index 267dd2050cfa..a1d0b5851f84 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string> <string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string> <string name="close_button_text" msgid="2913281996024033299">"بستن"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index 30321a20c643..5df1e04abb2a 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 12789975fb5e..4c59b9999d76 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Retour"</string> + <string name="handle_text" msgid="1766582106752184456">"Identifiant"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index 84c861896443..c4b386aad3d1 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index 59ec2deab17c..f5a106d3956d 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index 85d98ed2fc7b..5d52c58616a8 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string> <string name="minimize_button_text" msgid="271592547935841753">"નાનું કરો"</string> <string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string> + <string name="back_button_text" msgid="1469718707134137085">"પાછળ"</string> + <string name="handle_text" msgid="1766582106752184456">"હૅન્ડલ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index bd90b7ea9220..95484d5fa1d5 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string> <string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string> <string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index 41df0df1ee87..32ab87cfda64 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Natrag"</string> + <string name="handle_text" msgid="1766582106752184456">"Pokazivač"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index 9736a69f4001..dba7f4e37f8e 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Vissza"</string> + <string name="handle_text" msgid="1766582106752184456">"Fogópont"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 3c6449421dec..5ae72eb27df2 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string> <string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string> <string name="close_button_text" msgid="2913281996024033299">"Փակել"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index 61f39036320c..d36a83f115ec 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index 044348c94a95..31d483ed7c1a 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index ca97693481cb..4e8ac62a6288 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Indietro"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index 67de8a0fde34..5c9425e92929 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string> <string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string> <string name="close_button_text" msgid="2913281996024033299">"סגירה"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index cc8527e73636..d9f514a4c312 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> <string name="close_button_text" msgid="2913281996024033299">"閉じる"</string> + <string name="back_button_text" msgid="1469718707134137085">"戻る"</string> + <string name="handle_text" msgid="1766582106752184456">"ハンドル"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index 0ef4563b7a8a..787ac3e1af00 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string> <string name="minimize_button_text" msgid="271592547935841753">"ჩაკეცვა"</string> <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index 6ba9a68943cf..927f0d7a0dde 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string> <string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string> <string name="close_button_text" msgid="2913281996024033299">"Жабу"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 03682d800e50..955b2f88f314 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string> <string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string> <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index 167a38e09c60..57c7124222c9 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string> <string name="minimize_button_text" msgid="271592547935841753">"ಕುಗ್ಗಿಸಿ"</string> <string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string> + <string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string> + <string name="handle_text" msgid="1766582106752184456">"ಹ್ಯಾಂಡಲ್"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index 1175a0e884a4..a69e105876be 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string> <string name="minimize_button_text" msgid="271592547935841753">"최소화"</string> <string name="close_button_text" msgid="2913281996024033299">"닫기"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index b047863bdcf6..ab51ca0725b0 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string> <string name="minimize_button_text" msgid="271592547935841753">"Кичирейтүү"</string> <string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string> + <string name="back_button_text" msgid="1469718707134137085">"Артка"</string> + <string name="handle_text" msgid="1766582106752184456">"Маркер"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index 00bf7f4c7983..15bde88c7afb 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string> <string name="minimize_button_text" msgid="271592547935841753">"ຫຍໍ້ລົງ"</string> <string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index 39817930f308..275efa203a74 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index 3599c14dcadd..3048630e2344 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index 6c41b0ca0afd..64ce3f617768 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string> <string name="minimize_button_text" msgid="271592547935841753">"Минимизирај"</string> <string name="close_button_text" msgid="2913281996024033299">"Затвори"</string> + <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> + <string name="handle_text" msgid="1766582106752184456">"Прекар"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index 76dfc0d944a5..9013828fb487 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string> <string name="minimize_button_text" msgid="271592547935841753">"ചെറുതാക്കുക"</string> <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index a8bd85ed27f5..be02be111438 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string> <string name="minimize_button_text" msgid="271592547935841753">"Багасгах"</string> <string name="close_button_text" msgid="2913281996024033299">"Хаах"</string> + <string name="back_button_text" msgid="1469718707134137085">"Буцах"</string> + <string name="handle_text" msgid="1766582106752184456">"Бариул"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index 5874812d9281..dc884e9bf3dd 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string> <string name="minimize_button_text" msgid="271592547935841753">"लहान करा"</string> <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index 88270df81660..85d380e04889 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string> + <string name="handle_text" msgid="1766582106752184456">"Pemegang"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index 9a2d1bad094e..517098d76d4a 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -66,7 +66,7 @@ <string name="bubbles_user_education_description" msgid="4215862563054175407">"စကားဝိုင်းအသစ်များကို မျောနေသည့် သင်္ကေတများ သို့မဟုတ် ပူဖောင်းကွက်များအဖြစ် မြင်ရပါမည်။ ပူဖောင်းကွက်ကိုဖွင့်ရန် တို့ပါ။ ရွှေ့ရန် ၎င်းကို ဖိဆွဲပါ။"</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"ပူဖောင်းကွက်ကို အချိန်မရွေး ထိန်းချုပ်ရန်"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"ဤအက်ပ်မှနေ၍ ပူဖောင်းများကို ပိတ်ရန်အတွက် \'စီမံရန်\' ကို တို့ပါ"</string> - <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ရပြီ"</string> + <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"နားလည်ပြီ"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"လတ်တလော ပူဖောင်းကွက်များ မရှိပါ"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"လတ်တလော ပူဖောင်းကွက်များနှင့် ပိတ်လိုက်သော ပူဖောင်းကွက်များကို ဤနေရာတွင် မြင်ရပါမည်"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"ပူဖောင်းဖောက်သံ"</string> @@ -79,9 +79,11 @@ <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string> <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"မျက်နှာပြင် ခွဲ၍ပြသနိုင်ရန် နောက်အက်ပ်တစ်ခုကို ဖိဆွဲပါ"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string> - <string name="letterbox_education_got_it" msgid="4057634570866051177">"ရပြီ"</string> + <string name="letterbox_education_got_it" msgid="4057634570866051177">"နားလည်ပြီ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</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> + <string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string> + <string name="handle_text" msgid="1766582106752184456">"သုံးသူအမည်"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index ec9e635dd9bb..a2f24f2c948b 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index dc7d98d1f702..6a70d8da05ed 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string> <string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string> <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 5bae2a34100c..d1d7db80caf5 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index c5ef4f10ce39..52280a1daf24 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string> <string name="minimize_button_text" msgid="271592547935841753">"ଛୋଟ କରନ୍ତୁ"</string> <string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string> + <string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string> + <string name="handle_text" msgid="1766582106752184456">"ହେଣ୍ଡେଲ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index f0262052513c..d2a96d95db74 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string> <string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string> <string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index 22c0c37002c9..cc2b2c314a85 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index 0bbffb3182b5..3cb708dda567 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string> + <string name="handle_text" msgid="1766582106752184456">"Alça"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index 07dd4d73ebd3..e5be57848957 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Anterior"</string> + <string name="handle_text" msgid="1766582106752184456">"Indicador"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 0bbffb3182b5..3cb708dda567 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string> + <string name="handle_text" msgid="1766582106752184456">"Alça"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index 0aae6a509bdb..c03e043ee6f0 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string> + <string name="handle_text" msgid="1766582106752184456">"Ghidaj"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index f1ff000ebc4c..98a775e1e6b3 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string> <string name="minimize_button_text" msgid="271592547935841753">"Свернуть"</string> <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index 39bd260c2901..57240618ef19 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string> <string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string> <string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index 88592315a53c..e3dafb6c88e9 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Späť"</string> + <string name="handle_text" msgid="1766582106752184456">"Rukoväť"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index 22fe0f8bc657..12f022e7a1f6 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index 2a3671b85d0d..cdd870689886 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index 09471037fde7..b00c4f49f649 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string> <string name="minimize_button_text" msgid="271592547935841753">"Умањите"</string> <string name="close_button_text" msgid="2913281996024033299">"Затворите"</string> + <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> + <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index 70282a4b1a4d..fef3792b35a2 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index 1aab85c54add..2d10e2039a11 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index befc8eb6f808..0eeeca78cb86 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string> <string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string> <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index a3e21f745707..91c411f0d49c 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string> <string name="minimize_button_text" msgid="271592547935841753">"కుదించండి"</string> <string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string> + <string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string> + <string name="handle_text" msgid="1766582106752184456">"హ్యాండిల్"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index 63d3bb83832d..ee362dcedd0a 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string> <string name="minimize_button_text" msgid="271592547935841753">"ย่อ"</string> <string name="close_button_text" msgid="2913281996024033299">"ปิด"</string> + <string name="back_button_text" msgid="1469718707134137085">"กลับ"</string> + <string name="handle_text" msgid="1766582106752184456">"แฮนเดิล"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index 50334f5e772e..d82036568499 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string> + <string name="handle_text" msgid="1766582106752184456">"Handle"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index e1ea0df780e6..2b105bdb7963 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index 9e713c77cacd..a0925318ac26 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string> <string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string> <string name="close_button_text" msgid="2913281996024033299">"Закрити"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index 596cf4b15d40..883026a437a5 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string> <string name="minimize_button_text" msgid="271592547935841753">"چھوٹا کریں"</string> <string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string> + <string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string> + <string name="handle_text" msgid="1766582106752184456">"ہینڈل"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index 275940a22f25..0330125c3e6f 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index 24fb1ca57aec..6e4a7682854d 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -84,4 +84,8 @@ <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> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index e52a7b556be0..df911ed55ab3 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -84,4 +84,8 @@ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> <string name="close_button_text" msgid="2913281996024033299">"关闭"</string> + <!-- no translation found for back_button_text (1469718707134137085) --> + <skip /> + <!-- no translation found for handle_text (1766582106752184456) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index eed1e5299365..5a497d090f54 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> <string name="close_button_text" msgid="2913281996024033299">"關閉"</string> + <string name="back_button_text" msgid="1469718707134137085">"返去"</string> + <string name="handle_text" msgid="1766582106752184456">"控點"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index f9f28ab02aa6..2c2ce33f58ab 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -84,4 +84,6 @@ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string> <string name="close_button_text" msgid="2913281996024033299">"關閉"</string> + <string name="back_button_text" msgid="1469718707134137085">"返回"</string> + <string name="handle_text" msgid="1766582106752184456">"控點"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index ddb6a4c3166e..f48402264a73 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -84,4 +84,6 @@ <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> + <string name="back_button_text" msgid="1469718707134137085">"Emuva"</string> + <string name="handle_text" msgid="1766582106752184456">"Isibambo"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 0bc70857a113..3ee20ea95ee5 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -321,4 +321,21 @@ <!-- The smaller size of the dismiss target (shrinks when something is in the target). --> <dimen name="floating_dismiss_circle_small">120dp</dimen> + + <!-- The thickness of shadows of a window that has focus in DIP. --> + <dimen name="freeform_decor_shadow_focused_thickness">20dp</dimen> + + <!-- The thickness of shadows of a window that doesn't have focus in DIP. --> + <dimen name="freeform_decor_shadow_unfocused_thickness">5dp</dimen> + + <!-- 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> + + <dimen name="freeform_resize_handle">30dp</dimen> + + <dimen name="freeform_resize_corner">44dp</dimen> + </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java index 591e3476ecd9..215308d9e96e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java @@ -130,6 +130,10 @@ class ActivityEmbeddingAnimationAdapter { if (!cropRect.intersect(mWholeAnimationBounds)) { // Hide the surface when it is outside of the animation area. t.setAlpha(mLeash, 0); + } else if (mAnimation.hasExtension()) { + // Allow the surface to be shown in its original bounds in case we want to use edge + // extensions. + cropRect.union(mChange.getEndAbsBounds()); } // cropRect is in absolute coordinate, so we need to translate it to surface top left. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index 756d80204833..490975cce956 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -21,6 +21,7 @@ import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition; +import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; import android.animation.Animator; @@ -45,6 +46,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.function.Consumer; /** To run the ActivityEmbedding animations. */ class ActivityEmbeddingAnimationRunner { @@ -65,10 +67,31 @@ class ActivityEmbeddingAnimationRunner { void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction) { + // There may be some surface change that we want to apply after the start transaction is + // applied to make sure the surface is ready. + final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks = + new ArrayList<>(); final Animator animator = createAnimator(info, startTransaction, finishTransaction, - () -> mController.onAnimationFinished(transition)); - startTransaction.apply(); - animator.start(); + () -> mController.onAnimationFinished(transition), postStartTransactionCallbacks); + + // Start the animation. + if (!postStartTransactionCallbacks.isEmpty()) { + // postStartTransactionCallbacks require that the start transaction is already + // applied to run otherwise they may result in flickers and UI inconsistencies. + startTransaction.apply(true /* sync */); + + // Run tasks that require startTransaction to already be applied + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback : + postStartTransactionCallbacks) { + postStartTransactionCallback.accept(t); + } + t.apply(); + animator.start(); + } else { + startTransaction.apply(); + animator.start(); + } } /** @@ -85,9 +108,13 @@ class ActivityEmbeddingAnimationRunner { Animator createAnimator(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Runnable animationFinishCallback) { - final List<ActivityEmbeddingAnimationAdapter> adapters = - createAnimationAdapters(info, startTransaction, finishTransaction); + @NonNull Runnable animationFinishCallback, + @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) { + final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info, + startTransaction); + addEdgeExtensionIfNeeded(startTransaction, finishTransaction, postStartTransactionCallbacks, + adapters); + addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters); long duration = 0; for (ActivityEmbeddingAnimationAdapter adapter : adapters) { duration = Math.max(duration, adapter.getDurationHint()); @@ -131,8 +158,7 @@ class ActivityEmbeddingAnimationRunner { */ @NonNull private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters( - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction) { + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { boolean isChangeTransition = false; for (TransitionInfo.Change change : info.getChanges()) { if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) { @@ -148,25 +174,23 @@ class ActivityEmbeddingAnimationRunner { return createChangeAnimationAdapters(info, startTransaction); } if (Transitions.isClosingType(info.getType())) { - return createCloseAnimationAdapters(info, startTransaction, finishTransaction); + return createCloseAnimationAdapters(info); } - return createOpenAnimationAdapters(info, startTransaction, finishTransaction); + return createOpenAnimationAdapters(info); } @NonNull private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters( - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction) { - return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction, - true /* isOpening */, mAnimationSpec::loadOpenAnimation); + @NonNull TransitionInfo info) { + return createOpenCloseAnimationAdapters(info, true /* isOpening */, + mAnimationSpec::loadOpenAnimation); } @NonNull private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters( - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction) { - return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction, - false /* isOpening */, mAnimationSpec::loadCloseAnimation); + @NonNull TransitionInfo info) { + return createOpenCloseAnimationAdapters(info, false /* isOpening */, + mAnimationSpec::loadCloseAnimation); } /** @@ -175,8 +199,7 @@ class ActivityEmbeddingAnimationRunner { */ @NonNull private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters( - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, boolean isOpening, + @NonNull TransitionInfo info, boolean isOpening, @NonNull AnimationProvider animationProvider) { // We need to know if the change window is only a partial of the whole animation screen. // If so, we will need to adjust it to make the whole animation screen looks like one. @@ -200,8 +223,7 @@ class ActivityEmbeddingAnimationRunner { final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); for (TransitionInfo.Change change : openingChanges) { final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( - info, change, startTransaction, finishTransaction, animationProvider, - openingWholeScreenBounds); + info, change, animationProvider, openingWholeScreenBounds); if (isOpening) { adapter.overrideLayer(offsetLayer++); } @@ -209,8 +231,7 @@ class ActivityEmbeddingAnimationRunner { } for (TransitionInfo.Change change : closingChanges) { final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( - info, change, startTransaction, finishTransaction, animationProvider, - closingWholeScreenBounds); + info, change, animationProvider, closingWholeScreenBounds); if (!isOpening) { adapter.overrideLayer(offsetLayer++); } @@ -219,20 +240,51 @@ class ActivityEmbeddingAnimationRunner { return adapters; } + /** Adds edge extension to the surfaces that have such an animation property. */ + private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks, + @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { + for (ActivityEmbeddingAnimationAdapter adapter : adapters) { + final Animation animation = adapter.mAnimation; + if (!animation.hasExtension()) { + continue; + } + final TransitionInfo.Change change = adapter.mChange; + if (Transitions.isOpeningType(adapter.mChange.getMode())) { + // Need to screenshot after startTransaction is applied otherwise activity + // may not be visible or ready yet. + postStartTransactionCallbacks.add( + t -> edgeExtendWindow(change, animation, t, finishTransaction)); + } else { + // Can screenshot now (before startTransaction is applied) + edgeExtendWindow(change, animation, startTransaction, finishTransaction); + } + } + } + + /** Adds background color to the transition if any animation has such a property. */ + private void addBackgroundColorIfNeeded(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { + for (ActivityEmbeddingAnimationAdapter adapter : adapters) { + final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange, + adapter.mAnimation, 0 /* defaultColor */); + if (backgroundColor != 0) { + // We only need to show one color. + addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction, + finishTransaction); + return; + } + } + } + @NonNull private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter( @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) { final Animation animation = animationProvider.get(info, change, wholeAnimationBounds); - // We may want to show a background color for open/close transition. - final int backgroundColor = getTransitionBackgroundColorIfSet(info, change, animation, - 0 /* defaultColor */); - if (backgroundColor != 0) { - addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction, - finishTransaction); - } return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(), wholeAnimationBounds); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index eb6ac7615266..58b23667dc18 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -181,15 +181,15 @@ class ActivityEmbeddingAnimationSpec { @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = Transitions.isOpeningType(change.getMode()); final Animation animation; - // TODO(b/207070762): Implement edgeExtension version if (shouldShowBackdrop(info, change)) { animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter ? com.android.internal.R.anim.task_fragment_clear_top_open_enter : com.android.internal.R.anim.task_fragment_clear_top_open_exit); } else { + // Use the same edge extension animation as regular activity open. animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter - ? com.android.internal.R.anim.task_fragment_open_enter - : com.android.internal.R.anim.task_fragment_open_exit); + ? com.android.internal.R.anim.activity_open_enter + : com.android.internal.R.anim.activity_open_exit); } // Use the whole animation bounds instead of the change bounds, so that when multiple change // targets are opening at the same time, the animation applied to each will be the same. @@ -205,15 +205,15 @@ class ActivityEmbeddingAnimationSpec { @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = Transitions.isOpeningType(change.getMode()); final Animation animation; - // TODO(b/207070762): Implement edgeExtension version if (shouldShowBackdrop(info, change)) { animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter ? com.android.internal.R.anim.task_fragment_clear_top_close_enter : com.android.internal.R.anim.task_fragment_clear_top_close_exit); } else { + // Use the same edge extension animation as regular activity close. animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter - ? com.android.internal.R.anim.task_fragment_close_enter - : com.android.internal.R.anim.task_fragment_close_exit); + ? com.android.internal.R.anim.activity_close_enter + : com.android.internal.R.anim.activity_close_exit); } // Use the whole animation bounds instead of the change bounds, so that when multiple change // targets are closing at the same time, the animation applied to each will be the same. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 93413dbe7e5f..725b20525bf7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -28,10 +28,6 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTRO import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_BOTTOM; -import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_LEFT; -import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_NONE; -import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_RIGHT; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_GROUP_CANCELLED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_INVALID_INTENT; @@ -41,6 +37,7 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED; +import static com.android.wm.shell.floating.FloatingTasksController.SHOW_FLOATING_TASKS_AS_BUBBLES; import android.annotation.NonNull; import android.annotation.UserIdInt; @@ -59,10 +56,8 @@ import android.content.pm.ShortcutInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.PixelFormat; -import android.graphics.PointF; import android.graphics.Rect; import android.os.Binder; -import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; @@ -126,18 +121,6 @@ public class BubbleController implements ConfigurationChangeListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES; - // TODO(b/173386799) keep in sync with Launcher3, not hooked up to anything - public static final String EXTRA_TASKBAR_CREATED = "taskbarCreated"; - public static final String EXTRA_BUBBLE_OVERFLOW_OPENED = "bubbleOverflowOpened"; - public static final String EXTRA_TASKBAR_VISIBLE = "taskbarVisible"; - public static final String EXTRA_TASKBAR_POSITION = "taskbarPosition"; - public static final String EXTRA_TASKBAR_ICON_SIZE = "taskbarIconSize"; - public static final String EXTRA_TASKBAR_BUBBLE_XY = "taskbarBubbleXY"; - public static final String EXTRA_TASKBAR_SIZE = "taskbarSize"; - public static final String LEFT_POSITION = "Left"; - public static final String RIGHT_POSITION = "Right"; - public static final String BOTTOM_POSITION = "Bottom"; - // Should match with PhoneWindowManager private static final String SYSTEM_DIALOG_REASON_KEY = "reason"; private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav"; @@ -470,52 +453,6 @@ public class BubbleController implements ConfigurationChangeListener { mBubbleData.setExpanded(true); } - /** Called when any taskbar state changes (e.g. visibility, position, sizes). */ - private void onTaskbarChanged(Bundle b) { - if (b == null) { - return; - } - boolean isVisible = b.getBoolean(EXTRA_TASKBAR_VISIBLE, false /* default */); - String position = b.getString(EXTRA_TASKBAR_POSITION, RIGHT_POSITION /* default */); - @BubblePositioner.TaskbarPosition int taskbarPosition = TASKBAR_POSITION_NONE; - switch (position) { - case LEFT_POSITION: - taskbarPosition = TASKBAR_POSITION_LEFT; - break; - case RIGHT_POSITION: - taskbarPosition = TASKBAR_POSITION_RIGHT; - break; - case BOTTOM_POSITION: - taskbarPosition = TASKBAR_POSITION_BOTTOM; - break; - } - int[] itemPosition = b.getIntArray(EXTRA_TASKBAR_BUBBLE_XY); - int iconSize = b.getInt(EXTRA_TASKBAR_ICON_SIZE); - int taskbarSize = b.getInt(EXTRA_TASKBAR_SIZE); - Log.w(TAG, "onTaskbarChanged:" - + " isVisible: " + isVisible - + " position: " + position - + " itemPosition: " + itemPosition[0] + "," + itemPosition[1] - + " iconSize: " + iconSize); - PointF point = new PointF(itemPosition[0], itemPosition[1]); - mBubblePositioner.setPinnedLocation(isVisible ? point : null); - mBubblePositioner.updateForTaskbar(iconSize, taskbarPosition, isVisible, taskbarSize); - if (mStackView != null) { - if (isVisible && b.getBoolean(EXTRA_TASKBAR_CREATED, false /* default */)) { - // If taskbar was created, add and remove the window so that bubbles display on top - removeFromWindowManagerMaybe(); - addToWindowManagerMaybe(); - } - mStackView.updateStackPosition(); - mBubbleIconFactory = new BubbleIconFactory(mContext); - mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext); - mStackView.onDisplaySizeChanged(); - } - if (b.getBoolean(EXTRA_BUBBLE_OVERFLOW_OPENED, false)) { - openBubbleOverflow(); - } - } - /** * Called when the status bar has become visible or invisible (either permanently or * temporarily). @@ -654,6 +591,11 @@ public class BubbleController implements ConfigurationChangeListener { } mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); } + if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) { + mBubblePositioner.setUsePinnedLocation(true); + } else { + mBubblePositioner.setUsePinnedLocation(false); + } addToWindowManagerMaybe(); } @@ -1732,13 +1674,6 @@ public class BubbleController implements ConfigurationChangeListener { } @Override - public void onTaskbarChanged(Bundle b) { - mMainExecutor.execute(() -> { - BubbleController.this.onTaskbarChanged(b); - }); - } - - @Override public boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, IntConsumer removeCallback, Executor callbackExecutor) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index dbad5df9cf56..07c58527a815 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -713,6 +713,9 @@ public class BubblePositioner { * is being shown. */ public PointF getDefaultStartPosition() { + if (mPinLocation != null) { + return mPinLocation; + } // Start on the left if we're in LTR, right otherwise. final boolean startOnLeft = mContext.getResources().getConfiguration().getLayoutDirection() @@ -766,11 +769,18 @@ public class BubblePositioner { } /** - * In some situations bubbles will be pinned to a specific onscreen location. This sets the - * location to anchor the stack to. + * In some situations bubbles will be pinned to a specific onscreen location. This sets whether + * bubbles should be pinned or not. */ - public void setPinnedLocation(PointF point) { - mPinLocation = point; + public void setUsePinnedLocation(boolean usePinnedLocation) { + if (usePinnedLocation) { + mShowingInTaskbar = true; + mPinLocation = new PointF(mPositionRect.right - mBubbleSize, + mPositionRect.bottom - mBubbleSize); + } else { + mPinLocation = null; + mShowingInTaskbar = false; + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index be100bb1dd34..6efad097e3cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -613,16 +613,11 @@ public class BubbleStackView extends FrameLayout mBubbleContainer.setActiveController(mStackAnimationController); hideFlyoutImmediate(); - if (mPositioner.showingInTaskbar()) { - // In taskbar, the stack isn't draggable so we shouldn't dispatch touch events. - mMagnetizedObject = null; - } else { - // Save the magnetized stack so we can dispatch touch events to it. - mMagnetizedObject = mStackAnimationController.getMagnetizedStack(); - mMagnetizedObject.clearAllTargets(); - mMagnetizedObject.addTarget(mMagneticTarget); - mMagnetizedObject.setMagnetListener(mStackMagnetListener); - } + // Save the magnetized stack so we can dispatch touch events to it. + mMagnetizedObject = mStackAnimationController.getMagnetizedStack(); + mMagnetizedObject.clearAllTargets(); + mMagnetizedObject.addTarget(mMagneticTarget); + mMagnetizedObject.setMagnetListener(mStackMagnetListener); mIsDraggingStack = true; @@ -641,10 +636,7 @@ public class BubbleStackView extends FrameLayout public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, float viewInitialY, float dx, float dy) { // If we're expanding or collapsing, ignore all touch events. - if (mIsExpansionAnimating - // Also ignore events if we shouldn't be draggable. - || (mPositioner.showingInTaskbar() && !mIsExpanded) - || mShowedUserEducationInTouchListenerActive) { + if (mIsExpansionAnimating || mShowedUserEducationInTouchListenerActive) { return; } @@ -661,7 +653,7 @@ public class BubbleStackView extends FrameLayout // bubble since it's stuck to the target. if (!passEventToMagnetizedObject(ev)) { updateBubbleShadows(true /* showForAllBubbles */); - if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) { + if (mBubbleData.isExpanded()) { mExpandedAnimationController.dragBubbleOut( v, viewInitialX + dx, viewInitialY + dy); } else { @@ -678,9 +670,7 @@ public class BubbleStackView extends FrameLayout public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, float viewInitialY, float dx, float dy, float velX, float velY) { // If we're expanding or collapsing, ignore all touch events. - if (mIsExpansionAnimating - // Also ignore events if we shouldn't be draggable. - || (mPositioner.showingInTaskbar() && !mIsExpanded)) { + if (mIsExpansionAnimating) { return; } if (mShowedUserEducationInTouchListenerActive) { @@ -696,6 +686,8 @@ public class BubbleStackView extends FrameLayout // Re-show the expanded view if we hid it. showExpandedViewIfNeeded(); + } else if (mPositioner.showingInTaskbar()) { + mStackAnimationController.snapStackBack(); } else { // Fling the stack to the edge, and save whether or not it's going to end up on // the left side of the screen. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index b3104b518440..7f891ec6d215 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -23,7 +23,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.app.NotificationChannel; import android.content.pm.UserInfo; -import android.os.Bundle; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; @@ -114,9 +113,6 @@ public interface Bubbles { @Nullable Bubble getBubbleWithShortcutId(String shortcutId); - /** Called for any taskbar changes. */ - void onTaskbarChanged(Bundle b); - /** * We intercept notification entries (including group summaries) dismissed by the user when * there is an active bubble associated with it. We do this so that developers can still diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index 961722ba9bc0..0ee0ea60a1bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -417,6 +417,17 @@ public class StackAnimationController extends } /** + * Snaps the stack back to the previous resting position. + */ + public void snapStackBack() { + if (mLayout == null) { + return; + } + PointF p = getStackPositionAlongNearestHorizontalEdge(); + springStackAfterFling(p.x, p.y); + } + + /** * Where the stack would be if it were snapped to the nearest horizontal edge (left or right). */ public PointF getStackPositionAlongNearestHorizontalEdge() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 30124a5363a4..616d447247de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -745,6 +745,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Directly move PiP to its final destination bounds without animation. mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds); } + + // if the pip window size is beyond allowed bounds user resize to normal bounds + if (mPipBoundsState.getBounds().width() < mPipBoundsState.getMinSize().x + || mPipBoundsState.getBounds().width() > mPipBoundsState.getMaxSize().x + || mPipBoundsState.getBounds().height() < mPipBoundsState.getMinSize().y + || mPipBoundsState.getBounds().height() > mPipBoundsState.getMaxSize().y) { + mTouchHandler.userResizeTo(mPipBoundsState.getNormalBounds(), snapFraction); + } + } else { updateDisplayLayout.run(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 89d85e4b292d..41ff0b35a035 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -96,6 +96,7 @@ public class PipResizeGestureHandler { private final Rect mDisplayBounds = new Rect(); private final Function<Rect, Rect> mMovementBoundsSupplier; private final Runnable mUpdateMovementBoundsRunnable; + private final Consumer<Rect> mUpdateResizeBoundsCallback; private int mDelta; private float mTouchSlop; @@ -137,6 +138,13 @@ public class PipResizeGestureHandler { mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; mPinchResizingAlgorithm = new PipPinchResizingAlgorithm(); + + mUpdateResizeBoundsCallback = (rect) -> { + mUserResizeBounds.set(rect); + mMotionHelper.synchronizePinnedStackBounds(); + mUpdateMovementBoundsRunnable.run(); + resetState(); + }; } public void init() { @@ -508,15 +516,50 @@ public class PipResizeGestureHandler { } } + private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) { + final int leftEdge = bounds.left; + + + final int fromLeft = Math.abs(leftEdge - movementBounds.left); + final int fromRight = Math.abs(movementBounds.right - leftEdge); + + // The PIP will be snapped to either the right or left edge, so calculate which one + // is closest to the current position. + final int newLeft = fromLeft < fromRight + ? movementBounds.left : movementBounds.right; + + bounds.offsetTo(newLeft, mLastResizeBounds.top); + } + + /** + * Resizes the pip window and updates user-resized bounds. + * + * @param bounds target bounds to resize to + * @param snapFraction snap fraction to apply after resizing + */ + void userResizeTo(Rect bounds, float snapFraction) { + Rect finalBounds = new Rect(bounds); + + // get the current movement bounds + final Rect movementBounds = mPipBoundsAlgorithm.getMovementBounds(finalBounds); + + // snap the target bounds to the either left or right edge, by choosing the closer one + snapToMovementBoundsEdge(finalBounds, movementBounds); + + // apply the requested snap fraction onto the target bounds + mPipBoundsAlgorithm.applySnapFraction(finalBounds, snapFraction); + + // resize from current bounds to target bounds without animation + mPipTaskOrganizer.scheduleUserResizePip(mPipBoundsState.getBounds(), finalBounds, null); + // set the flag that pip has been resized + mPipBoundsState.setHasUserResizedPip(true); + + // finish the resize operation and update the state of the bounds + mPipTaskOrganizer.scheduleFinishResizePip(finalBounds, mUpdateResizeBoundsCallback); + } + private void finishResize() { if (!mLastResizeBounds.isEmpty()) { - final Consumer<Rect> callback = (rect) -> { - mUserResizeBounds.set(mLastResizeBounds); - mMotionHelper.synchronizePinnedStackBounds(); - mUpdateMovementBoundsRunnable.run(); - resetState(); - }; - // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped // position correctly. Drag-resize does not need to move, so just finalize resize. if (mOngoingPinchToResize) { @@ -526,24 +569,23 @@ public class PipResizeGestureHandler { || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); } - final int leftEdge = mLastResizeBounds.left; - final Rect movementBounds = - mPipBoundsAlgorithm.getMovementBounds(mLastResizeBounds); - final int fromLeft = Math.abs(leftEdge - movementBounds.left); - final int fromRight = Math.abs(movementBounds.right - leftEdge); - // The PIP will be snapped to either the right or left edge, so calculate which one - // is closest to the current position. - final int newLeft = fromLeft < fromRight - ? movementBounds.left : movementBounds.right; - mLastResizeBounds.offsetTo(newLeft, mLastResizeBounds.top); + + // get the current movement bounds + final Rect movementBounds = mPipBoundsAlgorithm + .getMovementBounds(mLastResizeBounds); + + // snap mLastResizeBounds to the correct edge based on movement bounds + snapToMovementBoundsEdge(mLastResizeBounds, movementBounds); + final float snapFraction = mPipBoundsAlgorithm.getSnapFraction( mLastResizeBounds, movementBounds); mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, - PINCH_RESIZE_SNAP_DURATION, mAngle, callback); + PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback); } else { mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, - PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback); + PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, + mUpdateResizeBoundsCallback); } final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f; mPipDismissTargetHandler diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 1f3f31e025a0..975d4bba276e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -825,6 +825,16 @@ public class PipTouchHandler { } /** + * Resizes the pip window and updates user resized bounds + * + * @param bounds target bounds to resize to + * @param snapFraction snap fraction to apply after resizing + */ + void userResizeTo(Rect bounds, float snapFraction) { + mPipResizeGestureHandler.userResizeTo(bounds, snapFraction); + } + + /** * Gesture controlling normal movement of the PIP. */ private class DefaultPipTouchGesture extends PipTouchGesture { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl index b71cc32a0347..1a6c1d65db03 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl @@ -16,7 +16,7 @@ package com.android.wm.shell.recents; -import android.app.ActivityManager; +import android.app.ActivityManager.RunningTaskInfo; import com.android.wm.shell.recents.IRecentTasksListener; import com.android.wm.shell.util.GroupedRecentTaskInfo; @@ -44,5 +44,5 @@ interface IRecentTasks { /** * Gets the set of running tasks. */ - ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) = 4; + RunningTaskInfo[] getRunningTasks(int maxNum) = 4; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl index 59f72335678e..e8f58fe2bfad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl @@ -16,7 +16,7 @@ package com.android.wm.shell.recents; -import android.app.ActivityManager; +import android.app.ActivityManager.RunningTaskInfo; /** * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. @@ -31,10 +31,10 @@ oneway interface IRecentTasksListener { /** * Called when a running task appears. */ - void onRunningTaskAppeared(in ActivityManager.RunningTaskInfo taskInfo); + void onRunningTaskAppeared(in RunningTaskInfo taskInfo); /** * Called when a running task vanishes. */ - void onRunningTaskVanished(in ActivityManager.RunningTaskInfo taskInfo); -}
\ No newline at end of file + void onRunningTaskVanished(in RunningTaskInfo taskInfo); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 9102bd3a9b6a..e2ac01f7b003 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -207,6 +207,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; private DefaultMixedHandler mMixedHandler; + private final Toast mSplitUnsupportedToast; private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = new SplitWindowManager.ParentContainerCallbacks() { @@ -300,6 +301,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId)); transitions.addHandler(this); mTaskOrganizer.addFocusListener(this); + mSplitUnsupportedToast = Toast.makeText(mContext, + R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT); } @VisibleForTesting @@ -329,6 +332,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayController.addDisplayWindowListener(this); mDisplayLayout = new DisplayLayout(); transitions.addHandler(this); + mSplitUnsupportedToast = Toast.makeText(mContext, + R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT); } public void setMixedHandler(DefaultMixedHandler mixedHandler) { @@ -470,6 +475,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + mSplitUnsupportedToast.show(); } else { // Switch the split position if launching as MULTIPLE_TASK failed. if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { @@ -736,6 +742,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + mSplitUnsupportedToast.show(); } else { mSyncQueue.queue(evictWct); } @@ -2287,13 +2294,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onNoLongerSupportMultiWindow() { if (mMainStage.isActive()) { - final Toast splitUnsupportedToast = Toast.makeText(mContext, - R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT); final boolean isMainStage = mMainStageListener == this; if (!ENABLE_SHELL_TRANSITIONS) { StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); - splitUnsupportedToast.show(); + mSplitUnsupportedToast.show(); return; } @@ -2302,7 +2307,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareExitSplitScreen(stageType, wct); mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); - splitUnsupportedToast.show(); + mSplitUnsupportedToast.show(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index dbb2948de5db..9c2c2fa8598a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -59,6 +59,7 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN; import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition; +import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty; @@ -76,10 +77,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.Canvas; import android.graphics.Insets; -import android.graphics.Paint; -import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -89,7 +87,6 @@ import android.os.IBinder; import android.os.UserHandle; import android.util.ArrayMap; import android.view.Choreographer; -import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; @@ -525,123 +522,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - private void edgeExtendWindow(TransitionInfo.Change change, - Animation a, SurfaceControl.Transaction startTransaction, - SurfaceControl.Transaction finishTransaction) { - // Do not create edge extension surface for transfer starting window change. - // The app surface could be empty thus nothing can draw on the hardware renderer, which will - // block this thread when calling Surface#unlockCanvasAndPost. - if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { - return; - } - final Transformation transformationAtStart = new Transformation(); - a.getTransformationAt(0, transformationAtStart); - final Transformation transformationAtEnd = new Transformation(); - a.getTransformationAt(1, transformationAtEnd); - - // We want to create an extension surface that is the maximal size and the animation will - // take care of cropping any part that overflows. - final Insets maxExtensionInsets = Insets.min( - transformationAtStart.getInsets(), transformationAtEnd.getInsets()); - - final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(), - change.getEndAbsBounds().height()); - final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(), - change.getEndAbsBounds().width()); - if (maxExtensionInsets.left < 0) { - final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight); - final Rect extensionRect = new Rect(0, 0, - -maxExtensionInsets.left, targetSurfaceHeight); - final int xPos = maxExtensionInsets.left; - final int yPos = 0; - createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, - "Left Edge Extension", startTransaction, finishTransaction); - } - - if (maxExtensionInsets.top < 0) { - final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1); - final Rect extensionRect = new Rect(0, 0, - targetSurfaceWidth, -maxExtensionInsets.top); - final int xPos = 0; - final int yPos = maxExtensionInsets.top; - createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, - "Top Edge Extension", startTransaction, finishTransaction); - } - - if (maxExtensionInsets.right < 0) { - final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0, - targetSurfaceWidth, targetSurfaceHeight); - final Rect extensionRect = new Rect(0, 0, - -maxExtensionInsets.right, targetSurfaceHeight); - final int xPos = targetSurfaceWidth; - final int yPos = 0; - createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, - "Right Edge Extension", startTransaction, finishTransaction); - } - - if (maxExtensionInsets.bottom < 0) { - final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1, - targetSurfaceWidth, targetSurfaceHeight); - final Rect extensionRect = new Rect(0, 0, - targetSurfaceWidth, -maxExtensionInsets.bottom); - final int xPos = maxExtensionInsets.left; - final int yPos = targetSurfaceHeight; - createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, - "Bottom Edge Extension", startTransaction, finishTransaction); - } - } - - private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds, - Rect extensionRect, int xPos, int yPos, String layerName, - SurfaceControl.Transaction startTransaction, - SurfaceControl.Transaction finishTransaction) { - final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder() - .setName(layerName) - .setParent(surfaceToExtend) - .setHidden(true) - .setCallsite("DefaultTransitionHandler#startAnimation") - .setOpaque(true) - .setBufferSize(extensionRect.width(), extensionRect.height()) - .build(); - - SurfaceControl.LayerCaptureArgs captureArgs = - new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend) - .setSourceCrop(edgeBounds) - .setFrameScale(1) - .setPixelFormat(PixelFormat.RGBA_8888) - .setChildrenOnly(true) - .setAllowProtected(true) - .build(); - final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer = - SurfaceControl.captureLayers(captureArgs); - - if (edgeBuffer == null) { - ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, - "Failed to capture edge of window."); - return null; - } - - android.graphics.BitmapShader shader = - new android.graphics.BitmapShader(edgeBuffer.asBitmap(), - android.graphics.Shader.TileMode.CLAMP, - android.graphics.Shader.TileMode.CLAMP); - final Paint paint = new Paint(); - paint.setShader(shader); - - final Surface surface = new Surface(edgeExtensionLayer); - Canvas c = surface.lockHardwareCanvas(); - c.drawRect(extensionRect, paint); - surface.unlockCanvasAndPost(c); - surface.release(); - - startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE); - startTransaction.setPosition(edgeExtensionLayer, xPos, yPos); - startTransaction.setVisibility(edgeExtensionLayer, true); - finishTransaction.remove(edgeExtensionLayer); - - return edgeExtensionLayer; - } - @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index efee6f40b53e..b75c55274cff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; @@ -34,10 +35,19 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.BitmapShader; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Shader; import android.os.SystemProperties; +import android.view.Surface; import android.view.SurfaceControl; import android.view.animation.Animation; +import android.view.animation.Transformation; import android.window.TransitionInfo; import com.android.internal.R; @@ -217,4 +227,126 @@ public class TransitionAnimationHelper { .show(animationBackgroundSurface); finishTransaction.remove(animationBackgroundSurface); } + + /** + * Adds edge extension surface to the given {@code change} for edge extension animation. + */ + public static void edgeExtendWindow(@NonNull TransitionInfo.Change change, + @NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + // Do not create edge extension surface for transfer starting window change. + // The app surface could be empty thus nothing can draw on the hardware renderer, which will + // block this thread when calling Surface#unlockCanvasAndPost. + if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { + return; + } + final Transformation transformationAtStart = new Transformation(); + a.getTransformationAt(0, transformationAtStart); + final Transformation transformationAtEnd = new Transformation(); + a.getTransformationAt(1, transformationAtEnd); + + // We want to create an extension surface that is the maximal size and the animation will + // take care of cropping any part that overflows. + final Insets maxExtensionInsets = Insets.min( + transformationAtStart.getInsets(), transformationAtEnd.getInsets()); + + final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(), + change.getEndAbsBounds().height()); + final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(), + change.getEndAbsBounds().width()); + if (maxExtensionInsets.left < 0) { + final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + -maxExtensionInsets.left, targetSurfaceHeight); + final int xPos = maxExtensionInsets.left; + final int yPos = 0; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Left Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.top < 0) { + final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1); + final Rect extensionRect = new Rect(0, 0, + targetSurfaceWidth, -maxExtensionInsets.top); + final int xPos = 0; + final int yPos = maxExtensionInsets.top; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Top Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.right < 0) { + final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0, + targetSurfaceWidth, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + -maxExtensionInsets.right, targetSurfaceHeight); + final int xPos = targetSurfaceWidth; + final int yPos = 0; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Right Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.bottom < 0) { + final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1, + targetSurfaceWidth, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + targetSurfaceWidth, -maxExtensionInsets.bottom); + final int xPos = maxExtensionInsets.left; + final int yPos = targetSurfaceHeight; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Bottom Edge Extension", startTransaction, finishTransaction); + } + } + + /** + * Takes a screenshot of {@code surfaceToExtend}'s edge and extends it for edge extension + * animation. + */ + private static SurfaceControl createExtensionSurface(@NonNull SurfaceControl surfaceToExtend, + @NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos, + @NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder() + .setName(layerName) + .setParent(surfaceToExtend) + .setHidden(true) + .setCallsite("TransitionAnimationHelper#createExtensionSurface") + .setOpaque(true) + .setBufferSize(extensionRect.width(), extensionRect.height()) + .build(); + + final SurfaceControl.LayerCaptureArgs captureArgs = + new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend) + .setSourceCrop(edgeBounds) + .setFrameScale(1) + .setPixelFormat(PixelFormat.RGBA_8888) + .setChildrenOnly(true) + .setAllowProtected(true) + .build(); + final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer = + SurfaceControl.captureLayers(captureArgs); + + if (edgeBuffer == null) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Failed to capture edge of window."); + return null; + } + + final BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(), + Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + final Paint paint = new Paint(); + paint.setShader(shader); + + final Surface surface = new Surface(edgeExtensionLayer); + final Canvas c = surface.lockHardwareCanvas(); + c.drawRect(extensionRect, paint); + surface.unlockCanvasAndPost(c); + surface.release(); + + startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE); + startTransaction.setPosition(edgeExtensionLayer, xPos, yPos); + startTransaction.setVisibility(edgeExtensionLayer, true); + finishTransaction.remove(edgeExtensionLayer); + + return edgeExtensionLayer; + } } 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 beace75d9fca..7d1f130daaef 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 @@ -21,7 +21,6 @@ import android.app.WindowConfiguration; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; -import android.graphics.Rect; import android.graphics.drawable.VectorDrawable; import android.os.Handler; import android.view.Choreographer; @@ -43,22 +42,6 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus; * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't. */ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> { - // The thickness of shadows of a window that has focus in DIP. - private static final int DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP = 20; - // The thickness of shadows of a window that doesn't have focus in DIP. - private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5; - - // Height of button (32dp) + 2 * margin (5dp each) - private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42; - // Width of buttons (64dp) + handle (128dp) + padding (24dp total) - private static final int DECOR_CAPTION_WIDTH_IN_DIP = 216; - private static final int RESIZE_HANDLE_IN_DIP = 30; - private static final int RESIZE_CORNER_IN_DIP = 44; - - private static final Rect EMPTY_OUTSET = new Rect(); - private static final Rect RESIZE_HANDLE_OUTSET = new Rect( - RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP); - private final Handler mHandler; private final Choreographer mChoreographer; private final SyncTransactionQueue mSyncQueue; @@ -69,6 +52,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL private DragResizeInputListener mDragResizeListener; + private RelayoutParams mRelayoutParams = new RelayoutParams(); private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult = new WindowDecoration.RelayoutResult<>(); @@ -114,19 +98,31 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL void relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - final int shadowRadiusDp = taskInfo.isFocused - ? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP; + final int shadowRadiusID = taskInfo.isFocused + ? R.dimen.freeform_decor_shadow_focused_thickness + : R.dimen.freeform_decor_shadow_unfocused_thickness; final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM; final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable; - final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET; WindowDecorLinearLayout oldRootView = mResult.mRootView; final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - relayout(taskInfo, R.layout.caption_window_decoration, oldRootView, - DECOR_CAPTION_HEIGHT_IN_DIP, DECOR_CAPTION_WIDTH_IN_DIP, outset, shadowRadiusDp, - startT, finishT, wct, mResult); + + int outsetLeftId = R.dimen.freeform_resize_handle; + int outsetTopId = R.dimen.freeform_resize_handle; + int outsetRightId = R.dimen.freeform_resize_handle; + int outsetBottomId = R.dimen.freeform_resize_handle; + + mRelayoutParams.mRunningTaskInfo = taskInfo; + mRelayoutParams.mLayoutResId = R.layout.caption_window_decoration; + 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); + } + relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); mTaskOrganizer.applyTransaction(wct); @@ -167,10 +163,12 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL } int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop(); - + int resize_handle = mResult.mRootView.getResources() + .getDimensionPixelSize(R.dimen.freeform_resize_handle); + int resize_corner = mResult.mRootView.getResources() + .getDimensionPixelSize(R.dimen.freeform_resize_corner); mDragResizeListener.setGeometry( - mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP), - (int) (mResult.mDensity * RESIZE_CORNER_IN_DIP), touchSlop); + mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop); } /** @@ -218,7 +216,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL View handle = caption.findViewById(R.id.caption_handle); VectorDrawable handleBackground = (VectorDrawable) handle.getBackground(); handleBackground.setTintList(buttonTintColor); - caption.setBackgroundColor(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT); + caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT); } private void closeDragResizeListener() { 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 bf863ea2c7ab..01cab9aae312 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 @@ -19,11 +19,11 @@ package com.android.wm.shell.windowdecor; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; -import android.util.DisplayMetrics; import android.view.Display; import android.view.InsetsState; import android.view.LayoutInflater; @@ -142,15 +142,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> */ abstract void relayout(RunningTaskInfo taskInfo); - void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp, - float captionWidthDp, Rect outsetsDp, float shadowRadiusDp, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - WindowContainerTransaction wct, RelayoutResult<T> outResult) { + void relayout(RelayoutParams params, SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, + RelayoutResult<T> outResult) { outResult.reset(); final Configuration oldTaskConfig = mTaskInfo.getConfiguration(); - if (taskInfo != null) { - mTaskInfo = taskInfo; + if (params.mRunningTaskInfo != null) { + mTaskInfo = params.mRunningTaskInfo; } if (!mTaskInfo.isVisible) { @@ -159,7 +158,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> return; } - if (rootView == null && layoutResId == 0) { + if (rootView == null && params.mLayoutResId == 0) { throw new IllegalArgumentException("layoutResId and rootView can't both be invalid."); } @@ -176,15 +175,15 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> return; } mDecorWindowContext = mContext.createConfigurationContext(taskConfig); - if (layoutResId != 0) { - outResult.mRootView = - (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null); + if (params.mLayoutResId != 0) { + outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) + .inflate(params.mLayoutResId, null); } } if (outResult.mRootView == null) { - outResult.mRootView = - (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null); + outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) + .inflate(params.mLayoutResId , null); } // DecorationContainerSurface @@ -200,18 +199,18 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); - outResult.mDensity = taskConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; - final int decorContainerOffsetX = -(int) (outsetsDp.left * outResult.mDensity); - final int decorContainerOffsetY = -(int) (outsetsDp.top * outResult.mDensity); + final int decorContainerOffsetX = -loadResource(params.mOutsetLeftId); + final int decorContainerOffsetY = -loadResource(params.mOutsetTopId); outResult.mWidth = taskBounds.width() - + (int) (outsetsDp.right * outResult.mDensity) + + loadResource(params.mOutsetRightId) - decorContainerOffsetX; outResult.mHeight = taskBounds.height() - + (int) (outsetsDp.bottom * outResult.mDensity) + + loadResource(params.mOutsetBottomId) - decorContainerOffsetY; startT.setPosition( mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY) - .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight) + .setWindowCrop(mDecorationContainerSurface, + outResult.mWidth, outResult.mHeight) // TODO(b/244455401): Change the z-order when it's better organized .setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1) .show(mDecorationContainerSurface); @@ -226,12 +225,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .build(); } - float shadowRadius = outResult.mDensity * shadowRadiusDp; + float shadowRadius = loadResource(params.mShadowRadiusId); int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor(); mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f; mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; - startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height()) + startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), + taskBounds.height()) .setShadowRadius(mTaskBackgroundSurface, shadowRadius) .setColor(mTaskBackgroundSurface, mTmpColor) // TODO(b/244455401): Change the z-order when it's better organized @@ -248,8 +248,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .build(); } - final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity); - final int captionWidth = (int) Math.ceil(captionWidthDp * outResult.mDensity); + final int captionHeight = loadResource(params.mCaptionHeightId); + final int captionWidth = loadResource(params.mCaptionWidthId); //Prevent caption from going offscreen if task is too high up final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2; @@ -289,8 +289,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Caption insets mCaptionInsetsRect.set(taskBounds); - mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight - captionYPos; - wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES); + mCaptionInsetsRect.bottom = + mCaptionInsetsRect.top + captionHeight - captionYPos; + wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, + CAPTION_INSETS_TYPES); } else { startT.hide(mCaptionContainerSurface); } @@ -307,6 +309,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setCrop(mTaskSurface, mTaskSurfaceCrop); } + private int loadResource(int resourceId) { + if (resourceId == Resources.ID_NULL) { + return 0; + } + return mDecorWindowContext.getResources().getDimensionPixelSize(resourceId); + } + /** * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or * registers {@link #mOnDisplaysChangedListener} if it doesn't. @@ -368,13 +377,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> static class RelayoutResult<T extends View & TaskFocusStateConsumer> { int mWidth; int mHeight; - float mDensity; T mRootView; void reset() { mWidth = 0; mHeight = 0; - mDensity = 0; mRootView = null; } } @@ -395,4 +402,37 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> return new SurfaceControlViewHost(c, d, wmm); } } + + static class RelayoutParams{ + RunningTaskInfo mRunningTaskInfo; + int mLayoutResId; + int mCaptionHeightId; + int mCaptionWidthId; + int mShadowRadiusId; + + int mOutsetTopId; + int mOutsetBottomId; + int mOutsetLeftId; + int mOutsetRightId; + + void setOutsets(int leftId, int topId, int rightId, int bottomId) { + mOutsetLeftId = leftId; + mOutsetTopId = topId; + mOutsetRightId = rightId; + mOutsetBottomId = bottomId; + } + + void reset() { + mLayoutResId = Resources.ID_NULL; + mCaptionHeightId = Resources.ID_NULL; + mCaptionWidthId = Resources.ID_NULL; + mShadowRadiusId = Resources.ID_NULL; + + mOutsetTopId = Resources.ID_NULL; + mOutsetBottomId = Resources.ID_NULL; + mOutsetLeftId = Resources.ID_NULL; + mOutsetRightId = Resources.ID_NULL; + } + + } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index 98b59126227c..79070b1469be 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -40,6 +40,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import java.util.ArrayList; + /** * Tests for {@link ActivityEmbeddingAnimationRunner}. * @@ -62,13 +64,13 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim final TransitionInfo.Change embeddingChange = createChange(); embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); info.addChange(embeddingChange); - doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); + doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any()); mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction); final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class); verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction), - finishCallback.capture()); + finishCallback.capture(), any()); verify(mStartTransaction).apply(); verify(mAnimator).start(); verifyNoMoreInteractions(mFinishTransaction); @@ -88,7 +90,8 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim info.addChange(embeddingChange); final Animator animator = mAnimRunner.createAnimator( info, mStartTransaction, mFinishTransaction, - () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); + () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */), + new ArrayList()); // The animation should be empty when it is behind starting window. assertEquals(0, animator.getDuration()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java index 3792e8361284..54a12ab999c5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java @@ -56,13 +56,12 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { @Mock SurfaceControl.Transaction mFinishTransaction; @Mock - Transitions.TransitionFinishCallback mFinishCallback; - @Mock Animator mAnimator; ActivityEmbeddingController mController; ActivityEmbeddingAnimationRunner mAnimRunner; ActivityEmbeddingAnimationSpec mAnimSpec; + Transitions.TransitionFinishCallback mFinishCallback; @CallSuper @Before @@ -75,9 +74,11 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { assertNotNull(mAnimRunner); mAnimSpec = mAnimRunner.mAnimationSpec; assertNotNull(mAnimSpec); + mFinishCallback = (wct, wctCB) -> {}; spyOn(mController); spyOn(mAnimRunner); spyOn(mAnimSpec); + spyOn(mFinishCallback); } /** Creates a mock {@link TransitionInfo.Change}. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index baecf6fe6673..4d98b6ba4f7a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -55,7 +55,7 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation @Before public void setup() { super.setUp(); - doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); + doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index dba037db72eb..3bd2ae76ebfd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip.phone; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -55,6 +56,7 @@ import org.mockito.MockitoAnnotations; @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) public class PipResizeGestureHandlerTest extends ShellTestCase { + private static final float DEFAULT_SNAP_FRACTION = 2.0f; private static final int STEP_SIZE = 40; private final MotionEvent.PointerProperties[] mPp = new MotionEvent.PointerProperties[2]; @@ -196,6 +198,51 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { < mPipBoundsState.getBounds().width()); } + @Test + public void testUserResizeTo() { + // resizing the bounds to normal bounds at first + mPipResizeGestureHandler.userResizeTo(mPipBoundsState.getNormalBounds(), + DEFAULT_SNAP_FRACTION); + + assertPipBoundsUserResizedTo(mPipBoundsState.getNormalBounds()); + + verify(mPipTaskOrganizer, times(1)) + .scheduleUserResizePip(any(), any(), any()); + + verify(mPipTaskOrganizer, times(1)) + .scheduleFinishResizePip(any(), any()); + + // bounds with max size + final Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x, + mPipBoundsState.getMaxSize().y); + + // resizing the bounds to maximum bounds the second time + mPipResizeGestureHandler.userResizeTo(maxBounds, DEFAULT_SNAP_FRACTION); + + assertPipBoundsUserResizedTo(maxBounds); + + // another call to scheduleUserResizePip() and scheduleFinishResizePip() makes + // the total number of invocations 2 for each method + verify(mPipTaskOrganizer, times(2)) + .scheduleUserResizePip(any(), any(), any()); + + verify(mPipTaskOrganizer, times(2)) + .scheduleFinishResizePip(any(), any()); + } + + private void assertPipBoundsUserResizedTo(Rect bounds) { + // check user-resized bounds + assertEquals(mPipResizeGestureHandler.getUserResizeBounds().width(), bounds.width()); + assertEquals(mPipResizeGestureHandler.getUserResizeBounds().height(), bounds.height()); + + // check if the bounds are the same + assertEquals(mPipBoundsState.getBounds().width(), bounds.width()); + assertEquals(mPipBoundsState.getBounds().height(), bounds.height()); + + // a flag should be set to indicate pip has been resized by the user + assertTrue(mPipBoundsState.hasUserResizedPip()); + } + private MotionEvent obtainMotionEvent(int action, int topLeft, int bottomRight) { final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[2]; for (int i = 0; i < 2; i++) { 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 fa62b9c00fc7..103c8dab17d5 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 @@ -51,6 +51,7 @@ import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; +import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; @@ -76,13 +77,9 @@ import java.util.function.Supplier; @SmallTest @RunWith(AndroidTestingRunner.class) public class WindowDecorationTests extends ShellTestCase { - private static final int CAPTION_HEIGHT_DP = 32; - private static final int CAPTION_WIDTH_DP = 216; - private static final int SHADOW_RADIUS_DP = 5; private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400); private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60); - private final Rect mOutsetsDp = new Rect(); private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult = new WindowDecoration.RelayoutResult<>(); @@ -104,6 +101,7 @@ public class WindowDecorationTests extends ShellTestCase { private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>(); private SurfaceControl.Transaction mMockSurfaceControlStartT; private SurfaceControl.Transaction mMockSurfaceControlFinishT; + private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams(); @Before public void setUp() { @@ -147,7 +145,8 @@ public class WindowDecorationTests extends ShellTestCase { // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is // 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mOutsetsDp.set(10, 20, 30, 40); + mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle, + R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -197,8 +196,13 @@ public class WindowDecorationTests extends ShellTestCase { // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is // 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mOutsetsDp.set(10, 20, 30, 40); - +// int outsetLeftId = R.dimen.split_divider_bar_width; +// int outsetTopId = R.dimen.gestures_onehanded_drag_threshold; +// int outsetRightId = R.dimen.freeform_resize_handle; +// int outsetBottomId = R.dimen.bubble_dismiss_target_padding_x; +// mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); + mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle, + R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -207,8 +211,8 @@ public class WindowDecorationTests extends ShellTestCase { verify(decorContainerSurfaceBuilder).setParent(taskSurface); verify(decorContainerSurfaceBuilder).setContainerLayer(); verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true); - verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40); - verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220); + verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -60, -60); + verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 420, 220); verify(taskBackgroundSurfaceBuilder).setParent(taskSurface); verify(taskBackgroundSurfaceBuilder).setEffectLayer(); @@ -221,34 +225,36 @@ public class WindowDecorationTests extends ShellTestCase { verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); - verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8); - verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); + verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -6, -156); + verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 432); verify(mMockSurfaceControlStartT).show(captionContainerSurface); verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); + verify(mMockSurfaceControlViewHost) .setView(same(mMockView), - argThat(lp -> lp.height == 64 - && lp.width == 300 + argThat(lp -> lp.height == 432 + && lp.width == 432 && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0)); if (ViewRootImpl.CAPTION_ON_SHELL) { verify(mMockView).setTaskFocusState(true); verify(mMockWindowContainerTransaction) .addRectInsetsProvider(taskInfo.token, - new Rect(100, 300, 400, 364), + new Rect(100, 300, 400, 516), new int[] { InsetsState.ITYPE_CAPTION_BAR }); } verify(mMockSurfaceControlFinishT) .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y); verify(mMockSurfaceControlFinishT) - .setCrop(taskSurface, new Rect(-20, -40, 360, 180)); + .setCrop(taskSurface, new Rect(-60, -60, 360, 160)); verify(mMockSurfaceControlStartT) .show(taskSurface); - assertEquals(380, mRelayoutResult.mWidth); + assertEquals(420, mRelayoutResult.mWidth); assertEquals(220, mRelayoutResult.mHeight); - assertEquals(2, mRelayoutResult.mDensity, 0.f); + + } @Test @@ -287,7 +293,8 @@ public class WindowDecorationTests extends ShellTestCase { // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is // 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mOutsetsDp.set(10, 20, 30, 40); + mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle, + R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -410,9 +417,15 @@ public class WindowDecorationTests extends ShellTestCase { @Override void relayout(ActivityManager.RunningTaskInfo taskInfo) { - relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP, - CAPTION_WIDTH_DP, mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT, - mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mRelayoutResult); + + mRelayoutParams.mLayoutResId = 0; + mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_width; + mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width; + mRelayoutParams.mShadowRadiusId = + R.dimen.freeform_decor_shadow_unfocused_thickness; + + relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, + mMockWindowContainerTransaction, mMockView, mRelayoutResult); } } } diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java index 2fe7b1668d08..262f5f198c22 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java @@ -100,16 +100,12 @@ public final class BluetoothMidiDevice { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + Log.d(TAG, "onConnectionStateChange() status: " + status + ", newState: " + newState); String intentAction; if (newState == BluetoothProfile.STATE_CONNECTED) { Log.d(TAG, "Connected to GATT server."); Log.d(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices()); - if (!mBluetoothGatt.requestMtu(MAX_PACKET_SIZE)) { - Log.e(TAG, "request mtu failed"); - mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE); - mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE); - } } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.i(TAG, "Disconnected from GATT server."); close(); @@ -118,6 +114,7 @@ public final class BluetoothMidiDevice { @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { + Log.d(TAG, "onServicesDiscovered() status: " + status); if (status == BluetoothGatt.GATT_SUCCESS) { BluetoothGattService service = gatt.getService(MIDI_SERVICE); if (service != null) { @@ -137,9 +134,14 @@ public final class BluetoothMidiDevice { // Specification says to read the characteristic first and then // switch to receiving notifications mBluetoothGatt.readCharacteristic(characteristic); - } - openBluetoothDevice(mBluetoothDevice); + // Request higher MTU size + if (!gatt.requestMtu(MAX_PACKET_SIZE)) { + Log.e(TAG, "request mtu failed"); + mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE); + mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE); + } + } } } else { Log.e(TAG, "onServicesDiscovered received: " + status); @@ -235,13 +237,13 @@ public final class BluetoothMidiDevice { System.arraycopy(buffer, 0, mCachedBuffer, 0, count); if (DEBUG) { - logByteArray("Sent ", mCharacteristic.getValue(), 0, - mCharacteristic.getValue().length); + logByteArray("Sent ", mCachedBuffer, 0, mCachedBuffer.length); } - if (mBluetoothGatt.writeCharacteristic(mCharacteristic, mCachedBuffer, - mCharacteristic.getWriteType()) != BluetoothGatt.GATT_SUCCESS) { - Log.w(TAG, "could not write characteristic to Bluetooth GATT"); + int result = mBluetoothGatt.writeCharacteristic(mCharacteristic, mCachedBuffer, + mCharacteristic.getWriteType()); + if (result != BluetoothGatt.GATT_SUCCESS) { + Log.w(TAG, "could not write characteristic to Bluetooth GATT. result: " + result); return false; } @@ -254,6 +256,10 @@ public final class BluetoothMidiDevice { mBluetoothDevice = device; mService = service; + // Set a small default packet size in case there is an issue with configuring MTUs. + mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE); + mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE); + mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback); mContext = context; diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml index db54ae3aeee9..d4439f9e7e64 100644 --- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml +++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml @@ -39,7 +39,6 @@ android:layout_height="wrap_content" android:paddingStart="24dp" android:paddingEnd="24dp" - android:singleLine="true" android:textAppearance="?android:attr/textAppearanceListItemSmall"/> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index 5c9ab7bf2feb..4e7e36797b7e 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -350,7 +350,7 @@ public class CompanionDeviceDiscoveryService extends Service { } return; } - if (DEBUG) Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device."); + Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device."); // First: make change. mDevicesFound.add(device); @@ -363,9 +363,9 @@ public class CompanionDeviceDiscoveryService extends Service { }); } - private void onDeviceLost(@Nullable DeviceFilterPair<?> device) { + private void onDeviceLost(@NonNull DeviceFilterPair<?> device) { runOnMainThread(() -> { - if (DEBUG) Log.i(TAG, "onDeviceLost(), device=" + device.toShortString()); + Log.i(TAG, "onDeviceLost(), device=" + device.toShortString()); // First: make change. mDevicesFound.remove(device); diff --git a/packages/PackageInstaller/res/values-as/strings.xml b/packages/PackageInstaller/res/values-as/strings.xml index dd776a986df6..84053353ebb3 100644 --- a/packages/PackageInstaller/res/values-as/strings.xml +++ b/packages/PackageInstaller/res/values-as/strings.xml @@ -28,11 +28,11 @@ <string name="install_confirm_question_update" msgid="3348888852318388584">"আপুনি এই এপ্টো আপডে’ট কৰিবলৈ বিচাৰেনে?"</string> <string name="install_failed" msgid="5777824004474125469">"এপ্ ইনষ্টল কৰা হোৱা নাই।"</string> <string name="install_failed_blocked" msgid="8512284352994752094">"পেকেজটোৰ ইনষ্টল অৱৰোধ কৰা হৈছে।"</string> - <string name="install_failed_conflict" msgid="3493184212162521426">"এপটো ইনষ্টল কৰিব পৰা নগ\'ল কাৰণ ইয়াৰ সৈতে আগৰে পৰা থকা এটা পেকেজৰ সংঘাত হৈছে।"</string> - <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"আপোনাৰ টেবলেটৰ সৈতে খাপ নোখোৱাৰ বাবে এপটো ইনষ্টল কৰা নহ\'ল।"</string> - <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"আপোনাৰ টিভিত এই এপটো নচলে"</string> - <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"আপোনাৰ ফ\'নৰ সৈতে খাপ নোখোৱাৰ বাবে এপটো ইনষ্টল কৰা নহ\'ল।"</string> - <string name="install_failed_invalid_apk" msgid="8581007676422623930">"পেকেজটো মান্য নোহোৱাৰ বাবে এপটো ইনষ্টল কৰা নহ\'ল।"</string> + <string name="install_failed_conflict" msgid="3493184212162521426">"এপ্টো ইনষ্টল কৰিব পৰা নগ\'ল কাৰণ ইয়াৰ সৈতে আগৰে পৰা থকা এটা পেকেজৰ সংঘাত হৈছে।"</string> + <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"আপোনাৰ টেবলেটৰ সৈতে খাপ নোখোৱাৰ বাবে এপ্টো ইনষ্টল কৰা নহ\'ল।"</string> + <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"আপোনাৰ টিভিত এই এপ্টো নচলে"</string> + <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"আপোনাৰ ফ\'নৰ সৈতে খাপ নোখোৱাৰ বাবে এপ্টো ইনষ্টল কৰা নহ\'ল।"</string> + <string name="install_failed_invalid_apk" msgid="8581007676422623930">"পেকেজটো মান্য নোহোৱাৰ বাবে এপ্টো ইনষ্টল কৰা নহ\'ল।"</string> <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"আপোনাৰ টে\'বলেটত <xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল৷"</string> <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"আপোনাৰ টিভিত <xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল।"</string> <string name="install_failed_msg" product="default" msgid="6484461562647915707">"আপোনাৰ ফ\'নত <xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল৷"</string> @@ -44,21 +44,21 @@ <string name="manage_applications" msgid="5400164782453975580">"এপ্ পৰিচালনা"</string> <string name="out_of_space_dlg_title" msgid="4156690013884649502">"খালী ঠাই নাই"</string> <string name="out_of_space_dlg_text" msgid="8727714096031856231">"<xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল। কিছু খালী ঠাই উলিয়াই আকৌ চেষ্টা কৰক৷"</string> - <string name="app_not_found_dlg_title" msgid="5107924008597470285">"এপটো পোৱা নগ\'ল"</string> - <string name="app_not_found_dlg_text" msgid="5219983779377811611">"ইনষ্টল কৰি ৰখা এপৰ তালিকাত এই এপটো পোৱা নগ\'ল।"</string> + <string name="app_not_found_dlg_title" msgid="5107924008597470285">"এপ্টো পোৱা নগ\'ল"</string> + <string name="app_not_found_dlg_text" msgid="5219983779377811611">"ইনষ্টল কৰি ৰখা এপৰ তালিকাত এই এপ্টো পোৱা নগ\'ল।"</string> <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"অনুমতি নাই"</string> <string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"বর্তমানৰ ব্যৱহাৰকাৰীজনক এইটো আনইনষ্টল কৰিবলৈ অনুমতি দিয়া হোৱা নাই।"</string> <string name="generic_error_dlg_title" msgid="5863195085927067752">"আসোঁৱাহ"</string> <string name="generic_error_dlg_text" msgid="5287861443265795232">"এপ্ আনইনষ্টল কৰিব পৰা নগ\'ল।"</string> <string name="uninstall_application_title" msgid="4045420072401428123">"এপ্ আনইনষ্টল কৰক"</string> <string name="uninstall_update_title" msgid="824411791011583031">"আপডে’ট আনইনষ্টল কৰক"</string> - <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> হৈছে তলৰ এপটোৰ এটা অংশ:"</string> + <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> হৈছে তলৰ এপ্টোৰ এটা অংশ:"</string> <string name="uninstall_application_text" msgid="3816830743706143980">"আপুনি এই এপ্টো আনইনষ্টল কৰিব বিচাৰে নেকি?"</string> - <string name="uninstall_application_text_all_users" msgid="575491774380227119">"আপুনি "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ বাবে এই এপটো আনইনষ্টল কৰিব বিচাৰেনে? এপ্লিকেশ্বন আৰু ইয়াৰ ডেটা ডিভাইচটোত থকা "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ পৰা আঁতৰোৱা হ\'ব৷"</string> - <string name="uninstall_application_text_user" msgid="498072714173920526">"আপুনি ব্যৱহাৰকাৰীৰ <xliff:g id="USERNAME">%1$s</xliff:g> বাবে এই এপটো আনইনষ্টল কৰিব বিচাৰেনে?"</string> + <string name="uninstall_application_text_all_users" msgid="575491774380227119">"আপুনি "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ বাবে এই এপ্টো আনইনষ্টল কৰিব বিচাৰেনে? এপ্লিকেশ্বন আৰু ইয়াৰ ডেটা ডিভাইচটোত থকা "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ পৰা আঁতৰোৱা হ\'ব৷"</string> + <string name="uninstall_application_text_user" msgid="498072714173920526">"আপুনি ব্যৱহাৰকাৰীৰ <xliff:g id="USERNAME">%1$s</xliff:g> বাবে এই এপ্টো আনইনষ্টল কৰিব বিচাৰেনে?"</string> <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"আপুনি নিজৰ কৰ্মস্থানৰ প্ৰ’ফাইলৰ পৰা এই এপ্টো আনইনষ্টল কৰিব বিচাৰেনে?"</string> <string name="uninstall_update_text" msgid="863648314632448705">"এই এপ্টোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? আটাইবোৰ ডেটা মচা হ\'ব।"</string> - <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"এই এপটোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? সকলো ডেটা মচা হ\'ব। কর্মস্থানৰ প্ৰফাইল থকা ব্যৱহাৰকাৰীৰ লগতে ডিভাইচটোৰ সকলো ব্যৱহাৰকাৰীৰ ওপৰত ইয়াৰ প্ৰভাৱ পৰিব।"</string> + <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"এই এপ্টোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? সকলো ডেটা মচা হ\'ব। কর্মস্থানৰ প্ৰফাইল থকা ব্যৱহাৰকাৰীৰ লগতে ডিভাইচটোৰ সকলো ব্যৱহাৰকাৰীৰ ওপৰত ইয়াৰ প্ৰভাৱ পৰিব।"</string> <string name="uninstall_keep_data" msgid="7002379587465487550">"এপৰ ডেটাৰ <xliff:g id="SIZE">%1$s</xliff:g> ৰাখক"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"আনইনষ্টল কৰি থকা হৈছে"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"যিবোৰ আনইনষ্টল পৰা নগ\'ল"</string> @@ -70,9 +70,9 @@ <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> আনইনষ্টল কৰিব পৰা নগ\'ল।"</string> <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"ডিভাইচৰ সক্ৰিয় প্ৰশাসক এপ্ আনইনষ্টল কৰিব নোৱাৰি"</string> <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g>ৰ সক্ৰিয় ডিভাইচৰ প্ৰশাসকীয় এপ্ আনইনষ্টল কৰিব নোৱাৰি"</string> - <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"এই এপটো কিছুসংখ্যক ব্যৱহাৰকাৰী বা প্ৰ\'ফাইলৰ বাবে প্ৰয়োজনীয় আৰু বাকীসকলৰ বাবে ইয়াক আনইনষ্টল কৰা হৈছে"</string> - <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"আপোনাৰ প্ৰ\'ফাইলৰ বাবে এই এপটোৰ প্ৰয়োজন আছে গতিকে আনইনষ্টল কৰিব পৰা নাযায়।"</string> - <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"এই এপটো আনইনষ্টল কৰিব পৰা নাযায় কাৰণ আপোনাৰ ডিভাইচৰ প্ৰশাসকে এই এপ্ ৰখাটো বাধ্যতামূলক কৰি ৰাখিছে।"</string> + <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"এই এপ্টো কিছুসংখ্যক ব্যৱহাৰকাৰী বা প্ৰ\'ফাইলৰ বাবে প্ৰয়োজনীয় আৰু বাকীসকলৰ বাবে ইয়াক আনইনষ্টল কৰা হৈছে"</string> + <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"আপোনাৰ প্ৰ\'ফাইলৰ বাবে এই এপ্টোৰ প্ৰয়োজন আছে গতিকে আনইনষ্টল কৰিব পৰা নাযায়।"</string> + <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"এই এপ্টো আনইনষ্টল কৰিব পৰা নাযায় কাৰণ আপোনাৰ ডিভাইচৰ প্ৰশাসকে এই এপ্ ৰখাটো বাধ্যতামূলক কৰি ৰাখিছে।"</string> <string name="manage_device_administrators" msgid="3092696419363842816">"ডিভাইচৰ প্ৰশাসক এপসমূহ পৰিচালনা কৰক"</string> <string name="manage_users" msgid="1243995386982560813">"ব্যৱহাৰকাৰী পৰিচালনা কৰক"</string> <string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> আনইনষ্টল কৰিব নোৱাৰি।"</string> @@ -84,9 +84,9 @@ <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ টেবলেটটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string> <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ টিভিটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string> <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ ফ’নটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string> - <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"আপোনাৰ ফ\'ন আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপটো ইনষ্টল কৰি এপটোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string> - <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"আপোনাৰ টেবলেট আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপটো ইনষ্টল কৰি এপটোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string> - <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"আপোনাৰ টিভি আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপটো ইনষ্টল কৰি এপটোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string> + <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"আপোনাৰ ফ\'ন আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্টো ইনষ্টল কৰি এপ্টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string> + <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"আপোনাৰ টেবলেট আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্টো ইনষ্টল কৰি এপ্টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string> + <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"আপোনাৰ টিভি আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্টো ইনষ্টল কৰি এপ্টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string> <string name="anonymous_source_continue" msgid="4375745439457209366">"অব্যাহত ৰাখক"</string> <string name="external_sources_settings" msgid="4046964413071713807">"ছেটিং"</string> <string name="wear_app_channel" msgid="1960809674709107850">"ৱেৰ এপসমূহ ইনষ্টল/আনইনষ্টল কৰি থকা হৈছে"</string> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 734eb33e3003..6bb759543a52 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -209,7 +209,7 @@ <string name="tts_engine_settings_button" msgid="477155276199968948">"የፍርግም ቅንብሮችን ያስጀምሩ"</string> <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"የተመረጠ ፍርግም"</string> <string name="tts_general_section_title" msgid="8919671529502364567">"አጠቃላይ"</string> - <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"የንግግር ድምጽ ውፍረት ዳግም አስጀምር"</string> + <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"የንግግር ድምፅ ውፍረት ዳግም አስጀምር"</string> <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"ጽሑፉ የሚነገርበትን የድምጽ ውፍረት ወደ ነባሪ ዳግም አስጀምር።"</string> <string-array name="tts_rate_entries"> <item msgid="9004239613505400644">"በጣም ቀርፋፋ"</item> @@ -407,7 +407,7 @@ <string name="force_resizable_activities" msgid="7143612144399959606">"እንቅስቃሴዎች ዳግመኛ እንዲመጣጠኑ አስገድድ"</string> <string name="force_resizable_activities_summary" msgid="2490382056981583062">"የዝርዝር ሰነድ እሴቶች ምንም ይሁኑ ምን ለበርካታ መስኮቶች ሁሉንም እንቅስቃሴዎች መጠናቸው የሚቀየሩ እንዲሆኑ ያደርጋቸዋል።"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"የነጻ ቅርጽ መስኮቶችን ያንቁ"</string> - <string name="enable_freeform_support_summary" msgid="1822862728719276331">"የሙከራ ነጻ መልክ መስኮቶች ድጋፍን አንቃ"</string> + <string name="enable_freeform_support_summary" msgid="1822862728719276331">"የሙከራ ነፃ መልክ መስኮቶች ድጋፍን አንቃ"</string> <string name="desktop_mode" msgid="2389067840550544462">"የዴስክቶፕ ሁነታ"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"የዴስክቶፕ መጠባበቂያ ይለፍ ቃል"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ዴስክቶፕ ሙሉ ምትኬዎች በአሁኑ ሰዓት አልተጠበቁም"</string> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index c4e9dd471c73..976949cb6b15 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -268,7 +268,7 @@ <string name="keep_screen_on" msgid="1187161672348797558">"জাগ্ৰত কৰি ৰাখক"</string> <string name="keep_screen_on_summary" msgid="1510731514101925829">"চ্চাৰ্জ হৈ থকাৰ সময়ত স্ক্ৰীন কেতিয়াও সুপ্ত অৱস্থালৈ নাযায়"</string> <string name="bt_hci_snoop_log" msgid="7291287955649081448">"ব্লুটুথ HCI স্নুপ ল’গ সক্ষম কৰক"</string> - <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"ব্লুটুথ পেকেট সংগ্ৰহ কৰক। (এই ছেটিংটো সলনি কৰাৰ পিছত ব্লুটুথ ট’গল কৰক)"</string> + <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"ব্লুটুথ পেকেট সংগ্ৰহ কৰক। (এই ছেটিংটো সলনি কৰাৰ পাছত ব্লুটুথ ট’গল কৰক)"</string> <string name="oem_unlock_enable" msgid="5334869171871566731">"ঔইএম আনলক"</string> <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"বুটল\'ডাৰটো আনলক কৰিবলৈ অনুমতি দিয়ক"</string> <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"ঔইএম আনলক কৰাৰ অনুমতি দিবনে?"</string> @@ -515,8 +515,8 @@ <string name="active_input_method_subtypes" msgid="4232680535471633046">"সক্ৰিয়হৈ থকা ইনপুট পদ্ধতিসমূহ"</string> <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"ছিষ্টেমৰ ভাষা ব্যৱহাৰ কৰক"</string> <string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>ৰ ছেটিং খুলিব পৰা নগ\'ল"</string> - <string name="ime_security_warning" msgid="6547562217880551450">"এই ইনপুট পদ্ধতিটোৱে আপুনি টাইপ কৰা আপোনাৰ ব্যক্তিগত ডেটা যেনে পাছৱৰ্ডসমূহ আৰু ক্ৰেডিট কাৰ্ডৰ নম্বৰসমূহকে ধৰি আটাইবোৰ পাঠ সংগ্ৰহ কৰিবলৈ সক্ষম হ\'ব পাৰে। <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> এপটোৰ লগত ই সংলগ্ন। এই ইনপুট পদ্ধতিটো ব্যৱহাৰ কৰেনে?"</string> - <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"টোকা: ৰিবুট কৰাৰ পিছত আপুনি ফ\'নটো আনলক নকৰালৈকে এই এপটো ষ্টাৰ্ট নহ’ব"</string> + <string name="ime_security_warning" msgid="6547562217880551450">"এই ইনপুট পদ্ধতিটোৱে আপুনি টাইপ কৰা আপোনাৰ ব্যক্তিগত ডেটা যেনে পাছৱৰ্ডসমূহ আৰু ক্ৰেডিট কাৰ্ডৰ নম্বৰসমূহকে ধৰি আটাইবোৰ পাঠ সংগ্ৰহ কৰিবলৈ সক্ষম হ\'ব পাৰে। <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> এপ্টোৰ লগত ই সংলগ্ন। এই ইনপুট পদ্ধতিটো ব্যৱহাৰ কৰেনে?"</string> + <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"টোকা: ৰিবুট কৰাৰ পাছত আপুনি ফ\'নটো আনলক নকৰালৈকে এই এপ্টো ষ্টাৰ্ট নহ’ব"</string> <string name="ims_reg_title" msgid="8197592958123671062">"আইএমএছ পঞ্জীয়ন স্থিতি"</string> <string name="ims_reg_status_registered" msgid="884916398194885457">"পঞ্জীকৃত"</string> <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"পঞ্জীকৃত নহয়"</string> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 98cc18e8ad13..b6eb82ef5578 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -247,7 +247,7 @@ <string name="adb_paired_devices_title" msgid="5268997341526217362">"Appareils associés"</string> <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Actuellement connecté"</string> <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Infos sur l\'appareil"</string> - <string name="adb_device_forget" msgid="193072400783068417">"Supprimer"</string> + <string name="adb_device_forget" msgid="193072400783068417">"Retirer"</string> <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Empreinte de l\'appareil : <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string> <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Échec de la connexion"</string> <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Vérifiez que l\'appareil <xliff:g id="DEVICE_NAME">%1$s</xliff:g> est connecté au bon réseau"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 5d5ebe7a39d1..c17a1d51042e 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -509,7 +509,7 @@ <string name="screen_zoom_summary_extremely_large" msgid="1438045624562358554">"అతి పెద్దగా"</string> <string name="screen_zoom_summary_custom" msgid="3468154096832912210">"అనుకూలం (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="content_description_menu_button" msgid="6254844309171779931">"మెనూ"</string> - <string name="retail_demo_reset_message" msgid="5392824901108195463">"డెమో మోడ్లో ఫ్యాక్టరీ రీసెట్ను నిర్వహించడానికి పాస్వర్డ్ను నమోదు చేయండి"</string> + <string name="retail_demo_reset_message" msgid="5392824901108195463">"డెమో మోడ్లో ఫ్యాక్టరీ రీసెట్ను మేనేజ్ చేయడానికి పాస్వర్డ్ను నమోదు చేయండి"</string> <string name="retail_demo_reset_next" msgid="3688129033843885362">"తర్వాత"</string> <string name="retail_demo_reset_title" msgid="1866911701095959800">"పాస్వర్డ్ అవసరం"</string> <string name="active_input_method_subtypes" msgid="4232680535471633046">"సక్రియ ఇన్పుట్ పద్ధతులు"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 5662ce6bd808..6bc1160a8d0a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -356,7 +356,7 @@ public class CachedBluetoothDeviceManager { * @return {@code true}, if the device should pair automatically; Otherwise, return * {@code false}. */ - public synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) { + private synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) { boolean isOngoingSetMemberPair = mOngoingSetMemberPair != null; int bondState = device.getBondState(); if (isOngoingSetMemberPair || bondState != BluetoothDevice.BOND_NONE @@ -365,10 +365,44 @@ public class CachedBluetoothDeviceManager { + " , device.getBondState: " + bondState); return false; } + return true; + } - Log.d(TAG, "Bond " + device.getName() + " by CSIP"); + /** + * Called when we found a set member of a group. The function will check the {@code groupId} if + * it exists and the bond state of the device is BOND_NONE, and if there isn't any ongoing pair + * , and then pair the device automatically. + * + * @param device The found device + * @param groupId The group id of the found device + */ + public synchronized void pairDeviceByCsip(BluetoothDevice device, int groupId) { + if (!shouldPairByCsip(device, groupId)) { + return; + } + Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " by CSIP"); mOngoingSetMemberPair = device; - return true; + syncConfigFromMainDevice(device, groupId); + device.createBond(BluetoothDevice.TRANSPORT_LE); + } + + private void syncConfigFromMainDevice(BluetoothDevice device, int groupId) { + if (!isOngoingPairByCsip(device)) { + return; + } + CachedBluetoothDevice memberDevice = findDevice(device); + CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(memberDevice); + if (mainDevice == null) { + mainDevice = mCsipDeviceManager.getCachedDevice(groupId); + } + + if (mainDevice == null || mainDevice.equals(memberDevice)) { + Log.d(TAG, "no mainDevice"); + return; + } + + // The memberDevice set PhonebookAccessPermission + device.setPhonebookAccessPermission(mainDevice.getDevice().getPhonebookAccessPermission()); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index d5de3f0525a0..20a6cd8e09ce 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -101,7 +101,14 @@ public class CsipDeviceManager { return groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } - private CachedBluetoothDevice getCachedDevice(int groupId) { + /** + * To find the device with {@code groupId}. + * + * @param groupId The group id + * @return if we could find a device with this {@code groupId} return this device. Otherwise, + * return null. + */ + public CachedBluetoothDevice getCachedDevice(int groupId) { log("getCachedDevice: groupId: " + groupId); for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index 62552f914459..61802a87361c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -582,4 +582,24 @@ public class CachedBluetoothDeviceManagerTest { assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue(); assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse(); } + + @Test + public void pairDeviceByCsip_device2AndCapGroup1_device2StartsPairing() { + doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1); + when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mDevice1.getPhonebookAccessPermission()).thenReturn(BluetoothDevice.ACCESS_ALLOWED); + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); + assertThat(cachedDevice1).isNotNull(); + when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); + assertThat(cachedDevice2).isNotNull(); + + int groupId = CAP_GROUP1.keySet().stream().findFirst().orElse( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + assertThat(groupId).isNotEqualTo(BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + mCachedDeviceManager.pairDeviceByCsip(mDevice2, groupId); + + verify(mDevice2).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); + verify(mDevice2).createBond(BluetoothDevice.TRANSPORT_LE); + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index fd7554f11873..528af2ec2528 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -376,9 +376,11 @@ final class SettingsState { Setting newSetting = new Setting(name, oldSetting.getValue(), null, oldSetting.getPackageName(), oldSetting.getTag(), false, oldSetting.getId()); - mSettings.put(name, newSetting); - updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue, + int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue, newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue()); + checkNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize); + mSettings.put(name, newSetting); + updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize); scheduleWriteIfNeededLocked(); } } @@ -410,6 +412,12 @@ final class SettingsState { Setting oldState = mSettings.get(name); String oldValue = (oldState != null) ? oldState.value : null; String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null; + String newDefaultValue = makeDefault ? value : oldDefaultValue; + + int newSize = getNewMemoryUsagePerPackageLocked(packageName, oldValue, value, + oldDefaultValue, newDefaultValue); + checkNewMemoryUsagePerPackageLocked(packageName, newSize); + Setting newState; if (oldState != null) { @@ -430,8 +438,7 @@ final class SettingsState { addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState); - updateMemoryUsagePerPackageLocked(packageName, oldValue, value, - oldDefaultValue, newState.getDefaultValue()); + updateMemoryUsagePerPackageLocked(packageName, newSize); scheduleWriteIfNeededLocked(); @@ -552,13 +559,14 @@ final class SettingsState { } Setting oldState = mSettings.remove(name); + int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, + null, oldState.defaultValue, null); FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "", /* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED); - updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, - null, oldState.defaultValue, null); + updateMemoryUsagePerPackageLocked(oldState.packageName, newSize); addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState); @@ -579,16 +587,18 @@ final class SettingsState { Setting oldSetting = new Setting(setting); String oldValue = setting.getValue(); String oldDefaultValue = setting.getDefaultValue(); + String newValue = oldDefaultValue; + String newDefaultValue = oldDefaultValue; + + int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, oldValue, + newValue, oldDefaultValue, newDefaultValue); + checkNewMemoryUsagePerPackageLocked(setting.packageName, newSize); if (!setting.reset()) { return false; } - String newValue = setting.getValue(); - String newDefaultValue = setting.getDefaultValue(); - - updateMemoryUsagePerPackageLocked(setting.packageName, oldValue, - newValue, oldDefaultValue, newDefaultValue); + updateMemoryUsagePerPackageLocked(setting.packageName, newSize); addHistoricalOperationLocked(HISTORICAL_OPERATION_RESET, oldSetting); @@ -696,38 +706,49 @@ final class SettingsState { } @GuardedBy("mLock") - private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue, - String newValue, String oldDefaultValue, String newDefaultValue) { - if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) { - return; - } + private boolean isExemptFromMemoryUsageCap(String packageName) { + return mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED + || SYSTEM_PACKAGE_NAME.equals(packageName); + } - if (SYSTEM_PACKAGE_NAME.equals(packageName)) { + @GuardedBy("mLock") + private void checkNewMemoryUsagePerPackageLocked(String packageName, int newSize) + throws IllegalStateException { + if (isExemptFromMemoryUsageCap(packageName)) { return; } + if (newSize > mMaxBytesPerAppPackage) { + throw new IllegalStateException("You are adding too many system settings. " + + "You should stop using system settings for app specific data" + + " package: " + packageName); + } + } + @GuardedBy("mLock") + private int getNewMemoryUsagePerPackageLocked(String packageName, String oldValue, + String newValue, String oldDefaultValue, String newDefaultValue) { + if (isExemptFromMemoryUsageCap(packageName)) { + return 0; + } + final Integer currentSize = mPackageToMemoryUsage.get(packageName); final int oldValueSize = (oldValue != null) ? oldValue.length() : 0; final int newValueSize = (newValue != null) ? newValue.length() : 0; final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0; final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0; final int deltaSize = newValueSize + newDefaultValueSize - oldValueSize - oldDefaultValueSize; + return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0); + } - Integer currentSize = mPackageToMemoryUsage.get(packageName); - final int newSize = Math.max((currentSize != null) - ? currentSize + deltaSize : deltaSize, 0); - - if (newSize > mMaxBytesPerAppPackage) { - throw new IllegalStateException("You are adding too many system settings. " - + "You should stop using system settings for app specific data" - + " package: " + packageName); + @GuardedBy("mLock") + private void updateMemoryUsagePerPackageLocked(String packageName, int newSize) { + if (isExemptFromMemoryUsageCap(packageName)) { + return; } - if (DEBUG) { Slog.i(LOG_TAG, "Settings for package: " + packageName + " size: " + newSize + " bytes."); } - mPackageToMemoryUsage.put(packageName, newSize); } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 69eb7133f46f..66b809aeae30 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -20,6 +20,8 @@ import android.test.AndroidTestCase; import android.util.TypedXmlSerializer; import android.util.Xml; +import com.google.common.base.Strings; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -276,4 +278,40 @@ public class SettingsStateTest extends AndroidTestCase { settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING); return settingsState; } + + public void testInsertSetting_memoryUsage() { + SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + // No exception should be thrown when there is no cap + settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001), + null, false, "p1"); + settingsState.deleteSettingLocked(SETTING_NAME); + + settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper()); + // System package doesn't have memory usage limit + settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001), + null, false, SYSTEM_PACKAGE); + settingsState.deleteSettingLocked(SETTING_NAME); + + // Should not throw if usage is under the cap + settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19999), + null, false, "p1"); + settingsState.deleteSettingLocked(SETTING_NAME); + try { + settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001), + null, false, "p1"); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("p1")); + } + try { + settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001), + null, false, "p1"); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("p1")); + } + assertTrue(settingsState.getSettingLocked(SETTING_NAME).isNull()); + } } diff --git a/packages/SoundPicker/res/values-am/strings.xml b/packages/SoundPicker/res/values-am/strings.xml index 07aee8a646ec..85206c0b46a0 100644 --- a/packages/SoundPicker/res/values-am/strings.xml +++ b/packages/SoundPicker/res/values-am/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="ringtone_default" msgid="798836092118824500">"ነባሪ የስልክ ላይ ጥሪ"</string> - <string name="notification_sound_default" msgid="8133121186242636840">"ነባሪ የማሳወቂያ ድምጽ"</string> + <string name="notification_sound_default" msgid="8133121186242636840">"ነባሪ የማሳወቂያ ድምፅ"</string> <string name="alarm_sound_default" msgid="4787646764557462649">"ነባሪ የማንቂያ ድምፅ"</string> <string name="add_ringtone_text" msgid="6642389991738337529">"የጥሪ ቅላጼ አክል"</string> <string name="add_alarm_text" msgid="3545497316166999225">"የማንቂያ ደውል አክል"</string> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 2737ecf5ffa6..b5145f926abd 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -402,6 +402,9 @@ android:permission="com.android.systemui.permission.SELF" android:exported="false" /> + <service android:name=".screenshot.ScreenshotCrossProfileService" + android:permission="com.android.systemui.permission.SELF" + android:exported="false" /> <service android:name=".screenrecord.RecordingService" /> diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index a65f9be3e83f..6d61fd86e39d 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -5,23 +5,32 @@ set noparent dsandler@android.com aaliomer@google.com +aaronjli@google.com +acul@google.com adamcohen@google.com +aioana@google.com alexflo@google.com +andonian@google.com +aroederer@google.com arteiro@google.com asc@google.com awickham@google.com +ayepin@google.com +bbade@google.com beverlyt@google.com -brockman@google.com -brzezinski@google.com +bhinegardner@google.com +bhnm@google.com brycelee@google.com +brzezinski@google.com caitlinshk@google.com +chandruis@google.com chrisgollner@google.com cinek@google.com -cwren@google.com dupin@google.com ethibodeau@google.com evanlaird@google.com florenceyang@google.com +gallmann@google.com gwasserman@google.com hwwang@google.com hyunyoungs@google.com @@ -37,35 +46,42 @@ jonmiranda@google.com joshtrask@google.com juliacr@google.com juliatuttle@google.com -kchyn@google.com +justinkoh@google.com +justinweir@google.com kozynski@google.com kprevas@google.com +lusilva@google.com lynhan@google.com madym@google.com mankoff@google.com -mett@google.com +mateuszc@google.com +michaelmikhil@google.com +michschn@google.com mkephart@google.com mpietal@google.com mrcasey@google.com mrenouf@google.com -nesciosquid@google.com nickchameyev@google.com nicomazz@google.com +nijamkin@google.com ogunwale@google.com +omarmt@google.com +patmanning@google.com peanutbutter@google.com peskal@google.com pinyaoting@google.com pixel@google.com pomini@google.com rahulbanerjee@google.com +rasheedlewis@google.com roosa@google.com +saff@google.com santie@google.com shanh@google.com snoeberger@google.com -sreyasr@google.com steell@google.com -sfufa@google.com stwu@google.com +syeonlee@google.com sunnygoyal@google.com susikp@google.com thiruram@google.com @@ -75,13 +91,14 @@ twickham@google.com vadimt@google.com victortulias@google.com winsonc@google.com +wleshner@google.com +xilei@google.com xuqiu@google.com +yeinj@google.com yuandizhou@google.com yurilin@google.com zakcohen@google.com - -#Android Auto -hseog@google.com +zoepage@google.com #Android TV rgl@google.com 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 23cee4d0972d..ca36fa43da76 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -865,7 +865,7 @@ private class AnimatedDialog( return } - ViewRootSync.synchronizeNextDraw(decorView, controller.viewRoot.view, then) + ViewRootSync.synchronizeNextDraw(controller.viewRoot.view, decorView, then) decorView.invalidate() controller.viewRoot.view.invalidate() } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt index 1b7e26b0aea0..58ffef25cb42 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt @@ -360,7 +360,9 @@ class ViewHierarchyAnimator { * [interpolator] and [duration]. * * The end state of the animation is controlled by [destination]. This value can be any of - * the four corners, any of the four edges, or the center of the view. + * the four corners, any of the four edges, or the center of the view. If any margins are + * added on the side(s) of the [destination], the translation of those margins can be + * included by specifying [includeMargins]. * * @param onAnimationEnd an optional runnable that will be run once the animation finishes * successfully. Will not be run if the animation is cancelled. @@ -371,6 +373,7 @@ class ViewHierarchyAnimator { destination: Hotspot = Hotspot.CENTER, interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR, duration: Long = DEFAULT_DURATION, + includeMargins: Boolean = false, onAnimationEnd: Runnable? = null, ): Boolean { if ( @@ -428,10 +431,12 @@ class ViewHierarchyAnimator { val endValues = processEndValuesForRemoval( destination, + rootView, rootView.left, rootView.top, rootView.right, - rootView.bottom + rootView.bottom, + includeMargins, ) val boundsToAnimate = mutableSetOf<Bound>() @@ -718,70 +723,111 @@ class ViewHierarchyAnimator { * | | -> | | -> | | -> x---x -> x * | | x-------x x-----x * x---------x + * 4) destination=TOP, includeMargins=true (and view has large top margin) + * x---------x + * x---------x + * x---------x x---------x + * x---------x | | + * x---------x | | x---------x + * | | | | + * | | -> x---------x -> -> -> + * | | + * x---------x * ``` */ private fun processEndValuesForRemoval( destination: Hotspot, + rootView: View, left: Int, top: Int, right: Int, - bottom: Int + bottom: Int, + includeMargins: Boolean = false, ): Map<Bound, Int> { - val endLeft = - when (destination) { - Hotspot.CENTER -> (left + right) / 2 - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT, - Hotspot.TOP_LEFT, - Hotspot.TOP -> left - Hotspot.TOP_RIGHT, - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT -> right - } - val endTop = - when (destination) { - Hotspot.CENTER -> (top + bottom) / 2 - Hotspot.LEFT, - Hotspot.TOP_LEFT, - Hotspot.TOP, - Hotspot.TOP_RIGHT, - Hotspot.RIGHT -> top - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT -> bottom - } - val endRight = - when (destination) { - Hotspot.CENTER -> (left + right) / 2 - Hotspot.TOP, - Hotspot.TOP_RIGHT, - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM -> right - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT, - Hotspot.TOP_LEFT -> left - } - val endBottom = - when (destination) { - Hotspot.CENTER -> (top + bottom) / 2 - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT -> bottom - Hotspot.TOP_LEFT, - Hotspot.TOP, - Hotspot.TOP_RIGHT -> top - } + val marginAdjustment = + if (includeMargins && + (rootView.layoutParams is ViewGroup.MarginLayoutParams)) { + val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams + DimenHolder( + left = marginLp.leftMargin, + top = marginLp.topMargin, + right = marginLp.rightMargin, + bottom = marginLp.bottomMargin + ) + } else { + DimenHolder(0, 0, 0, 0) + } - return mapOf( - Bound.LEFT to endLeft, - Bound.TOP to endTop, - Bound.RIGHT to endRight, - Bound.BOTTOM to endBottom - ) + // These are the end values to use *if* this bound is part of the destination. + val endLeft = left - marginAdjustment.left + val endTop = top - marginAdjustment.top + val endRight = right + marginAdjustment.right + val endBottom = bottom + marginAdjustment.bottom + + // For the below calculations: We need to ensure that the destination bound and the + // bound *opposite* to the destination bound end at the same value, to ensure that the + // view has size 0 for that dimension. + // For example, + // - If destination=TOP, then endTop == endBottom. Left and right stay the same. + // - If destination=RIGHT, then endRight == endLeft. Top and bottom stay the same. + // - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight. + + return when (destination) { + Hotspot.TOP -> mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.LEFT to left, + Bound.RIGHT to right, + ) + Hotspot.TOP_RIGHT -> mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + ) + Hotspot.RIGHT -> mapOf( + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + Bound.TOP to top, + Bound.BOTTOM to bottom, + ) + Hotspot.BOTTOM_RIGHT -> mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + ) + Hotspot.BOTTOM -> mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.LEFT to left, + Bound.RIGHT to right, + ) + Hotspot.BOTTOM_LEFT -> mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + ) + Hotspot.LEFT -> mapOf( + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + Bound.TOP to top, + Bound.BOTTOM to bottom, + ) + Hotspot.TOP_LEFT -> mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + ) + Hotspot.CENTER -> mapOf( + Bound.LEFT to (endLeft + endRight) / 2, + Bound.RIGHT to (endLeft + endRight) / 2, + Bound.TOP to (endTop + endBottom) / 2, + Bound.BOTTOM to (endTop + endBottom) / 2, + ) + } } /** @@ -1061,4 +1107,12 @@ class ViewHierarchyAnimator { abstract fun setValue(view: View, value: Int) abstract fun getValue(view: View): Int } + + /** Simple data class to hold a set of dimens for left, top, right, bottom. */ + private data class DimenHolder( + val left: Int, + val top: Int, + val right: Int, + val bottom: Int, + ) } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt new file mode 100644 index 000000000000..1db072548a76 --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt @@ -0,0 +1,100 @@ +/* + * 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.internal.systemui.lint + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiMethod +import org.jetbrains.uast.UCallExpression + +private const val CLASS_SETTINGS = "android.provider.Settings" + +/** + * Detects usage of static methods in android.provider.Settings and suggests to use an injected + * settings provider instance instead. + */ +@Suppress("UnstableApiUsage") +class StaticSettingsProviderDetector : Detector(), SourceCodeScanner { + override fun getApplicableMethodNames(): List<String> { + return listOf( + "getFloat", + "getInt", + "getLong", + "getString", + "getUriFor", + "putFloat", + "putInt", + "putLong", + "putString" + ) + } + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + val evaluator = context.evaluator + val className = method.containingClass?.qualifiedName + if ( + className != "$CLASS_SETTINGS.Global" && + className != "$CLASS_SETTINGS.Secure" && + className != "$CLASS_SETTINGS.System" + ) { + return + } + if (!evaluator.isStatic(method)) { + return + } + + val subclassName = className.substring(CLASS_SETTINGS.length + 1) + + context.report( + ISSUE, + method, + context.getNameLocation(node), + "`@Inject` a ${subclassName}Settings instead" + ) + } + + companion object { + @JvmField + val ISSUE: Issue = + Issue.create( + id = "StaticSettingsProvider", + briefDescription = "Static settings provider usage", + explanation = + """ + Static settings provider methods, such as `Settings.Global.putInt()`, should \ + not be used because they make testing difficult. Instead, use an injected \ + settings provider. For example, instead of calling `Settings.Secure.getInt()`, \ + annotate the class constructor with `@Inject` and add `SecureSettings` to the \ + parameters. + """, + category = Category.CORRECTNESS, + priority = 8, + severity = Severity.WARNING, + implementation = + Implementation( + StaticSettingsProviderDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + } +} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt index cf7c1b5e44a2..3f334c1cdb9c 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt @@ -36,6 +36,7 @@ class SystemUIIssueRegistry : IssueRegistry() { RegisterReceiverViaContextDetector.ISSUE, SoftwareBitmapDetector.ISSUE, NonInjectedServiceDetector.ISSUE, + StaticSettingsProviderDetector.ISSUE ) override val api: Int diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt index 486af9dd5d98..d4c55c0d9149 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt @@ -24,6 +24,47 @@ import org.intellij.lang.annotations.Language @NonNull private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented() +internal val commonSettingsCode = + """ +public static float getFloat(ContentResolver cr, String name) { return 0.0f; } +public static long getLong(ContentResolver cr, String name) { + return 0L; +} +public static int getInt(ContentResolver cr, String name) { + return 0; +} +public static String getString(ContentResolver cr, String name) { + return ""; +} +public static float getFloat(ContentResolver cr, String name, float def) { + return 0.0f; +} +public static long getLong(ContentResolver cr, String name, long def) { + return 0L; +} +public static int getInt(ContentResolver cr, String name, int def) { + return 0; +} +public static String getString(ContentResolver cr, String name, String def) { + return ""; +} +public static boolean putFloat(ContentResolver cr, String name, float value) { + return true; +} +public static boolean putLong(ContentResolver cr, String name, long value) { + return true; +} +public static boolean putInt(ContentResolver cr, String name, int value) { + return true; +} +public static boolean putFloat(ContentResolver cr, String name) { + return true; +} +public static boolean putString(ContentResolver cr, String name, String value) { + return true; +} +""" + /* * This file contains stubs of framework APIs and System UI classes for testing purposes only. The * stubs are not used in the lint detectors themselves. @@ -186,4 +227,28 @@ public @interface WorkerThread { } """ ), + indentedJava( + """ +package android.provider; + +public class Settings { + public static final class Global { + public static final String UNLOCK_SOUND = "unlock_sound"; + """ + + commonSettingsCode + + """ + } + public static final class Secure { + """ + + commonSettingsCode + + """ + } + public static final class System { + """ + + commonSettingsCode + + """ + } +} +""" + ), ) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt index 6ae8fd3f25a1..c35ac61a6543 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt @@ -16,18 +16,15 @@ package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue import org.junit.Test @Suppress("UnstableApiUsage") -class BindServiceOnMainThreadDetectorTest : LintDetectorTest() { +class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() { override fun getDetector(): Detector = BindServiceOnMainThreadDetector() - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt index 7d422807ae08..376acb56fac9 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt @@ -16,18 +16,15 @@ package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue import org.junit.Test @Suppress("UnstableApiUsage") -class BroadcastSentViaContextDetectorTest : LintDetectorTest() { +class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() { override fun getDetector(): Detector = BroadcastSentViaContextDetector() - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) override fun getIssues(): List<Issue> = listOf(BroadcastSentViaContextDetector.ISSUE) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt index c468af8d09e0..301c338f9b42 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt @@ -16,18 +16,15 @@ package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue import org.junit.Test @Suppress("UnstableApiUsage") -class NonInjectedMainThreadDetectorTest : LintDetectorTest() { +class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() { override fun getDetector(): Detector = NonInjectedMainThreadDetector() - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) override fun getIssues(): List<Issue> = listOf(NonInjectedMainThreadDetector.ISSUE) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt index c83a35b46ca6..0a74bfcfee57 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt @@ -16,18 +16,15 @@ package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue import org.junit.Test @Suppress("UnstableApiUsage") -class NonInjectedServiceDetectorTest : LintDetectorTest() { +class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() { override fun getDetector(): Detector = NonInjectedServiceDetector() - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) override fun getIssues(): List<Issue> = listOf(NonInjectedServiceDetector.ISSUE) @Test diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt index ebcddebfbc28..9ed7aa029b1d 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt @@ -16,18 +16,15 @@ package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue import org.junit.Test @Suppress("UnstableApiUsage") -class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { +class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() { override fun getDetector(): Detector = RegisterReceiverViaContextDetector() - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt index b03a11c4f02f..54cac7b35598 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt @@ -16,18 +16,15 @@ package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue import org.junit.Test @Suppress("UnstableApiUsage") -class SlowUserQueryDetectorTest : LintDetectorTest() { +class SlowUserQueryDetectorTest : SystemUILintDetectorTest() { override fun getDetector(): Detector = SlowUserQueryDetector() - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) override fun getIssues(): List<Issue> = listOf( diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt index fb6537e92d15..090ddf88fa3c 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt @@ -16,18 +16,15 @@ package com.android.internal.systemui.lint -import com.android.tools.lint.checks.infrastructure.LintDetectorTest import com.android.tools.lint.checks.infrastructure.TestFiles -import com.android.tools.lint.checks.infrastructure.TestLintTask import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue import org.junit.Test @Suppress("UnstableApiUsage") -class SoftwareBitmapDetectorTest : LintDetectorTest() { +class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() { override fun getDetector(): Detector = SoftwareBitmapDetector() - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) override fun getIssues(): List<Issue> = listOf(SoftwareBitmapDetector.ISSUE) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt new file mode 100644 index 000000000000..b83ed7067bc3 --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt @@ -0,0 +1,208 @@ +/* + * 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.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +@Suppress("UnstableApiUsage") +class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() { + + override fun getDetector(): Detector = StaticSettingsProviderDetector() + override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE) + + @Test + fun testGetServiceWithString() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + + import android.provider.Settings; + import android.provider.Settings.Global; + import android.provider.Settings.Secure; + + public class TestClass { + public void getSystemServiceWithoutDagger(Context context) { + final ContentResolver cr = mContext.getContentResolver(); + Global.getFloat(cr, Settings.Global.UNLOCK_SOUND); + Global.getInt(cr, Settings.Global.UNLOCK_SOUND); + Global.getLong(cr, Settings.Global.UNLOCK_SOUND); + Global.getString(cr, Settings.Global.UNLOCK_SOUND); + Global.getFloat(cr, Settings.Global.UNLOCK_SOUND, 1f); + Global.getInt(cr, Settings.Global.UNLOCK_SOUND, 1); + Global.getLong(cr, Settings.Global.UNLOCK_SOUND, 1L); + Global.getString(cr, Settings.Global.UNLOCK_SOUND, "1"); + Global.putFloat(cr, Settings.Global.UNLOCK_SOUND, 1f); + Global.putInt(cr, Settings.Global.UNLOCK_SOUND, 1); + Global.putLong(cr, Settings.Global.UNLOCK_SOUND, 1L); + Global.putString(cr, Settings.Global.UNLOCK_SOUND, "1"); + + Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); + Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); + Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); + Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); + Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f); + Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1); + Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L); + Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1"); + Secure.putFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f); + Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1); + Secure.putLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L); + Secure.putString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1"); + + Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT); + Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT); + Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT); + Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT); + Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f); + Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1); + Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L); + Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT, "1"); + Settings.System.putFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f); + Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1); + Settings.System.putLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L); + Settings.System.putString(cr, Settings.Global.UNLOCK_SOUND, "1"); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(StaticSettingsProviderDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/TestClass.java:10: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.getFloat(cr, Settings.Global.UNLOCK_SOUND); + ~~~~~~~~ + src/test/pkg/TestClass.java:11: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.getInt(cr, Settings.Global.UNLOCK_SOUND); + ~~~~~~ + src/test/pkg/TestClass.java:12: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.getLong(cr, Settings.Global.UNLOCK_SOUND); + ~~~~~~~ + src/test/pkg/TestClass.java:13: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.getString(cr, Settings.Global.UNLOCK_SOUND); + ~~~~~~~~~ + src/test/pkg/TestClass.java:14: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.getFloat(cr, Settings.Global.UNLOCK_SOUND, 1f); + ~~~~~~~~ + src/test/pkg/TestClass.java:15: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.getInt(cr, Settings.Global.UNLOCK_SOUND, 1); + ~~~~~~ + src/test/pkg/TestClass.java:16: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.getLong(cr, Settings.Global.UNLOCK_SOUND, 1L); + ~~~~~~~ + src/test/pkg/TestClass.java:17: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.getString(cr, Settings.Global.UNLOCK_SOUND, "1"); + ~~~~~~~~~ + src/test/pkg/TestClass.java:18: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.putFloat(cr, Settings.Global.UNLOCK_SOUND, 1f); + ~~~~~~~~ + src/test/pkg/TestClass.java:19: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.putInt(cr, Settings.Global.UNLOCK_SOUND, 1); + ~~~~~~ + src/test/pkg/TestClass.java:20: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.putLong(cr, Settings.Global.UNLOCK_SOUND, 1L); + ~~~~~~~ + src/test/pkg/TestClass.java:21: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider] + Global.putString(cr, Settings.Global.UNLOCK_SOUND, "1"); + ~~~~~~~~~ + src/test/pkg/TestClass.java:23: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); + ~~~~~~~~ + src/test/pkg/TestClass.java:24: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); + ~~~~~~ + src/test/pkg/TestClass.java:25: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); + ~~~~~~~ + src/test/pkg/TestClass.java:26: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED); + ~~~~~~~~~ + src/test/pkg/TestClass.java:27: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f); + ~~~~~~~~ + src/test/pkg/TestClass.java:28: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1); + ~~~~~~ + src/test/pkg/TestClass.java:29: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L); + ~~~~~~~ + src/test/pkg/TestClass.java:30: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1"); + ~~~~~~~~~ + src/test/pkg/TestClass.java:31: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.putFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f); + ~~~~~~~~ + src/test/pkg/TestClass.java:32: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1); + ~~~~~~ + src/test/pkg/TestClass.java:33: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.putLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L); + ~~~~~~~ + src/test/pkg/TestClass.java:34: Warning: @Inject a SecureSettings instead [StaticSettingsProvider] + Secure.putString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1"); + ~~~~~~~~~ + src/test/pkg/TestClass.java:36: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT); + ~~~~~~~~ + src/test/pkg/TestClass.java:37: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT); + ~~~~~~ + src/test/pkg/TestClass.java:38: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT); + ~~~~~~~ + src/test/pkg/TestClass.java:39: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT); + ~~~~~~~~~ + src/test/pkg/TestClass.java:40: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f); + ~~~~~~~~ + src/test/pkg/TestClass.java:41: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1); + ~~~~~~ + src/test/pkg/TestClass.java:42: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L); + ~~~~~~~ + src/test/pkg/TestClass.java:43: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT, "1"); + ~~~~~~~~~ + src/test/pkg/TestClass.java:44: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.putFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f); + ~~~~~~~~ + src/test/pkg/TestClass.java:45: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1); + ~~~~~~ + src/test/pkg/TestClass.java:46: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.putLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L); + ~~~~~~~ + src/test/pkg/TestClass.java:47: Warning: @Inject a SystemSettings instead [StaticSettingsProvider] + Settings.System.putString(cr, Settings.Global.UNLOCK_SOUND, "1"); + ~~~~~~~~~ + 0 errors, 36 warnings + """ + ) + } + + private val stubs = androidStubs +} diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt new file mode 100644 index 000000000000..2183b3805eed --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt @@ -0,0 +1,15 @@ +package com.android.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestLintTask +import java.io.File + +@Suppress("UnstableApiUsage") +abstract class SystemUILintDetectorTest : LintDetectorTest() { + /** + * Customize the lint task to disable SDK usage completely. This ensures that running the tests + * in Android Studio has the same result as running the tests in atest + */ + override fun lint(): TestLintTask = + super.lint().allowMissingSdk(true).sdkHome(File("/dev/null")) +} diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 491ec20c519d..9a7327804051 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -246,8 +246,6 @@ -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/MediaTttChipControllerSender.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.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 @@ -528,6 +526,8 @@ -packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt -packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt -packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +-packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt +-packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt -packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt -packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt -packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt @@ -678,7 +678,6 @@ -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/MediaTttChipControllerSenderTest.kt -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt -packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt -packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index b3dd95553ed0..dee0f5cd1979 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -205,6 +205,13 @@ enum class Style(internal val coreSpec: CoreSpec) { n1 = TonalSpec(HueSource(), ChromaMultiple(0.0833)), n2 = TonalSpec(HueSource(), ChromaMultiple(0.1666)) )), + MONOCHROMATIC(CoreSpec( + a1 = TonalSpec(HueSource(), ChromaConstant(.0)), + a2 = TonalSpec(HueSource(), ChromaConstant(.0)), + a3 = TonalSpec(HueSource(), ChromaConstant(.0)), + n1 = TonalSpec(HueSource(), ChromaConstant(.0)), + n2 = TonalSpec(HueSource(), ChromaConstant(.0)) + )), } class ColorScheme( @@ -219,7 +226,7 @@ class ColorScheme( val neutral1: List<Int> val neutral2: List<Int> - constructor(@ColorInt seed: Int, darkTheme: Boolean): + constructor(@ColorInt seed: Int, darkTheme: Boolean) : this(seed, darkTheme, Style.TONAL_SPOT) @JvmOverloads @@ -227,7 +234,7 @@ class ColorScheme( wallpaperColors: WallpaperColors, darkTheme: Boolean, style: Style = Style.TONAL_SPOT - ): + ) : this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style) val allAccentColors: List<Int> @@ -472,4 +479,4 @@ class ColorScheme( return huePopulation } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp index cafaaf854eed..7709f210f22f 100644 --- a/packages/SystemUI/plugin/Android.bp +++ b/packages/SystemUI/plugin/Android.bp @@ -33,6 +33,7 @@ java_library { static_libs: [ "androidx.annotation_annotation", + "error_prone_annotations", "PluginCoreLib", "SystemUIAnimationLib", ], diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt index 6124e10144f2..6436dcb5f613 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt @@ -14,12 +14,11 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.plugins.log import android.os.Trace import android.util.Log -import com.android.systemui.log.dagger.LogModule -import com.android.systemui.util.collection.RingBuffer +import com.android.systemui.plugins.util.RingBuffer import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter import java.util.concurrent.ArrayBlockingQueue @@ -61,15 +60,18 @@ import kotlin.math.max * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or * the first letter of any of the previous. * - * Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory]. + * In SystemUI, buffers are provided by LogModule. Instances should be created using a SysUI + * LogBufferFactory. * * @param name The name of this buffer, printed when the buffer is dumped and in some other * situations. * @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start - * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches - * the maximum, it behaves like a ring buffer. + * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches the + * maximum, it behaves like a ring buffer. */ -class LogBuffer @JvmOverloads constructor( +class LogBuffer +@JvmOverloads +constructor( private val name: String, private val maxSize: Int, private val logcatEchoTracker: LogcatEchoTracker, @@ -78,7 +80,7 @@ class LogBuffer @JvmOverloads constructor( private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() } private val echoMessageQueue: BlockingQueue<LogMessage>? = - if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null + if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null init { if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) { @@ -133,11 +135,11 @@ class LogBuffer @JvmOverloads constructor( */ @JvmOverloads inline fun log( - tag: String, - level: LogLevel, - messageInitializer: MessageInitializer, - noinline messagePrinter: MessagePrinter, - exception: Throwable? = null, + tag: String, + level: LogLevel, + messageInitializer: MessageInitializer, + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, ) { val message = obtain(tag, level, messagePrinter, exception) messageInitializer(message) @@ -152,14 +154,13 @@ class LogBuffer @JvmOverloads constructor( * log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in * an initializer and a message printer. * - * Log buffers are limited by the number of entries, so logging more frequently - * will limit the time window that the LogBuffer covers in a bug report. Richer logs, on the - * other hand, make a bug report more actionable, so using the [log] with a messagePrinter to - * add more detail to every log may do more to improve overall logging than adding more logs - * with this method. + * Log buffers are limited by the number of entries, so logging more frequently will limit the + * time window that the LogBuffer covers in a bug report. Richer logs, on the other hand, make a + * bug report more actionable, so using the [log] with a messagePrinter to add more detail to + * every log may do more to improve overall logging than adding more logs with this method. */ fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) = - log(tag, level, {str1 = message}, { str1!! }) + log(tag, level, { str1 = message }, { str1!! }) /** * You should call [log] instead of this method. @@ -172,10 +173,10 @@ class LogBuffer @JvmOverloads constructor( */ @Synchronized fun obtain( - tag: String, - level: LogLevel, - messagePrinter: MessagePrinter, - exception: Throwable? = null, + tag: String, + level: LogLevel, + messagePrinter: MessagePrinter, + exception: Throwable? = null, ): LogMessage { if (!mutable) { return FROZEN_MESSAGE @@ -189,8 +190,7 @@ class LogBuffer @JvmOverloads constructor( * You should call [log] instead of this method. * * After acquiring a message via [obtain], call this method to signal to the buffer that you - * have finished filling in its data fields. The message will be echoed to logcat if - * necessary. + * have finished filling in its data fields. The message will be echoed to logcat if necessary. */ @Synchronized fun commit(message: LogMessage) { @@ -213,7 +213,8 @@ class LogBuffer @JvmOverloads constructor( /** Sends message to echo after determining whether to use Logcat and/or systrace. */ private fun echoToDesiredEndpoints(message: LogMessage) { - val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) || + val includeInLogcat = + logcatEchoTracker.isBufferLoggable(name, message.level) || logcatEchoTracker.isTagLoggable(message.tag, message.level) echo(message, toLogcat = includeInLogcat, toSystrace = systrace) } @@ -221,7 +222,12 @@ class LogBuffer @JvmOverloads constructor( /** Converts the entire buffer to a newline-delimited string */ @Synchronized fun dump(pw: PrintWriter, tailLength: Int) { - val iterationStart = if (tailLength <= 0) { 0 } else { max(0, buffer.size - tailLength) } + val iterationStart = + if (tailLength <= 0) { + 0 + } else { + max(0, buffer.size - tailLength) + } for (i in iterationStart until buffer.size) { buffer[i].dump(pw) @@ -229,9 +235,9 @@ class LogBuffer @JvmOverloads constructor( } /** - * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called. - * Calls to [log], [obtain], and [commit] will not affect the buffer and will return dummy - * values if necessary. + * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called. Calls + * to [log], [obtain], and [commit] will not affect the buffer and will return dummy values if + * necessary. */ @Synchronized fun freeze() { @@ -241,9 +247,7 @@ class LogBuffer @JvmOverloads constructor( } } - /** - * Undoes the effects of calling [freeze]. - */ + /** Undoes the effects of calling [freeze]. */ @Synchronized fun unfreeze() { if (frozen) { @@ -265,8 +269,11 @@ class LogBuffer @JvmOverloads constructor( } private fun echoToSystrace(message: LogMessage, strMessage: String) { - Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", - "$name - ${message.level.shortString} ${message.tag}: $strMessage") + Trace.instantForTrack( + Trace.TRACE_TAG_APP, + "UI Events", + "$name - ${message.level.shortString} ${message.tag}: $strMessage" + ) } private fun echoToLogcat(message: LogMessage, strMessage: String) { diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt index 53f231c9f9d2..b036cf0be1d6 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt @@ -14,17 +14,12 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.plugins.log import android.util.Log -/** - * Enum version of @Log.Level - */ -enum class LogLevel( - @Log.Level val nativeLevel: Int, - val shortString: String -) { +/** Enum version of @Log.Level */ +enum class LogLevel(@Log.Level val nativeLevel: Int, val shortString: String) { VERBOSE(Log.VERBOSE, "V"), DEBUG(Log.DEBUG, "D"), INFO(Log.INFO, "I"), diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt index dae2592e116c..9468681289bf 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.plugins.log import java.io.PrintWriter import java.text.SimpleDateFormat @@ -29,9 +29,10 @@ import java.util.Locale * * When a message is logged, the code doing the logging stores data in one or more of the generic * fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the - * [messagePrinter] function reads the data stored in the generic fields and converts that to a human- - * readable string. Thus, for every log type there must be a specialized initializer function that - * stores data specific to that log type and a specialized printer function that prints that data. + * [messagePrinter] function reads the data stored in the generic fields and converts that to a + * human- readable string. Thus, for every log type there must be a specialized initializer function + * that stores data specific to that log type and a specialized printer function that prints that + * data. * * See [LogBuffer.log] for more information. */ @@ -55,9 +56,7 @@ interface LogMessage { var bool3: Boolean var bool4: Boolean - /** - * Function that dumps the [LogMessage] to the provided [writer]. - */ + /** Function that dumps the [LogMessage] to the provided [writer]. */ fun dump(writer: PrintWriter) { val formattedTimestamp = DATE_FORMAT.format(timestamp) val shortLevel = level.shortString @@ -68,12 +67,12 @@ interface LogMessage { } /** - * A function that will be called if and when the message needs to be dumped to - * logcat or a bug report. It should read the data stored by the initializer and convert it to - * a human-readable string. The value of `this` will be the LogMessage to be printed. - * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any - * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance - * of the printer for each call, thwarting our attempts at avoiding any sort of allocation. + * A function that will be called if and when the message needs to be dumped to logcat or a bug + * report. It should read the data stored by the initializer and convert it to a human-readable + * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer + * should ONLY ever reference fields on the LogMessage and NEVER any variables in its enclosing + * scope. Otherwise, the runtime will need to allocate a new instance of the printer for each call, + * thwarting our attempts at avoiding any sort of allocation. */ typealias MessagePrinter = LogMessage.() -> String diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt index 4dd6f652d1c7..f2a6a91adcdf 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt @@ -14,11 +14,9 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.plugins.log -/** - * Recyclable implementation of [LogMessage]. - */ +/** Recyclable implementation of [LogMessage]. */ data class LogMessageImpl( override var level: LogLevel, override var tag: String, @@ -68,23 +66,24 @@ data class LogMessageImpl( companion object Factory { fun create(): LogMessageImpl { return LogMessageImpl( - LogLevel.DEBUG, - DEFAULT_TAG, - 0, - DEFAULT_PRINTER, - null, - null, - null, - null, - 0, - 0, - 0, - 0, - 0.0, - false, - false, - false, - false) + LogLevel.DEBUG, + DEFAULT_TAG, + 0, + DEFAULT_PRINTER, + null, + null, + null, + null, + 0, + 0, + 0, + 0, + 0.0, + false, + false, + false, + false + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt index 8cda4236bc87..cfe894f276a0 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt @@ -14,24 +14,16 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.plugins.log -/** - * Keeps track of which [LogBuffer] messages should also appear in logcat. - */ +/** Keeps track of which [LogBuffer] messages should also appear in logcat. */ interface LogcatEchoTracker { - /** - * Whether [bufferName] should echo messages of [level] or higher to logcat. - */ + /** Whether [bufferName] should echo messages of [level] or higher to logcat. */ fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean - /** - * Whether [tagName] should echo messages of [level] or higher to logcat. - */ + /** Whether [tagName] should echo messages of [level] or higher to logcat. */ fun isTagLoggable(tagName: String, level: LogLevel): Boolean - /** - * Whether to log messages in a background thread. - */ + /** Whether to log messages in a background thread. */ val logInBackgroundThread: Boolean } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt index 40b0cdc173d8..d3fabaccb6d3 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.plugins.log import android.content.ContentResolver import android.database.ContentObserver @@ -36,19 +36,15 @@ import android.provider.Settings * $ adb shell settings put global systemui/tag/<tag> <level> * ``` */ -class LogcatEchoTrackerDebug private constructor( - private val contentResolver: ContentResolver -) : LogcatEchoTracker { +class LogcatEchoTrackerDebug private constructor(private val contentResolver: ContentResolver) : + LogcatEchoTracker { private val cachedBufferLevels: MutableMap<String, LogLevel> = mutableMapOf() private val cachedTagLevels: MutableMap<String, LogLevel> = mutableMapOf() override val logInBackgroundThread = true companion object Factory { @JvmStatic - fun create( - contentResolver: ContentResolver, - mainLooper: Looper - ): LogcatEchoTrackerDebug { + fun create(contentResolver: ContentResolver, mainLooper: Looper): LogcatEchoTrackerDebug { val tracker = LogcatEchoTrackerDebug(contentResolver) tracker.attach(mainLooper) return tracker @@ -57,37 +53,35 @@ class LogcatEchoTrackerDebug private constructor( private fun attach(mainLooper: Looper) { contentResolver.registerContentObserver( - Settings.Global.getUriFor(BUFFER_PATH), - true, - object : ContentObserver(Handler(mainLooper)) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - super.onChange(selfChange, uri) - cachedBufferLevels.clear() - } - }) + Settings.Global.getUriFor(BUFFER_PATH), + true, + object : ContentObserver(Handler(mainLooper)) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + super.onChange(selfChange, uri) + cachedBufferLevels.clear() + } + } + ) contentResolver.registerContentObserver( - Settings.Global.getUriFor(TAG_PATH), - true, - object : ContentObserver(Handler(mainLooper)) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - super.onChange(selfChange, uri) - cachedTagLevels.clear() - } - }) + Settings.Global.getUriFor(TAG_PATH), + true, + object : ContentObserver(Handler(mainLooper)) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + super.onChange(selfChange, uri) + cachedTagLevels.clear() + } + } + ) } - /** - * Whether [bufferName] should echo messages of [level] or higher to logcat. - */ + /** Whether [bufferName] should echo messages of [level] or higher to logcat. */ @Synchronized override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean { return level.ordinal >= getLogLevel(bufferName, BUFFER_PATH, cachedBufferLevels).ordinal } - /** - * Whether [tagName] should echo messages of [level] or higher to logcat. - */ + /** Whether [tagName] should echo messages of [level] or higher to logcat. */ @Synchronized override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { return level >= getLogLevel(tagName, TAG_PATH, cachedTagLevels) diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt index 1a4ad1907ff1..3c8bda4a44e0 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt @@ -14,11 +14,9 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.plugins.log -/** - * Production version of [LogcatEchoTracker] that isn't configurable. - */ +/** Production version of [LogcatEchoTracker] that isn't configurable. */ class LogcatEchoTrackerProd : LogcatEchoTracker { override val logInBackgroundThread = false diff --git a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt index 97dc842ec699..68d78907f028 100644 --- a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.util.collection +package com.android.systemui.plugins.util import kotlin.math.max @@ -32,19 +32,16 @@ import kotlin.math.max * @param factory A function that creates a fresh instance of T. Used by the buffer while it's * growing to [maxSize]. */ -class RingBuffer<T>( - private val maxSize: Int, - private val factory: () -> T -) : Iterable<T> { +class RingBuffer<T>(private val maxSize: Int, private val factory: () -> T) : Iterable<T> { private val buffer = MutableList<T?>(maxSize) { null } /** * An abstract representation that points to the "end" of the buffer. Increments every time - * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into - * the backing array. Always points to the "next" available slot in the buffer. Before the - * buffer has completely filled, the value pointed to will be null. Afterward, it will be the - * value at the "beginning" of the buffer. + * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into the + * backing array. Always points to the "next" available slot in the buffer. Before the buffer + * has completely filled, the value pointed to will be null. Afterward, it will be the value at + * the "beginning" of the buffer. * * This value is unlikely to overflow. Assuming [advance] is called at rate of 100 calls/ms, * omega will overflow after a little under three million years of continuous operation. @@ -60,24 +57,23 @@ class RingBuffer<T>( /** * Advances the buffer's position by one and returns the value that is now present at the "end" - * of the buffer. If the buffer is not yet full, uses [factory] to create a new item. - * Otherwise, reuses the value that was previously at the "beginning" of the buffer. + * of the buffer. If the buffer is not yet full, uses [factory] to create a new item. Otherwise, + * reuses the value that was previously at the "beginning" of the buffer. * - * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that - * was previously stored on it. + * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that was + * previously stored on it. */ fun advance(): T { val index = indexOf(omega) omega += 1 - val entry = buffer[index] ?: factory().also { - buffer[index] = it - } + val entry = buffer[index] ?: factory().also { buffer[index] = it } return entry } /** * Returns the value stored at [index], which can range from 0 (the "start", or oldest element - * of the buffer) to [size] - 1 (the "end", or newest element of the buffer). + * of the buffer) to [size] + * - 1 (the "end", or newest element of the buffer). */ operator fun get(index: Int): T { if (index < 0 || index >= size) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/plugin/tests/log/LogBufferTest.kt index 56aff3c2fc8b..a39b856f0f49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt +++ b/packages/SystemUI/plugin/tests/log/LogBufferTest.kt @@ -2,6 +2,7 @@ package com.android.systemui.log import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.log.LogBuffer import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter @@ -18,8 +19,7 @@ class LogBufferTest : SysuiTestCase() { private lateinit var outputWriter: StringWriter - @Mock - private lateinit var logcatEchoTracker: LogcatEchoTracker + @Mock private lateinit var logcatEchoTracker: LogcatEchoTracker @Before fun setup() { @@ -67,15 +67,17 @@ class LogBufferTest : SysuiTestCase() { @Test fun dump_writesCauseAndStacktrace() { buffer = createBuffer() - val exception = createTestException("Exception message", + val exception = + createTestException( + "Exception message", "TestClass", - cause = createTestException("The real cause!", "TestClass")) + cause = createTestException("The real cause!", "TestClass") + ) buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) val dumpedString = dumpBuffer() - assertThat(dumpedString) - .contains("Caused by: java.lang.RuntimeException: The real cause!") + assertThat(dumpedString).contains("Caused by: java.lang.RuntimeException: The real cause!") assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:1)") assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:2)") } @@ -85,49 +87,47 @@ class LogBufferTest : SysuiTestCase() { buffer = createBuffer() val exception = RuntimeException("Root exception message") exception.addSuppressed( - createTestException( - "First suppressed exception", - "FirstClass", - createTestException("Cause of suppressed exp", "ThirdClass") - )) - exception.addSuppressed( - createTestException("Second suppressed exception", "SecondClass")) + createTestException( + "First suppressed exception", + "FirstClass", + createTestException("Cause of suppressed exp", "ThirdClass") + ) + ) + exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass")) buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) val dumpedStr = dumpBuffer() // first suppressed exception assertThat(dumpedStr) - .contains("Suppressed: " + - "java.lang.RuntimeException: First suppressed exception") + .contains("Suppressed: " + "java.lang.RuntimeException: First suppressed exception") assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:1)") assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:2)") assertThat(dumpedStr) - .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp") + .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp") assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:1)") assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:2)") // second suppressed exception assertThat(dumpedStr) - .contains("Suppressed: " + - "java.lang.RuntimeException: Second suppressed exception") + .contains("Suppressed: " + "java.lang.RuntimeException: Second suppressed exception") assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:1)") assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:2)") } private fun createTestException( - message: String, - errorClass: String, - cause: Throwable? = null, + message: String, + errorClass: String, + cause: Throwable? = null, ): Exception { val exception = RuntimeException(message, cause) - exception.stackTrace = (1..5).map { lineNumber -> - StackTraceElement(errorClass, - "TestMethod", - "$errorClass.java", - lineNumber) - }.toTypedArray() + exception.stackTrace = + (1..5) + .map { lineNumber -> + StackTraceElement(errorClass, "TestMethod", "$errorClass.java", lineNumber) + } + .toTypedArray() return exception } diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml index b22655af4c74..cbfb325fa9b3 100644 --- a/packages/SystemUI/res-keyguard/values-as/strings.xml +++ b/packages/SystemUI/res-keyguard/values-as/strings.xml @@ -64,9 +64,9 @@ <string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"ছিম কার্ড আনলক কৰি থকা হৈছে…"</string> <string name="kg_invalid_sim_pin_hint" msgid="2762202646949552978">"৪টাৰ পৰা ৮টা সংখ্যাযুক্ত এটা পিন লিখক।"</string> <string name="kg_invalid_sim_puk_hint" msgid="5319756880543857694">"PUK ক\'ডটো ৮টা বা তাতকৈ অধিক সংখ্যা থকা হ\'ব লাগিব।"</string> - <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> + <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string> <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string> - <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"আপুনি আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> + <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"আপুনি আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string> <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"ছিমৰ ভুল পিন ক\'ড, আপোনাৰ ডিভাইচটো আনলক কৰিবলৈ আপুনি এতিয়া আপোনাৰ বাহকৰ সৈতে যোগাযোগ কৰিবই লাগিব।"</string> <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ডিভাইচ আনলক কৰিবলৈ আপোনাৰ বাহকৰ লগত যোগাযোগ কৰিবই লগা হোৱাৰ পূৰ্বে আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে।}one{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে। }other{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে। }}"</string> <string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"ছিম ব্যৱহাৰযোগ্য নহয়। আপোনাৰ বাহকৰ সৈতে যোগাযোগ কৰক।"</string> @@ -75,9 +75,9 @@ <string name="kg_password_puk_failed" msgid="6778867411556937118">"ছিম PUKৰ জৰিয়তে আনলক কৰিব পৰা নগ\'ল!"</string> <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ইনপুট পদ্ধতি সলনি কৰক"</string> <string name="airplane_mode" msgid="2528005343938497866">"এয়াৰপ্লে’ন ম’ড"</string> - <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string> - <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত পিন দিয়াটো বাধ্যতামূলক"</string> - <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string> + <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string> + <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পিন দিয়াটো বাধ্যতামূলক"</string> + <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string> <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"অতিৰিক্ত সুৰক্ষাৰ বাবে আর্হি দিয়াটো বাধ্যতামূলক"</string> <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"অতিৰিক্ত সুৰক্ষাৰ বাবে পিন দিয়াটো বাধ্যতামূলক"</string> <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"অতিৰিক্ত সুৰক্ষাৰ বাবে পাছৱর্ড দিয়াটো বাধ্যতামূলক"</string> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index d90156d451c7..a129fb650ba6 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -201,13 +201,13 @@ <string name="kg_prompt_reason_restart_password">Password required after device restarts</string> <!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] --> - <string name="kg_prompt_reason_timeout_pattern">Pattern required for additional security</string> + <string name="kg_prompt_reason_timeout_pattern">For additional security, use pattern instead</string> <!-- An explanation text that the pin needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] --> - <string name="kg_prompt_reason_timeout_pin">PIN required for additional security</string> + <string name="kg_prompt_reason_timeout_pin">For additional security, use PIN instead</string> <!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] --> - <string name="kg_prompt_reason_timeout_password">Password required for additional security</string> + <string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string> <!-- An explanation text that the credential needs to be entered because a device admin has locked the device. [CHAR LIMIT=80] --> @@ -241,4 +241,6 @@ <string name="clock_title_bubble">Bubble</string> <!-- Name of the "Analog" clock face [CHAR LIMIT=15]--> <string name="clock_title_analog">Analog</string> + <!-- Title of bouncer when we want to authenticate before continuing with action. [CHAR LIMIT=NONE] --> + <string name="keyguard_unlock_to_continue">Unlock your device to continue</string> </resources> diff --git a/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml b/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml index 69390848245d..33c68bf1f6ac 100644 --- a/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml +++ b/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml @@ -16,6 +16,6 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <solid android:color="@android:color/white" /> + <solid android:color="@android:color/transparent" /> <corners android:radius="@dimen/qs_media_album_radius" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/chipbar.xml index ae8e38e2634b..bc97e511e7f4 100644 --- a/packages/SystemUI/res/layout/media_ttt_chip.xml +++ b/packages/SystemUI/res/layout/chipbar.xml @@ -16,15 +16,15 @@ <!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view is the root view of a window, we cannot change the root view's margins.) --> <!-- Alphas start as 0 because the view will be animated in. --> -<com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView +<com.android.systemui.temporarydisplay.chipbar.ChipbarRootView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/media_ttt_sender_chip" + android:id="@+id/chipbar_root_view" android:layout_width="wrap_content" android:layout_height="wrap_content"> <LinearLayout - android:id="@+id/media_ttt_sender_chip_inner" + android:id="@+id/chipbar_inner" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -39,7 +39,7 @@ > <com.android.internal.widget.CachingIconView - android:id="@+id/app_icon" + android:id="@+id/start_icon" android:layout_width="@dimen/media_ttt_app_icon_size" android:layout_height="@dimen/media_ttt_app_icon_size" android:layout_marginEnd="12dp" @@ -69,7 +69,7 @@ /> <ImageView - android:id="@+id/failure_icon" + android:id="@+id/error" android:layout_width="@dimen/media_ttt_status_icon_size" android:layout_height="@dimen/media_ttt_status_icon_size" android:layout_marginStart="@dimen/media_ttt_last_item_start_margin" @@ -78,11 +78,11 @@ android:alpha="0.0" /> + <!-- TODO(b/245610654): Re-name all the media-specific dimens to chipbar dimens instead. --> <TextView - android:id="@+id/undo" + android:id="@+id/end_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/media_transfer_undo" android:textColor="?androidprv:attr/textColorOnAccent" android:layout_marginStart="@dimen/media_ttt_last_item_start_margin" android:textSize="@dimen/media_ttt_text_size" @@ -97,4 +97,4 @@ /> </LinearLayout> -</com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView> +</com.android.systemui.temporarydisplay.chipbar.ChipbarRootView> diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 1a1fc75a41a1..0e9abee2f050 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.screenshot.DraggableConstraintLayout +<com.android.systemui.clipboardoverlay.ClipboardOverlayView 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" @@ -157,4 +157,4 @@ 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 +</com.android.systemui.clipboardoverlay.ClipboardOverlayView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml new file mode 100644 index 000000000000..1a1fc75a41a1 --- /dev/null +++ b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml @@ -0,0 +1,160 @@ +<?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/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index 5dc34b9db594..a565988c14ad 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -73,8 +73,8 @@ android:singleLine="true" android:textDirection="locale" android:textAppearance="@style/TextAppearance.QS.Status" - android:transformPivotX="0sp" - android:transformPivotY="20sp" + android:transformPivotX="0dp" + android:transformPivotY="24dp" android:scaleX="1" android:scaleY="1" /> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index f07f10d47476..eccce2f79844 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -94,19 +94,14 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"ለአንድ የማያ ገጽ ቀረጻ ክፍለ-ጊዜ በመካሄድ ያለ ማሳወቂያ"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"መቅረጽ ይጀመር?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"እየቀረጹ ሳለ የAndroid ስርዓት በማያ ገጽዎ ላይ የሚታይ ወይም በመሣሪያዎ ላይ የሚጫወት ማንኛውም ሚስጥራዊነት ያለው መረጃን መያዝ ይችላል። ይህ የይለፍ ቃላትን፣ የክፍያ መረጃን፣ ፎቶዎችን፣ መልዕክቶችን እና ኦዲዮን ያካትታል።"</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"መላው ማያ ገጹን ቅረጽ"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"አንድ ነጠላ መተግበሪያን ቅረጽ"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"እየቀረጹ እያለ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"አንድን መተግበሪያ እየቀረጹ ሳለ Android በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"መቅረጽ ጀምር"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"ኦዲዮን ቅረጽ"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"የመሣሪያ ኦዲዮ"</string> - <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"እንደ ሙዚቃ፣ ጥሪዎች እና የጥሪ ቅላጼዎች ያሉ የመሣሪያዎ ድምጽ"</string> + <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"እንደ ሙዚቃ፣ ጥሪዎች እና የጥሪ ቅላጼዎች ያሉ የመሣሪያዎ ድምፅ"</string> <string name="screenrecord_mic_label" msgid="2111264835791332350">"ማይክሮፎን"</string> <string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"የመሣሪያ ኦዲዮ እና ማይክሮፎን"</string> <string name="screenrecord_start" msgid="330991441575775004">"ጀምር"</string> @@ -274,14 +269,14 @@ <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"የ<xliff:g id="DATA_LIMIT">%s</xliff:g> ማስጠንቀቂያ"</string> <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"የሥራ መተግበሪያዎች"</string> <string name="quick_settings_night_display_label" msgid="8180030659141778180">"የምሽት ብርሃን"</string> - <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"ጸሐይ ስትጠልቅ ይበራል"</string> - <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"ጸሐይ እስክትወጣ ድረስ"</string> + <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"ፀሐይ ስትጠልቅ ይበራል"</string> + <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"ፀሐይ እስክትወጣ ድረስ"</string> <string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"<xliff:g id="TIME">%s</xliff:g> ላይ ይበራል"</string> <string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"እስከ <xliff:g id="TIME">%s</xliff:g> ድረስ"</string> <string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"ጨለማ ገጽታ"</string> <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ባትሪ ቆጣቢ"</string> - <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ጸሐይ ስትጠልቅ ይበራል"</string> - <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ጸሐይ እስክትወጣ ድረስ"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ፀሐይ ስትጠልቅ ይበራል"</string> + <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ፀሐይ እስክትወጣ ድረስ"</string> <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> ላይ ይበራል"</string> <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"እስከ <xliff:g id="TIME">%s</xliff:g> ድረስ"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"በመኝታ ሰዓት ላይ"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ይህን ተግባር የሚያቀርበው አገልግሎት በእርስዎ ማያ ገጽ ላይ ያለን ወይም በእርስዎ መሣሪያ ላይ በመጫወት ላይ ያለን ሁሉንም መረጃ በቀረጻ ወይም casting ላይ እያለ መዳረሻ ይኖረዋል። ይህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ ፎቶዎች፣ መልዕክቶች እና እርስዎ የሚጫውቱት ኦዲዮን የመሳሰለ መረጃን ያካትታል።"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ቀረጻ ወይም cast ማድረግ ይጀምር?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"ከ<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ጋር ቀረጻ ወይም casting ይጀምር?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> እንዲያጋራ ወይም እንዲቀርጽ ይፈቀድለት?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"መላው ማያ ገጽ"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"አንድ ነጠላ መተግበሪያ"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"አንድን መተግበሪያ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ቀጥል"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"መተግበሪያ ያጋሩ ወይም ይቅረጹ"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"ያቀናብሩ"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ታሪክ"</string> @@ -445,7 +433,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> @@ -527,11 +515,11 @@ <string name="notification_silence_title" msgid="8608090968400832335">"ፀጥ ያለ"</string> <string name="notification_alert_title" msgid="3656229781017543655">"ነባሪ"</string> <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_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_automatic" msgid="5813109268050235275">"ይህ ማሳወቂያ ድምጽ ወይም ንዝረት መደረግ ካለበት ስርዓቱ እንዲወሰን ያድርጉት"</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> <string name="notification_channel_summary_automatic_promoted" msgid="1301710305149590426">"<b>ሁኔታ:</b> ክፍተኛ ደረጃ ተሰጥቶታል"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 732c2e62c29f..8f996fb9794a 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"إشعار مستمر لجلسة تسجيل شاشة"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"هل تريد بدء التسجيل؟"</string> <string name="screenrecord_description" msgid="1123231719680353736">"أثناء التسجيل، يمكن أن يسجّل نظام Android أي معلومات حساسة مرئية على شاشتك أو يتم تشغيلها على جهازك. ويشمل ذلك كلمات المرور ومعلومات الدفع والصور والرسائل والمقاطع الصوتية."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"تسجيل الشاشة بالكامل"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"تسجيل محتوى تطبيق واحد"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"أثناء التسجيل، يمكن لنظام Android الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"أثناء تسجيل محتوى أحد التطبيقات، يمكن لنظام Android الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"بدء التسجيل"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"تسجيل الصوت"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"صوت الجهاز"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"الصوت من جهازك، مثلاً الموسيقى والمكالمات ونغمات الرنين"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ستتمكن الخدمة التي تقدّم هذه الوظيفة من الوصول إلى كل المعلومات المرئية لك على الشاشة أو التي يتم تشغيلها على جهازك أثناء التسجيل أو الإرسال. ويشمل ذلك معلومات مثل كلمات المرور وتفاصيل الدفع والصور والرسائل والمقاطع الصوتية التي تشغِّلها."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"هل تريد بدء التسجيل أو الإرسال؟"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"هل تريد بدء التسجيل أو الإرسال باستخدام <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>؟"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"هل تريد السماح لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>بالمشاركة أو التسجيل؟"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"الشاشة بالكامل"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"تطبيق واحد"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"أثناء المشاركة أو التسجيل أو البث، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثه، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"متابعة"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"مشاركة محتوى تطبيق أو تسجيله"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"إدارة"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"السجلّ"</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index c2f8e4350df6..fe95fa024b93 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রীন ৰেকৰ্ডিং ছেশ্বন চলি থকা সময়ত পোৱা জাননী"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"ৰেকৰ্ড কৰা আৰম্ভ কৰিবনে?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"ৰেকৰ্ড কৰি থাকোঁতে, Android Systemএ আপোনাৰ স্ক্রীনত দৃশ্যমান হোৱা অথবা আপোনাৰ ডিভাইচত প্লে’ হৈ থকা যিকোনো সংবেনদশীল তথ্য কেপচাৰ কৰিব পাৰে। এইটোত পাছৱর্ড, পৰিশোধৰ তথ্য, ফট’, বার্তাসমূহ আৰু অডিঅ’ অন্তর্ভুক্ত হয়।"</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"গোটেই স্ক্ৰীনখন ৰেকৰ্ড কৰক"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"এটা এপ্ ৰেকৰ্ড কৰক"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"আপুনি ৰেকৰ্ড কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"আপুনি এপ এপ্ ৰেকৰ্ড কৰাৰ সময়ত সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"ৰেকৰ্ডিং আৰম্ভ কৰক"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"অডিঅ’ ৰেকৰ্ড কৰক"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ডিভাইচৰ অডিঅ’"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"সংগীত, কল আৰু ৰিংট’নসমূহৰ দৰে আপোনাৰ ডিভাইচৰ পৰা কেপচাৰ কৰিব পৰা ধ্বনি"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"এই সুবিধাটো প্ৰদান কৰা সেৱাটোৱে আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিঙৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা আটাইবোৰ তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ জৰিয়তে ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে ?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ক শ্বেয়াৰ অথবা ৰেকৰ্ড কৰিবলৈ অনুমতি দিবনে?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"সম্পূৰ্ণ স্ক্ৰীন"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"এটা একক এপ্"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"অব্যাহত ৰাখক"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"এটা এপ্ শ্বেয়াৰ অথবা ৰেকৰ্ড কৰক"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string> @@ -575,7 +563,7 @@ <string name="keyboard_key_dpad_down" msgid="2110172278574325796">"তললৈ"</string> <string name="keyboard_key_dpad_left" msgid="8329738048908755640">"বাওঁফালে"</string> <string name="keyboard_key_dpad_right" msgid="6282105433822321767">"সোঁফালে"</string> - <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"স্ক্ৰীণৰ মাজত"</string> + <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"স্ক্ৰীনৰ মাজত"</string> <string name="keyboard_key_tab" msgid="4592772350906496730">"Tab"</string> <string name="keyboard_key_space" msgid="6980847564173394012">"স্পেচ"</string> <string name="keyboard_key_enter" msgid="8633362970109751646">"এণ্টাৰ"</string> @@ -596,7 +584,7 @@ <string name="keyboard_key_numpad_template" msgid="7316338238459991821">"নামপেড <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"সংলগ্নক আঁতৰাওক"</string> <string name="keyboard_shortcut_group_system" msgid="1583416273777875970">"ছিষ্টেম"</string> - <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"গৃহ স্ক্ৰীণ"</string> + <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"গৃহ স্ক্ৰীন"</string> <string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"শেহতীয়াসমূহ"</string> <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"উভতি যাওক"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"জাননীসমূহ"</string> @@ -693,7 +681,7 @@ <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string> <string name="high_temp_title" msgid="2218333576838496100">"ফ\'নটো গৰম হ\'বলৈ ধৰিছে"</string> <string name="high_temp_notif_message" msgid="1277346543068257549">"ফ’নটো ঠাণ্ডা হৈ থকাৰ সময়ত কিছুমান সুবিধা উপলব্ধ নহয়।\nঅধিক তথ্যৰ বাবে টিপক"</string> - <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপোনাৰ ফ\'নটোৱে নিজে নিজে ঠাণ্ডা হ\'বলৈ স্বয়ংক্ৰিয়ভাৱে চেষ্টা কৰিব। আপুনি ফ\'নটো ব্যৱহাৰ কৰি থাকিব পাৰে কিন্তু ই লাহে লাহে চলিব পাৰে।\n\nফ\'নটো সম্পূৰ্ণভাৱে ঠাণ্ডা হোৱাৰ পিছত ই আগৰ নিচিনাকৈয়েই চলিব।"</string> + <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপোনাৰ ফ\'নটোৱে নিজে নিজে ঠাণ্ডা হ\'বলৈ স্বয়ংক্ৰিয়ভাৱে চেষ্টা কৰিব। আপুনি ফ\'নটো ব্যৱহাৰ কৰি থাকিব পাৰে কিন্তু ই লাহে লাহে চলিব পাৰে।\n\nফ\'নটো সম্পূৰ্ণভাৱে ঠাণ্ডা হোৱাৰ পাছত ই আগৰ নিচিনাকৈয়েই চলিব।"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string> <string name="high_temp_alarm_title" msgid="8654754369605452169">"আপোনাৰ ডিভাইচটো আনপ্লাগ কৰক"</string> <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"আপোনাৰ ডিভাইচটো চাৰ্জিং প’ৰ্টৰ ওচৰত গৰম হৈছে। যদি এইটো কোনো চার্জাৰ অথবা ইউএছবিৰ সহায়ক সামগ্ৰীৰ সৈতে সংযুক্ত হৈ আছে, ইয়াক আনপ্লাগ কৰক আৰু কে’বলডালো গৰম হ\'ব পাৰে, গতিকে যত্ন লওক।"</string> @@ -722,7 +710,7 @@ <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string> <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> চলি আছে"</string> <string name="instant_apps_message" msgid="6112428971833011754">"এপ্টো ইনষ্ট\'ল নকৰাকৈ খোলা হৈছে।"</string> - <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ইনষ্ট\'ল নকৰাকৈয়েই এপটো খোলা হৈছে। অধিক জানিবলৈ টিপক।"</string> + <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ইনষ্ট\'ল নকৰাকৈয়েই এপ্টো খোলা হৈছে। অধিক জানিবলৈ টিপক।"</string> <string name="app_info" msgid="5153758994129963243">"এপৰ তথ্য"</string> <string name="go_to_web" msgid="636673528981366511">"ব্ৰাউজাৰলৈ যাওক"</string> <string name="mobile_data" msgid="4564407557775397216">"ম’বাইল ডেটা"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index fa6da9413356..cbc7c8aea155 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущо известие за сесия за записване на екрана"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Да се стартира ли записът?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"По време на записване системата Android може да запише и поверителна информация, която е показана на екрана или възпроизвеждана на устройството ви. Това включва пароли, данни за плащане, снимки, съобщения и аудио."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записване на целия екран"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записване на едно приложение"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Когато записвате, Android има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Когато записвате приложение, Android има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Стартиране на записа"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Записване на звук"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Аудио от устройството"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук от устройството ви, като например музика, обаждания и мелодии"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Услугата, предоставяща тази функция, ще има достъп до цялата информация, която е видима на екрана или възпроизвеждана от устройството ви по време на записване или предаване. Това включва различна информация, като например пароли, данни за плащане, снимки, съобщения и възпроизвеждано аудио."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Да се стартира ли записване или предаване?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Да се стартира ли записване или предаване чрез <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Разрешавате ли на <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> да споделя и записва?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Цял екран"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Едно приложение"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Напред"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделяне или записване на приложение"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Управление"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 8dc5f33b8358..44fb0e610ef7 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রিন রেকর্ডিং সেশন চলার বিজ্ঞপ্তি"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"রেকর্ডিং শুরু করবেন?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"রেকর্ড করার সময়, আপনার স্ক্রিনে দেখানো বা ডিভাইসে চালানো যেকোনও ধরনের সংবেদনশীল তথ্য Android সিস্টেম ক্যাপচার করতে পারে। এর মধ্যে পাসওয়ার্ড, পেমেন্টের তথ্য, ফটো, মেসেজ এবং অডিও সম্পর্কিত তথ্য থাকে।"</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"সম্পূর্ণ স্ক্রিন রেকর্ড করুন"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"একটিমাত্র অ্যাপ রেকর্ড করুন"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"আপনার রেকর্ড করার সময়, স্ক্রিনে দেখা যায় বা ডিভাইসে খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি Android-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"কোনও অ্যাপ আপনার রেকর্ড করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি Android-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"রেকর্ড করা শুরু করুন"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"অডিও রেকর্ড করুন"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ডিভাইস অডিও"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"মিউজিক, কল এবং রিংটোনগুলির মতো আপনার ডিভাইস থেকে সাউন্ড"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"রেকর্ড করা বা কাস্টিং করার সময় আপনার স্ক্রিনে দেখানো বা ডিভাইসে চালানো হয়েছে এমন সমস্ত তথ্যের অ্যাক্সেস এই ফাংশন প্রদানকারী পরিষেবার কাছে থাকবে। এর মধ্যে আপনার পাসওয়ার্ড, পেমেন্টের বিবরণ, ফটো, মেসেজ এবং যে অডিও আপনি চালান সেগুলি সম্পর্কিত তথ্য রয়েছে।"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"রেকর্ড অথবা কাস্টিং শুরু করতে চান?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> দিয়ে রেকর্ড করা বা কাস্টিং শুরু করবেন?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-কে শেয়ার বা রেকর্ড করার অনুমতি দেবেন?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"সম্পূর্ণ স্ক্রিন"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"একটি মাত্র অ্যাপ"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, স্ক্রিনে দেখা যায় বা ডিভাইসে খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"কোনও অ্যাপ আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"চালিয়ে যান"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"অ্যাপ শেয়ার বা রেকর্ড করা"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"সবকিছু সাফ করুন"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"পরিচালনা করুন"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 5e3fb3362719..172b14d8f6d0 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificació en curs d\'una sessió de gravació de la pantalla"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Vols iniciar la gravació?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Durant la gravació, el sistema Android pot capturar qualsevol informació sensible que es mostri a la pantalla o que es reprodueixi al dispositiu. Això inclou contrasenyes, informació de pagament, fotos, missatges i àudio."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grava la pantalla completa"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grava una sola aplicació"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mentre graves, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mentre graves una aplicació, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Inicia la gravació"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Grava l\'àudio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Àudio del dispositiu"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"So del dispositiu, com ara música, trucades i sons de trucada"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servei que ofereix aquesta funció tindrà accés a tota la informació visible a la teva pantalla o que es reprodueix al dispositiu mentre graves o emets contingut, com ara contrasenyes, detalls dels pagaments, fotos, missatges i àudio que reprodueixis."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vols començar a gravar o emetre contingut?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vols començar a gravar o emetre contingut amb <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vols permetre que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparteixi o gravi?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tota la pantalla"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola aplicació"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Comparteix o grava una aplicació"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gestiona"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index b22656c8787a..034fe9a33eb7 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Trvalé oznámení o relaci nahrávání"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Spustit nahrávání?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Při nahrávání může systém Android zaznamenávat citlivé údaje, které jsou viditelné na obrazovce nebo které jsou přehrávány na zařízení. Týká se to hesel, údajů o platbě, fotek, zpráv a zvuků."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Nahrát celou obrazovku"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Nahrát samostatnou aplikaci"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Během nahrávání má Android přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Během nahrávání aplikace má Android přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Spustit nahrávání"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Nahrávat zvuk"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk zařízení"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvuk ze zařízení, například hudba, hovory a vyzvánění"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Služba, která tuto funkci poskytuje, bude mít při nahrávání nebo odesílání přístup ke všem informacím, které jsou viditelné na obrazovce nebo které jsou přehrávány ze zařízení. Týká se to i hesel, údajů o platbě, fotek, zpráv a přehrávaných zvuků."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Začít nahrávat nebo odesílat?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Začít nahrávat nebo odesílat s aplikací <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Povolit aplikaci <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sdílení nebo nahrávání?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Celá obrazovka"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Samostatná aplikace"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Když sdílíte, nahráváte nebo odesíláte obsah, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Když sdílíte, nahráváte nebo odesíláte aplikaci, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovat"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Sdílení nebo nahrání aplikace"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Spravovat"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historie"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 9edb337763b8..03d12aff4ff1 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Konstant notifikation om skærmoptagelse"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Vil du starte optagelse?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Når du optager, kan Android-systemet registrere følsomme oplysninger, der er synlige på din skærm, eller som afspilles på din enhed. Dette inkluderer adgangskoder, betalingsoplysninger, fotos, meddelelser og lyd."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Optag hele skærmen"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Optag én app"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mens du optager, har Android adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mens du optager en app, har Android adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Start optagelse"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Optag lyd"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Enhedslyd"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Lyd fra din enhed såsom musik, opkald og ringetoner"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Tjenesten, der tilbyder denne funktion, får adgang til alle de oplysninger, der er synlige på din skærm, eller som afspilles på din enhed, når du optager eller caster. Dette omfatter oplysninger som f.eks. adgangskoder, betalingsoplysninger, billeder, beskeder og afspillet lyd."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vil du begynde at optage eller caste?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vil du begynde at optage eller caste via <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vil du tillade, at <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> deler eller optager?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hele skærmen"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Én app"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Når du deler, optager eller caster, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, optager eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsæt"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller optag en app"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 76980658f41f..1d13c87fc548 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Fortlaufende Benachrichtigung für eine Bildschirmaufzeichnung"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Aufzeichnung starten?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Beim Aufnehmen kann das Android-System vertrauliche Informationen erfassen, die auf deinem Bildschirm angezeigt oder von deinem Gerät wiedergegeben werden. Das können Passwörter, Zahlungsinformationen, Fotos, Nachrichten und Audioinhalte sein."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gesamten Bildschirm aufnehmen"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Eine einzelne App aufnehmen"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Während der Aufnahme hat Android Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Während der Aufnahme einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Aufnahme starten"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Audio aufnehmen"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio des Geräts"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Audioinhalte auf deinem Gerät, wie Musik, Anrufe und Klingeltöne"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Der Anbieter dieser App erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise angezeigte Passwörter, Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aufnahme oder Stream starten?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Aufnehmen oder Streamen mit der App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" starten?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Zulassen, dass <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Inhalte teilt oder aufnimmt?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Gesamter Bildschirm"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Eine einzelne App"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Beim Teilen, Aufnehmen oder Übertragen hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Beim Teilen, Aufnehmen oder Übertragen einer App hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Weiter"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App teilen oder aufnehmen"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Verwalten"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Verlauf"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index e9d1534442dc..0df5f16c0409 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación continua de una sesión de grabación de la pantalla"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"¿Empezar a grabar?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Mientras grabas, el sistema Android puede capturar información sensible que se muestre o se reproduzca en tu dispositivo, como contraseñas, datos de pago, fotos, mensajes y audio."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grabar toda la pantalla"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grabar una sola aplicación"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mientras grabes contenido, Android podrá acceder a todo lo que sea visible en tu pantalla o que reproduzcas en tu dispositivo. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mientras grabes una aplicación, Android podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar grabación"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Grabar audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio del dispositivo"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sonido de tu dispositivo, como música, llamadas y tonos de llamada"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servicio que ofrece esta función tendrá acceso a toda la información que se muestre en la pantalla o se reproduzca en el dispositivo mientras grabas o envías contenido, incluyendo contraseñas, detalles de pagos, fotos, mensajes y audios que reproduzcas."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"¿Empezar a grabar o enviar contenido?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"¿Iniciar grabación o el envío de contenido en <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"¿Permitir que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparta o grabe contenido?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Toda la pantalla"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola aplicación"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Cuando compartas, grabes o envíes contenido, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que sea visible en tu pantalla o que reproduzcas en tu dispositivo. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o envíes una aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una aplicación"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionar"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index fae8364fa55b..d42b39704594 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pooleli märguanne ekraanikuva salvestamise seansi puhul"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Kas alustada salvestamist?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Salvestamise ajal võib Androidi süsteem jäädvustada tundlikku teavet, mis on ekraanikuval nähtav või mida seadmes esitatakse. See hõlmab paroole, makseteavet, fotosid, sõnumeid ja heli."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Kogu ekraanikuva salvestamine"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ühe rakenduse salvestamine"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Salvestamise ajal on Androidil juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Rakenduse salvestamise ajal on Androidil juurdepääs kõigele, mis on selles rakenduses nähtaval või mida selles esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Alusta salvestamist"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Heli salvestamine"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Seadme heli"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Seadmest pärinev heli, nt muusika, kõned ja helinad"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Seda funktsiooni pakkuv teenus saab juurdepääsu kogu teabele, mis on teie ekraanikuval nähtav või mida seadmes salvestamise või ülekande ajal esitatakse. See hõlmab teavet, nagu paroolid, maksete üksikasjad, fotod, sõnumid ja esitatav heli."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Kas alustada salvestamist või ülekannet?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Kas alustada rakendusega <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> salvestamist või ülekannet?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Kas lubada rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> jagada või salvestada?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Kogu ekraanikuva"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Üks rakendus"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kui jagate, salvestate või kannate üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kui jagate, salvestate või kannate rakendust üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jätka"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Rakenduse jagamine või salvestamine"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Haldamine"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Ajalugu"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index cf53e327b9a5..fc93a800e974 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pantailaren grabaketa-saioaren jakinarazpen jarraitua"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Grabatzen hasi nahi duzu?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Pantaila grabatzen duzun bitartean, baliteke Android sistemak pantailan agertzen den edo gailuak erreproduzitzen duen kontuzko informazioa grabatzea; besteak beste, pasahitzak, ordainketa-informazioa, argazkiak, mezuak eta audioa."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grabatu pantaila osoko edukia"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grabatu aplikazio bakar bat"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Grabatzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztirako sarbidea du Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Aplikazio bat grabatzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Hasi grabatzen"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Grabatu audioa"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Gailuaren audioa"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Gailuko soinuak; adibidez, musika, deiak eta tonuak"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Zerbait grabatzen edo igortzen duzunean, pantailan ikus daitekeen edo gailuak erreproduzitzen duen informazio guztia atzitu ahalko du funtzio hori eskaintzen duen zerbitzuak; besteak beste, pasahitzak, ordainketen xehetasunak, argazkiak, mezuak eta erreproduzitzen dituzun audioak."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Grabatzen edo igortzen hasi nahi duzu?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioarekin grabatzen edo igortzen hasi nahi duzu?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Edukia partekatu edo grabatzeko baimena eman nahi diozu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioari?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Pantaila osoa"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Aplikazio bakar bat"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Egin aurrera"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partekatu edo grabatu aplikazioak"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Kudeatu"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 14a65dc4bcd6..088234281cae 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pysyvä ilmoitus näytön tallentamisesta"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Aloitetaanko tallennus?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Tallennuksen aikana Android-järjestelmä voi tallentaa mitä tahansa näytöllä näkyvää tai laitteen toistamaa arkaluontoista tietoa. Näitä tietoja ovat esimerkiksi salasanat, maksutiedot, kuvat, viestit ja audio."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Tallenna koko näyttö"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Tallenna yhtä sovellusta"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Kun tallennat, Android saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Kun tallennat sovellusta, Android saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Aloita tallennus"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Tallenna audiota"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Laitteen audio"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Musiikki, puhelut, soittoäänet ja muut äänet laitteesta"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ominaisuuden tarjoavalla palvelulla on pääsy kaikkiin näytölläsi näkyviin tietoihin ja tietoihin laitteesi toistamasta sisällöstä tallennuksen tai striimauksen aikana. Näitä tietoja ovat esimerkiksi salasanat, maksutiedot, kuvat, viestit ja toistettava audiosisältö."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aloitetaanko tallentaminen tai striimaus?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Haluatko, että <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aloittaa tallennuksen tai striimauksen?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Sallitaanko, että <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> jaetaan tai tallennetaan?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Koko näyttö"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Yksittäinen sovellus"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kun jaat, tallennat tai striimaat, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kun jaat, tallennat tai striimaat sovellusta, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jatka"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Jaa sovellus tai tallenna sen sisältöä"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Muuta asetuksia"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 9fdae3e3f0a8..a80b03791bd9 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement de l\'écran"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Démarrer l\'enregistrement ?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Durant l\'enregistrement, le système Android peut capturer les infos sensibles affichées à l\'écran ou lues sur votre appareil. Cela inclut les mots de passe, les infos de paiement, les photos, les messages et l\'audio."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Enregistrer tout l\'écran"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Enregistrer une seule appli"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Lorsque vous enregistrez une appli, Android à accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Lorsque vous enregistrez une appli, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Lancer l\'enregistrement"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Enregistrer l\'audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio de l\'appareil"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Son provenant de l\'appareil (musique, appels et sonneries, etc.)"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Le service qui fournit cette fonction aura accès à toutes les infos visibles sur votre écran ou lues depuis votre appareil lors d\'un enregistrement ou de la diffusion d\'un contenu. Cela comprend, entre autres, vos mots de passe, les détails de vos paiements, vos photos, vos messages ou les contenus audio que vous écoutez."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Démarrer l\'enregistrement ou la diffusion ?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Démarrer l\'enregistrement ou la diffusion avec <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Autoriser <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> à partager ou enregistrer ?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tout l\'écran"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Une seule appli"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Lorsque vous partagez, enregistrez ou castez, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou castez une appli, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une appli"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 36036aaf06b9..bb3894aa21e6 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación en curso sobre unha sesión de gravación de pantalla"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Queres iniciar a gravación?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Durante a gravación, o sistema Android pode captar información confidencial que apareza na pantalla ou se reproduza no dispositivo, como contrasinais, información de pago, fotos, mensaxes e audio."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gravar pantalla completa"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Gravar unha soa aplicación"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Durante a gravación, Android ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Cando gravas unha aplicación, Android ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar gravación"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Gravar audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio do dispositivo"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Son do dispositivo (por exemplo, música, chamadas e tons de chamada)"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"O servizo que proporciona esta función terá acceso a toda a información visible na pantalla ou reproducida desde o teu dispositivo mentres graves ou emitas contido. Isto inclúe información como contrasinais, detalles de pago, fotos, mensaxes e o audio que reproduzas."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Queres iniciar a gravación ou a emisión?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Queres comezar a gravar ou emitir contido con <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Queres permitir que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparta ou grave contido?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Pantalla completa"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Unha soa aplicación"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Cando compartes, gravas ou emites contido, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cando compartes, gravas ou emites unha aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir ou gravar unha aplicación"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todas"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Xestionar"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 3d1b1ea56fb9..9dc6f67b1b93 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रिकॉर्ड सेशन के लिए जारी सूचना"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"क्या आपको रिकॉर्डिंग शुरू करनी है?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"रिकॉर्ड करते समय, Android सिस्टम आपकी स्क्रीन पर दिखने वाली या चलाई जाने वाली संवेदनशील जानकारी को कैप्चर कर सकता है. इसमें पासवर्ड, पैसे चुकाने से जुड़ी जानकारी, फ़ोटो, मैसेज, और ऑडियो शामिल हैं."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"पूरी स्क्रीन रिकॉर्ड करें"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"सिर्फ़ एक ऐप्लिकेशन रिकॉर्ड करें"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Android के पास, रिकॉर्ड करने के दौरान, स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"कोई ऐप्लिकेशन रिकॉर्ड करने के दौरान, Android के पास उस पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"रिकॉर्ड करना शुरू करें"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"ऑडियो रिकॉर्ड करें"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिवाइस ऑडियो"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"आपके डिवाइस से आने वाली आवाज़ जैसे कि संगीत, कॉल, और रिंगटोन"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"इस फ़ंक्शन को उपलब्ध कराने वाली सेवा, रिकॉर्ड या कास्ट करते समय, आपकी स्क्रीन पर दिखने वाली या चलाई जाने वाली जानकारी को ऐक्सेस कर सकती है. इसमें पासवर्ड, पैसे चुकाने से जुड़ी जानकारी, फ़ोटो, मैसेज, और चलाए जाने वाले ऑडियो शामिल हैं."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रिकॉर्डिंग या कास्ट करना शुरू करें?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> का इस्तेमाल करके रिकॉर्ड और कास्ट करना शुरू करें?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> को शेयर या रिकॉर्ड करने की अनुमति दें?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"पूरी स्क्रीन"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"सिर्फ़ एक ऐप्लिकेशन"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी रखें"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ऐप्लिकेशन शेयर करें या उसकी रिकॉर्डिंग करें"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"मैनेज करें"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index fed009f42025..5e5e249fc415 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Էկրանի տեսագրման աշխատաշրջանի ընթացիկ ծանուցում"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Սկսե՞լ տեսագրումը"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Տեսագրման ընթացքում Android համակարգը կարող է գրանցել անձնական տեղեկություններ, որոնք տեսանելի են էկրանին կամ նվագարկվում են ձեր սարքում։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Տեսագրել ամբողջ էկրանը"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Տեսագրել մեկ հավելված"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Երբ դուք տեսագրում եք էկրանը, Android-ին հասանելի է դառնում այն ամենը, ինչ տեսանելի է էկրանին և նվագարկվում է ձեր սարքում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Երբ դուք տեսագրում եք որևէ հավելվածի էկրանը, Android-ին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Սկսել տեսագրումը"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Ձայնագրել"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Սարքի ձայները"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Ձեր սարքի ձայները, օրինակ՝ երաժշտությունը, զանգերն ու զանգերանգները"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ձայնագրման և հեռարձակման ընթացքում ծառայությունների մատակարարին հասանելի կլինեն ձեր սարքի էկրանին ցուցադրվող տեղեկությունները և ձեր սարքով նվագարկվող նյութերը։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Սկսե՞լ ձայնագրումը կամ հեռարձակումը"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Սկսե՞լ ձայնագրումը կամ հեռարձակումը <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածով"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Թույլատրե՞լ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին ցուցադրել կամ տեսագրել էկրանը"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ամբողջ էկրանը"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Մեկ հավելված"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ տեսանելի է էկրանին և նվագարկվում է ձեր սարքում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Շարունակել"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Հավելվածի էկրանի ցուցադրում կամ տեսագրում"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Կառավարել"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Պատմություն"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 01569000b915..e372c3735478 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifikasi yang sedang berjalan untuk sesi rekaman layar"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Mulai merekam?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Saat merekam, Sistem Android dapat ikut merekam informasi sensitif yang terlihat di layar atau diputar di perangkat Anda. Informasi ini mencakup sandi, info pembayaran, foto, pesan, dan audio."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Rekam seluruh layar"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Rekam satu aplikasi"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Saat Anda merekam, Android akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Saat Anda merekam aplikasi, Android akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Mulai merekam"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Rekam audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio perangkat"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Suara dari perangkat Anda, seperti musik, panggilan, dan nada dering"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Layanan yang menyediakan fungsi ini akan memiliki akses ke semua informasi yang terlihat di layar atau diputar dari perangkat saat merekam atau melakukan transmisi. Ini mencakup informasi seperti sandi, detail pembayaran, foto, pesan, dan audio yang Anda putar."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Mulai merekam atau melakukan transmisi?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Mulai merekam atau melakukan transmisi dengan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Izinkan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> untuk membagikan atau merekam?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Seluruh layar"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Satu aplikasi"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Jika Anda membagikan, merekam, atau mentransmisikan, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Lanjutkan"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Bagikan atau rekam aplikasi"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Kelola"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Histori"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 7622ad4ed87b..136db9e92afc 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Áframhaldandi tilkynning fyrir skjáupptökulotu"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Hefja upptöku?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Á meðan tekið er upp getur Android kerfið fangað viðkvæmar upplýsingar sem sjást á skjánum eða spilast í tækinu. Þar á meðal eru upplýsingar á borð við aðgangsorð, greiðsluupplýsingar, myndir, skilaboð og hljóð."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Taka upp allan skjáinn"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Taka upp eitt forrit"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Þegar þú tekur upp hefur Android aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Þegar þú tekur upp forrit hefur Android aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Hefja upptöku"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Taka upp hljóð"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Hljóð tækis"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Hljóð úr tækinu á borð við tónlist, símtöl og hringitóna"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Þjónustan sem býður upp á þennan eiginleika fær aðgang að öllum upplýsingum sem sjást á skjánum eða eru spilaðar í tækinu á meðan upptaka eða útsending er í gangi, þar á meðal aðgangsorði, greiðsluupplýsingum, myndum, skilaboðum og hljóðefni sem þú spilar."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Viltu hefja upptöku eða útsendingu?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Viltu hefja upptöku eða útsendingu með <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Leyfa <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> að deila eða taka upp?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Allur skjárinn"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Eitt forrit"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Þegar þú deilir, tekur upp eða sendir út hefur<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Þegar þú deilir, tekur upp eða sendir út forrit hefur <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Áfram"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deila eða taka upp forrit"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Ferill"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index d6631e7683b0..df3e6fd6007c 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды бейнеге жазудың ағымдағы хабарландыруы"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Жазу басталсын ба?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Android жүйесі экранда көрсетілетін немесе құрылғыда ойнатылатын құпия ақпаратты жазып алуы мүмкін. Ондай ақпаратқа құпия сөздер, төлем ақпараты, фотосуреттер, хабарлар және аудио жатады."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Бүкіл экранды жазу"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Жалғыз қолданбаны жазу"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Жазу кезінде Android жүйесі экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Қолданба экранын жазу кезінде Android жүйесі қолданбада көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Жазуды бастау"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Аудио жазу"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Құрылғыдан шығатын дыбыс"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Музыка, қоңыраулар және рингтондар сияқты құрылғыдан шығатын дыбыс"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Осы функцияны ұсынатын қызмет жазу не трансляциялау кезінде экранда көрсетілетін немесе құрылғыда дыбысталатын ақпаратты пайдалана алады. Бұған құпия сөздер, төлем туралы мәліметтер, суреттер, хабарлар және аудиоматериалдар кіреді."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жазу немесе трансляциялау басталсын ба?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> арқылы жазу немесе трансляциялау басталсын ба?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасына экранды бөлісуге не жазуға рұқсат берілсін бе?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Бүкіл экран"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Жалғыз қолданба"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Жалғастыру"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Қолданба экранын бөлісу не жазу"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазалау"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Басқару"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Тарих"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 713d20d130fe..83a5e87260ca 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೆಶನ್ಗಾಗಿ ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಅಧಿಸೂಚನೆ"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"ರೆಕಾರ್ಡಿಂಗ್ ಸಮಯದಲ್ಲಿ, ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ಗೋಚರಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲಾದ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು Android ಸಿಸ್ಟಂ ಕ್ಯಾಪ್ಚರ್ ಮಾಡಬಹುದು. ಇದು ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ಮಾಹಿತಿ, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೋವನ್ನು ಒಳಗೊಂಡಿದೆ."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ಒಂದೇ ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ನೀವು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಿ"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ಸಾಧನದ ಆಡಿಯೋ"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ನಿಮ್ಮ ಸಾಧನದ ಧ್ವನಿ ಉದಾ: ಸಂಗೀತ, ಕರೆಗಳು ಮತ್ತು ರಿಂಗ್ಟೋನ್ಗಳು"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ಈ ವೈಶಿಷ್ಟ್ಯವು ಒದಗಿಸುವ ಸೇವೆಗಳು, ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಗೋಚರಿಸುವ ಅಥವಾ ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಾಗ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಎಲ್ಲಾ ಮಾಹಿತಿಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುತ್ತವೆ. ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೋ ಪ್ಲೇಬ್ಯಾಕ್ನಂತಹ ಮಾಹಿತಿಯನ್ನು ಇದು ಒಳಗೊಂಡಿದೆ."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಿಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಮೂಲಕ ರೆಕಾರ್ಡಿಂಗ್, ಬಿತ್ತರಿಸುವುದನ್ನು ಪ್ರಾರಂಭಿಸುವುದೇ?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ಹಂಚಿಕೊಳ್ಳಲು ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಲು <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಅನ್ನು ಅನುಮತಿಸಬೇಕೆ?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ಒಂದೇ ಆ್ಯಪ್"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ಮುಂದುವರಿಸಿ"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಿ ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ಇತಿಹಾಸ"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 290c51724ec7..f3fb7a085549 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"화면 녹화 세션에 관한 지속적인 알림"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"녹화를 시작하시겠습니까?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Android 시스템이 녹화 중에 화면에 표시되거나 기기에서 재생되는 민감한 정보를 캡처할 수 있습니다. 여기에는 비밀번호, 결제 정보, 사진, 메시지 및 오디오가 포함됩니다."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"전체 화면 녹화"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"단일 앱 녹화"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"녹화할 때 Android에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"앱을 녹화할 때 Android에서 해당 앱에서 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"녹화 시작"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"오디오 녹음"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"기기 오디오"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"음악, 통화, 벨소리와 같이 기기에서 나는 소리"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"이 기능을 제공하는 서비스는 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"녹화 또는 전송을 시작하시겠습니까?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>으로 녹화 또는 전송을 시작하시겠습니까?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 공유 또는 녹화를 허용할까요?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"전체 화면"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"단일 앱"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"공유하거나 녹화하거나 전송할 때 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 앱에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"앱을 공유하거나 녹화하거나 전송할 때는 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"계속"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"앱 공유 또는 녹화"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"기록"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 9cb8bfdff771..5a63b626c81c 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Aktīvs paziņojums par ekrāna ierakstīšanas sesiju"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Vai sākt ierakstīšanu?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Ierakstīšanas laikā Android sistēmā var tikt tverta jebkura sensitīvā informācija, kas ir redzama jūsu ekrānā vai tiek atskaņota jūsu ierīcē. Šī informācija ir paroles, maksājumu informācija, fotoattēli, ziņojumi un audio."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Ierakstīt visu ekrānu"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ierakstīt vienu lietotni"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Ierakstīšanas laikā Android var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Lietotnes ierakstīšanas laikā Android var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Sākt ierakstīšanu"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Ierakstīt audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Ierīces audio"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Skaņa no jūsu ierīces, piemēram, mūzika, sarunas un zvana signāli"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Pakalpojums, kas nodrošina šo funkciju, iegūs piekļuvi visai informācijai, kas ierakstīšanas vai apraides laikā tiks rādīta jūsu ekrānā vai atskaņota jūsu ierīcē. Atļauja attiecas uz tādu informāciju kā paroles, maksājumu informācija, fotoattēli, ziņojumi un jūsu atskaņotais audio saturs."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vai vēlaties sākt ierakstīšanu/apraidi?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vai vēlaties sākt ierakstīšanu vai apraidi, izmantojot lietotni <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vai atļaujat lietotnei <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> veikt kopīgošanu vai ierakstīšanu?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Viss ekrāns"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Viena lietotne"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Turpināt"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Lietotnes kopīgošana vai ierakstīšana"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Pārvaldīt"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Vēsture"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index bf1c262008fc..7611822c689c 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"ഒരു സ്ക്രീൻ റെക്കോർഡിംഗ് സെഷനായി നിലവിലുള്ള അറിയിപ്പ്"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"റെക്കോർഡിംഗ് ആരംഭിക്കണോ?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"റെക്കോർഡ് ചെയ്യുമ്പോൾ, നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് തന്ത്രപ്രധാന വിവരങ്ങളും Android സിസ്റ്റത്തിന് പകർത്താനാവും. പാസ്വേഡുകൾ, പേയ്മെന്റ് വിവരം, ഫോട്ടോകൾ, സന്ദേശങ്ങൾ, ഓഡിയോ എന്നിവ ഇതിൽ ഉൾപ്പെടുന്നു."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"പൂർണ സ്ക്രീൻ റെക്കോർഡ് ചെയ്യൂ"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ഒറ്റ ആപ്പ് റെക്കോർഡ് ചെയ്യുക"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"റെക്കോർഡ് ചെയ്യുമ്പോൾ, Android-ന് സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ഒരു ആപ്പ് റെക്കോർഡ് ചെയ്യുമ്പോൾ, Android-ന് ആ ആപ്പിൽ കാണിക്കുന്നതോ പ്ലേ ചെയ്യുന്നതോ ആയ എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"റെക്കോർഡിംഗ് ആരംഭിക്കുക"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ഉപകരണത്തിന്റെ ഓഡിയോ"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"സംഗീതം, കോളുകൾ, റിംഗ്ടോണുകൾ എന്നിവപോലെ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്നുള്ള ശബ്ദം"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"റെക്കോർഡ് ചെയ്യുമ്പോഴോ കാസ്റ്റ് ചെയ്യുമ്പോഴോ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് പ്ലേ ചെയ്യുന്നതോ നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ആയ എല്ലാ വിവരങ്ങളിലേക്കും ഈ ഫംഗ്ഷൻ ലഭ്യമാക്കുന്ന സേവനത്തിന് ആക്സസ് ഉണ്ടായിരിക്കും. നിങ്ങൾ പ്ലേ ചെയ്യുന്ന ഓഡിയോ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, പാസ്വേഡുകൾ എന്നിവ പോലുള്ള വിവരങ്ങൾ ഇതിൽ ഉൾപ്പെടുന്നു."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ഉപയോഗിച്ച് റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"പങ്കിടാനോ റെക്കോർഡ് ചെയ്യാനോ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"മുഴുവൻ സ്ക്രീൻ"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ഒറ്റ ആപ്പ്"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"തുടരുക"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ഒരു ആപ്പ് പങ്കിടുക അല്ലെങ്കിൽ റെക്കോർഡ് ചെയ്യുക"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്ക്കുക"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ചരിത്രം"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index f7b8ef0446d4..2f96f3397306 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रेकॉर्ड सत्रासाठी सुरू असलेली सूचना"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"रेकॉर्डिंग सुरू करायचे आहे का?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"रेकॉर्डिंग करताना, Android सिस्टीम तुमच्या स्क्रीनवर दिसणारी किंवा तुमच्या डिव्हाइसवर प्ले केलेली कोणतीही संवेदनशील माहिती कॅप्चर करू शकते. यात पासवर्ड, पेमेंट माहिती, फोटो, मेसेज आणि ऑडिओचा समावेश आहे."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"संपूर्ण स्क्रीन रेकॉर्ड करा"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"एकच अॅप रेकॉर्ड करा"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"तुम्ही रेकॉर्ड करत असताना, Android ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"तुम्ही अॅप रेकॉर्ड करत असताना, Android ला त्या अॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"रेकॉर्डिंग सुरू करा"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"ऑडिओ रेकॉर्ड करा"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिव्हाइस ऑडिओ"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"तुमच्या डिव्हाइसवरील आवाज, जसे की संगीत, कॉल आणि रिंगटोन"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"हे कार्य पुरवणाऱ्या सेवेस तुमच्या स्क्रीनवर दृश्यमान असलेल्या किंवा रेकॉर्ड किंवा कास्ट करताना तुमच्या डिव्हाइसमधून प्ले केलेल्या सर्व माहितीचा अॅक्सेस असेल. यामध्ये पासवर्ड, पेमेंट तपशील, फोटो, मेसेज आणि तुम्ही प्ले केलेला ऑडिओ यासारख्या माहितीचा समावेश असतो."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकॉर्ड करणे किंवा कास्ट करणे सुरू करायचे का ?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ने रेकॉर्ड करणे किंवा कास्ट करणे सुरू करायचे का?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला शेअर किंवा रेकॉर्ड करण्याची अनुमती द्यायची आहे का?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"संपूर्ण स्क्रीन"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"एक अॅप"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तुम्ही अॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला त्या अॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"पुढे सुरू ठेवा"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"अॅप शेअर किंवा रेकॉर्ड करा"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index a874e4588e25..fe1b908e098b 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"ဖန်သားပြင် ရိုက်ကူးသည့် စက်ရှင်အတွက် ဆက်တိုက်လာနေသော အကြောင်းကြားချက်"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"စတင် ရိုက်ကူးမလား။"</string> <string name="screenrecord_description" msgid="1123231719680353736">"ရိုက်ကူးနေစဉ်အတွင်း Android စနစ်သည် သင့်ဖန်သားပြင်ပေါ်တွင် မြင်နိုင်သော (သို့) သင့်စက်ပစ္စည်းတွင် ဖွင့်ထားသော အရေးကြီးသည့် အချက်အလက်များကို ရိုက်ယူနိုင်သည်။ ၎င်းတွင် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ ဓာတ်ပုံ၊ မက်ဆေ့ဂျ်နှင့် အသံများ ပါဝင်သည်။"</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ဖန်သားပြင်တစ်ခုလုံးရိုက်ကူးရန်"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"အက်ပ်တစ်ခုတွင် ရိုက်ကူးရန်"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ရိုက်ကူးနေစဉ် Android သည် သင့်ဖန်သားပြင်ရှိ မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"အက်ပ်တစ်ခုကို ရိုက်ကူးနေစဉ် Android သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"စတင်ရိုက်ကူးရန်"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"အသံဖမ်းရန်"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"စက်ပစ္စည်းအသံ"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"သီချင်း၊ ဖုန်းခေါ်ဆိုမှုနှင့် ဖုန်းမြည်သံကဲ့သို့ သင့်စက်ပစ္စည်းမှ အသံ"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ဤဝန်ဆောင်မှုသည် ရိုက်ကူးဖမ်းယူနေစဉ် (သို့) ကာစ်လုပ်နေစဉ်အတွင်း သင့်ဖန်သားပြင်တွင် မြင်ရသော (သို့) သင့်စက်တွင် ဖွင့်ထားသော အချက်အလက်အားလုံးကို ကြည့်နိုင်ပါမည်။ ၎င်းတွင် စကားဝှက်များ၊ ငွေပေးချေမှုအသေးစိတ်များ၊ ဓာတ်ပုံများ၊ မက်ဆေ့ဂျ်များနှင့် သင်ဖွင့်သည့်အသံကဲ့သို့သော အချက်အလက်များ ပါဝင်သည်။"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ရိုက်ကူးဖမ်းယူခြင်း (သို့) ကာစ်လုပ်ခြင်း စတင်မလား။"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> နှင့် ဖမ်းယူခြင်း သို့မဟုတ် ကာစ်လုပ်ခြင်း စတင်မလား။"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"မျှဝေရန် (သို့) ရိုက်ကူးရန် <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ကို ခွင့်ပြုမလား။"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ဖန်သားပြင်တစ်ခုလုံး"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"အက်ပ်တစ်ခုတွင်"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် သင့်ဖန်သားပြင်ရှိ မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"အက်ပ်ဖြင့် မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ရှေ့ဆက်ရန်"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"အက်ပ် မျှဝေခြင်း (သို့) ရိုက်ကူးခြင်း"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"စီမံရန်"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"မှတ်တမ်း"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 6dd9d8f4b7f1..951eb8c46436 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Vedvarende varsel for et skjermopptak"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Vil du starte et opptak?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Under opptak kan Android-systemet registrere all sensitiv informasjon som er synlig på skjermen eller spilles av på enheten. Dette inkluderer passord, betalingsinformasjon, bilder, meldinger og lyd."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Ta opp hele skjermen"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ta opp én app"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Når du tar opp noe, har Android tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Når du tar opp en app, har Android tilgang til alt som vises eller spilles av i appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Start opptaket"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Spill inn lyd"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Enhetslyd"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Lyd fra enheten, f.eks. musikk, samtaler og ringelyder"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Tjenesten som leverer denne funksjonen, får tilgang til all informasjon som er synlig på skjermen din, eller som spilles av fra enheten når du tar opp eller caster. Dette inkluderer informasjon som passord, betalingsopplysninger, bilder, meldinger og lyd du spiller av."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vil du starte opptak eller casting?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vil du starte opptak eller casting med <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vil du gi <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tillatelse til å dele eller ta opp?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hele skjermen"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Én app"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Når du deler, tar opp eller caster noe, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, tar opp eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsett"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller ta opp en app"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Logg"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 2362b401df7d..35112a7b1a03 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"कुनै स्क्रिन रेकर्ड गर्ने सत्रका लागि चलिरहेको सूचना"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"रेकर्ड गर्न थाल्ने हो?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"रेकर्ड गर्दा, Android सिस्टमले तपाईंको स्क्रिनमा देखिने वा तपाईंको डिभाइसमा प्ले गरिने सबै संवेदनशील जानकारी रेकर्ड गर्न सक्छ। यो जानकारीमा पासवर्ड, भुक्तानीसम्बन्धी जानकारी, फोटो, सन्देश र अडियो समावेश हुन्छ।"</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"पूरै स्क्रिन रेकर्ड गर्नुहोस्"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"एउटा एप मात्र रेकर्ड गर्नुहोस्"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"तपाईंले रेकर्ड गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले रेकर्ड गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"तपाईंले रेकर्ड गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले रेकर्ड गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"रेकर्ड गर्न थाल्नुहोस्"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"अडियो रेकर्ड गरियोस्"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिभाइसको अडियो"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"तपाईंको डिभाइसका सङ्गीत, कल र रिङटोन जस्ता साउन्ड"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"यो कार्य गर्ने सेवाले तपाईंको स्क्रिनमा देख्न सकिने सबै जानकारी अथवा रेकर्ड वा कास्ट गर्दा तपाईंको डिभाइसबाट प्ले गरिएका कुरा हेर्न तथा प्रयोग गर्न सक्छ। यसले हेर्न तथा प्रयोग गर्न सक्ने कुरामा पासवर्ड, भुक्तानीका विवरण, फोटो, सन्देश र तपाईंले प्ले गर्ने अडियो कुराहरू समावेश हुन सक्छन्।"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकर्ड गर्न वा cast गर्न थाल्ने हो?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> मार्फत रेकर्ड गर्न वा cast गर्न थाल्ने हो?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> लाई सेयर गर्न वा रेकर्ड गर्न दिने हो?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"पूर्ण स्क्रिन"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"एकल एप"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले सेयर, रेकर्ड वा कास्ट गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी राख्नुहोस्"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"सेयर वा रेकर्ड गर्नका लागि एप चयन गर्नुहोस्"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थित गर्नुहोस्"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 01d0d9fc2216..04c3774be53a 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"ଏକ ସ୍କ୍ରିନ୍ ରେକର୍ଡ୍ ସେସନ୍ ପାଇଁ ଚାଲୁଥିବା ବିଜ୍ଞପ୍ତି"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବେ?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"ରେକର୍ଡିଂ ସମୟରେ, Android ସିଷ୍ଟମ୍ ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ଚାଲୁଥିବା ଯେ କୌଣସି ସମ୍ବେଦନଶୀଳ ସୂଚନାକୁ କ୍ୟାପଚର୍ କରିପାରିବ। ଏଥିରେ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ସୂଚନା, ଫଟୋ, ମେସେଜ ଏବଂ ଅଡିଓ ଅନ୍ତର୍ଭୁକ୍ତ।"</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ ରେକର୍ଡ କର"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ଏକ ସିଙ୍ଗଲ ଆପ ରେକର୍ଡ କରନ୍ତୁ"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ଆପଣ ରେକର୍ଡିଂ କରିବା ବେଳେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ଆପଣ ଏକ ଆପ ରେକର୍ଡିଂ କରିବା ବେଳେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"ରେକର୍ଡିଂ ଆରମ୍ଭ କରନ୍ତୁ"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ଡିଭାଇସ୍ ଅଡିଓ"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ଆପଣଙ୍କ ଡିଭାଇସରୁ ସାଉଣ୍ଡ, ଯେପରିକି ସଙ୍ଗୀତ, କଲ୍ ଏବଂ ରିଂଟୋନଗୁଡ଼ିକ"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ବେଳେ ଆପଣଙ୍କର ଡିଭାଇସରେ ଦେଖାଯାଉଥିବା ବା ଆପଣଙ୍କ ଡିଭାଇସରୁ ପ୍ଲେ କରାଯାଉଥିବା ସବୁ ସୂଚନାକୁ ଏହି ଫଙ୍କସନ୍ ପ୍ରଦାନ କରୁଥିବା ସେବାର ଆକ୍ସେସ୍ ରହିବ। ପାସ୍ୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ଫଟୋ, ମେସେଜ୍ ଏବଂ ଆପଣ ଚଲାଉଥିବା ଅଡିଓ ପରି ସୂଚନା ଏଥିରେ ଅନ୍ତର୍ଭୁକ୍ତ ଅଛି।"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ଆରମ୍ଭ କରିବେ?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ସହ ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ଆରମ୍ଭ କରିବେ?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ସେୟାର କିମ୍ବା ରେକର୍ଡ କରିବା ପାଇଁ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ଏକ ସିଙ୍ଗଲ ଆପ"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ସେହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ଜାରି ରଖନ୍ତୁ"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ଏକ ଆପକୁ ସେୟାର କିମ୍ବା ରେକର୍ଡ କରନ୍ତୁ"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"ସମସ୍ତ ଖାଲି କରନ୍ତୁ"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"ପରିଚାଳନା କରନ୍ତୁ"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ଇତିହାସ"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index e9f4cf9862bb..5f9709a9f46a 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"ਕਿਸੇ ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਸੈਸ਼ਨ ਲਈ ਚੱਲ ਰਹੀ ਸੂਚਨਾ"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"ਕੀ ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰਨੀ ਹੈ?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਸਿਸਟਮ ਕੋਈ ਵੀ ਅਜਿਹੀ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਕੈਪਚਰ ਕਰ ਸਕਦਾ ਹੈ ਜੋ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਣਯੋਗ ਹੈ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਵਿੱਚ ਪਾਸਵਰਡ, ਭੁਗਤਾਨ ਵੇਰਵੇ, ਫ਼ੋਟੋਆਂ, ਸੁਨੇਹੇ ਅਤੇ ਆਡੀਓ ਸ਼ਾਮਲ ਹਨ।"</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ਇਕਹਿਰੀ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰੋ"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"ਆਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ਡੀਵਾਈਸ ਆਡੀਓ"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਧੁਨੀ, ਜਿਵੇਂ ਕਿ ਸੰਗੀਤ, ਕਾਲਾਂ ਅਤੇ ਰਿੰਗਟੋਨਾਂ"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ਇਹ ਫੰਕਸ਼ਨ ਪ੍ਰਦਾਨ ਕਰਨ ਵਾਲੀ ਸੇਵਾ ਕੋਲ ਸਾਰੀ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਹੋਵੇਗੀ ਜੋ ਕਿ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਣਯੋਗ ਹੁੰਦੀ ਹੈ ਜਾਂ ਰਿਕਾਰਡ ਜਾਂ ਕਾਸਟ ਕਰਨ ਵੇਲੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਵਿੱਚ ਪਾਸਵਰਡ, ਭੁਗਤਾਨ ਵੇਰਵੇ, ਫ਼ੋਟੋਆਂ, ਸੁਨੇਹੇ ਅਤੇ ਤੁਹਾਡੇ ਵੱਲੋਂ ਚਲਾਏ ਆਡੀਓ ਦੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੁੰਦੀ ਹੈ।"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ਕੀ ਰਿਕਾਰਡ ਜਾਂ ਕਾਸਟ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਨਾਲ ਰਿਕਾਰਡਿੰਗ ਜਾਂ ਕਾਸਟ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ਕੀ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਜਾਂ ਰਿਕਾਰਡ ਕਰਨ ਲਈ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ਇਕਹਿਰੀ ਐਪ"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ਜਾਰੀ ਰੱਖੋ"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ਐਪ ਨੂੰ ਸਾਂਝਾ ਕਰੋ ਜਾਂ ਰਿਕਾਰਡ ਕਰੋ"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ਇਤਿਹਾਸ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index dad40e10db75..563f8dc89638 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Stałe powiadomienie o sesji rejestrowania zawartości ekranu"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Rozpocząć nagrywanie?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Podczas nagrywania system Android może rejestrować wszelkie informacje poufne wyświetlane na ekranie lub odtwarzane na urządzeniu. Dotyczy to m.in. haseł, szczegółów płatności, zdjęć, wiadomości i odtwarzanych dźwięków."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Nagrywaj cały ekran"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Nagrywaj pojedynczą aplikację"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Podczas nagrywania Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Podczas nagrywania treści z aplikacji Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Zacznij nagrywać"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Nagraj dźwięk"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Dźwięki z urządzenia"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Dźwięki odtwarzane na urządzeniu, na przykład muzyka, połączenia i dzwonki"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Podczas nagrywania i przesyłania usługa udostępniająca tę funkcję będzie miała dostęp do wszystkich informacji widocznych na ekranie lub odtwarzanych na urządzeniu. Dotyczy to m.in. haseł, szczegółów płatności, zdjęć, wiadomości i odtwarzanych dźwięków."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Rozpocząć nagrywanie lub przesyłanie?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Rozpocząć nagrywanie lub przesyłanie za pomocą aplikacji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Zezwolić aplikacji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> na udostępnianie lub nagrywanie?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Cały ekran"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Pojedyncza aplikacja"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Dalej"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Udostępnianie i nagrywanie za pomocą aplikacji"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Zarządzaj"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 9110aa0117a4..085522ae1412 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação persistente de uma sessão de gravação de ecrã"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Iniciar a gravação?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Enquanto estiver a gravar, o sistema Android pode capturar quaisquer informações confidenciais que estejam visíveis no ecrã ou que sejam reproduzidas no dispositivo. Isto inclui palavras-passe, informações de pagamento, fotos, mensagens e áudio."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gravar o ecrã inteiro"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Gravar só uma app"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Enquanto está a gravar, o Android tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Enquanto está a gravar uma app, o Android tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Começar gravação"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Gravar áudio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Áudio do dispositivo"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"O som do dispositivo, como música, chamadas e toques."</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"O serviço que fornece esta função terá acesso a todas as informações que estiverem visíveis no ecrã ou que forem reproduzidas a partir do dispositivo durante a gravação ou transmissão. Isto inclui informações como palavras-passe, detalhes de pagamentos, fotos, mensagens e áudio reproduzido."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Começar a gravar ou a transmitir?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Começar a gravar ou a transmitir com a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Permitir que a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> partilhe ou grave?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ecrã inteiro"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Só uma app"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quando está a partilhar, gravar ou transmitir, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando está a partilhar, gravar ou transmitir uma app, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partilhe ou grave uma app"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gerir"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index f82954eb46c1..0db2dd594477 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificare în curs pentru o sesiune de înregistrare a ecranului"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Începi înregistrarea?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"În timpul înregistrării, sistemul Android poate captura informațiile sensibile vizibile pe ecran sau redate pe dispozitiv. Aici sunt incluse parole, informații de plată, fotografii, mesaje și conținut audio."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Înregistrează tot ecranul"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Înregistrează doar o aplicație"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Când înregistrezi, Android are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Când înregistrezi o aplicație, Android are acces la orice se afișează sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Începe înregistrarea"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Înregistrează audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Conținutul audio de la dispozitiv"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sunetul de la dispozitiv, precum muzică, apeluri și tonuri de sonerie"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Serviciul care oferă această funcție va avea acces la toate informațiile vizibile pe ecran sau redate pe dispozitiv în timp ce înregistrezi sau proiectezi. Între aceste informații se numără parole, detalii de plată, fotografii, mesaje și conținutul audio pe care îl redai."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Începi să înregistrezi sau să proiectezi?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Începi să înregistrezi sau să proiectezi cu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> poate permite accesul sau înregistra?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tot ecranul"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"O singură aplicație"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Când permiți accesul, înregistrezi sau proiectezi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice se afișează pe ecran sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuă"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Permite accesul la o aplicație sau înregistreaz-o"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionează"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Istoric"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 84b5061755a5..7dcf873da40e 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущее уведомление для записи видео с экрана"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Начать запись?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"В записи может появиться конфиденциальная информация, которая видна на экране или воспроизводится на устройстве, например пароли, сведения о платежах, фотографии, сообщения и аудиозаписи."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записывать весь экран"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записывать окно приложения"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Когда вы записываете видео с экрана, Android получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Когда вы записываете видео с окна приложения, Android получает доступ ко всему, что видно и воспроизводится в приложении. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Начать запись"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Записывать аудио"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук с устройства"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук с вашего устройства, например музыка, звонки и рингтоны"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Во время записи или трансляции у сервиса, предоставляющего эту функцию, будет доступ ко всей информации, которая видна на экране или воспроизводится на устройстве, включая пароли, сведения о платежах, фотографии, сообщения и звуки."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Начать запись или трансляцию?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Начать запись или трансляцию через приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\"?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Разрешить приложению \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" демонстрировать экран или записывать видео с него?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Весь экран"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Отдельное приложение"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далее"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Демонстрация экрана или запись видео с него"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 1ccdbf36507f..de498cb075c8 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"තිර පටිගත කිරීමේ සැසියක් සඳහා කෙරෙන දැනුම් දීම"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"පටිගත කිරීම ආරම්භ කරන්නද?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"පටිගත කරන අතරතුර, Android පද්ධතියට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය කරන ඕනෑම සංවේදී තොරතුරක් ග්රහණය කර ගැනීමට හැකිය. මෙයට මුරපද, ගෙවීම් තොරතුරු, ඡායාරූප, පණිවිඩ සහ ඕඩියෝ ඇතුළත් වේ."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"සම්පූර්ණ තිරය පටිගත කරන්න"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"තනි යෙදුමක් පටිගත කරන්න"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ඔබ පටිගත කරන අතරේ, Android හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ඔබ යෙදුමක් පටිගත කරන අතරේ, Android හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"පටිගත කිරීම අරඹන්න"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"ඕඩියෝ පටිගත කරන්න"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"උපාංග ඕඩියෝ"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"සංගීතය, ඇමතුම් සහ නාද රිද්ම වැනි ඔබේ උපාංගය වෙතින් ශබ්ද"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"මෙම ශ්රිතය සපයන සේවාවට පටිගත කරන හෝ විකාශ කරන අතරතුර ඔබේ තිරයේ දිස් වන හෝ ඔබේ උපාංගයෙන් වාදනය කරන සියලු තොරතුරු වෙත ප්රවේශය ලැබෙනු ඇත. මෙහි මුරපද, ගෙවීම් විස්තර, ඡායාරූප, පණිවිඩ සහ ඔබ වාදනය කරන ඕඩියෝ යනාදි තොරතුරු ඇතුළත් වේ."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"පටිගත කිරීම හෝ විකාශය කිරීම ආරම්භ කරන්නද?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> සමග පටිගත කිරීම හෝ විකාශය කිරීම ආරම්භ කරන්නද?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට බෙදා ගැනීමට හෝ පටිගත කිරීමට ඉඩ දෙන්න ද?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"සම්පූර්ණ තිරය"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"තනි යෙදුමක්"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශනය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ඉදිරියට යන්න"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"යෙදුමක් බෙදා ගන්න හෝ පටිගත කරන්න"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"කළමනාකරණය කරන්න"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ඉතිහාසය"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index dcebddb4d999..c1a83db6752f 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Njoftim i vazhdueshëm për një seancë regjistrimi të ekranit"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Të niset regjistrimi?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Gjatë regjistrimit, sistemi Android mund të regjistrojë çdo informacion delikat që është i dukshëm në ekranin tënd ose që luhet në pajisje. Kjo përfshin fjalëkalimet, informacionin e pagesave, fotografitë, mesazhet dhe audion."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Regjistro të gjithë ekranin"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Regjistro vetëm një aplikacion"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Gjatë regjistrimit, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Gjatë regjistrimit të një aplikacioni, Android ka qasje te çdo gjë e dukshme ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Nis regjistrimin"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Regjistro audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audioja e pajisjes"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Tingulli nga pajisja, si muzika, telefonatat dhe tonet e ziles"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Shërbimi që e ofron këtë funksion do të ketë qasje te të gjitha informacionet që janë të dukshme në ekran ose që luhen nga pajisja jote gjatë regjistrimit ose transmetimit. Kjo përfshin informacione, si p.sh.: fjalëkalimet, detajet e pagesave, fotografitë, mesazhet dhe audion që luan ti."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Do të fillosh regjistrimin ose transmetimin?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Fillo regjistrimin ose transmetimin me <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Të lejohet <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> të shpërndajë ose regjistrojë?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ekran i plotë"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Vetëm një aplikacion"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Gjatë shpërndarjes, regjistrimit ose transmetimit, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Gjatë shpërndarjes, regjistrimit ose transmetimit të një aplikacioni, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Vazhdo"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shpërndaj ose regjistro një aplikacion"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Menaxho"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historiku"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 77f0d30b412d..1778b01b2891 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Avisering om att skärminspelning pågår"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Vill du starta inspelningen?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"När du spelar in kan Android-systemet registrera alla känsliga uppgifter som visas på skärmen eller spelas upp på enheten. Detta omfattar lösenord, betalningsuppgifter, foton, meddelanden och ljud."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Spela in hela skärmen"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Spela in en enda app"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"När du spelar in har Android åtkomst till allt som visas på skärmen eller spelas upp på enheten. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"När du spelar in en app har Android åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Börja spela in"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Spela in ljud"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Ljud på enheten"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Ljud från enheten, till exempel musik, samtal och ringsignaler"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Den tjänst som tillhandahåller funktionen får åtkomst till all information som visas på skärmen eller spelas upp från enheten när du spelar in eller castar. Detta omfattar uppgifter som lösenord, betalningsinformation, foton, meddelanden och ljud som du spelar upp."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vill du börja spela in eller casta?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vill du börja spela in eller casta med <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vill du tillåta att <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> delar eller spelar in?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hela skärmen"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"En enda app"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"När du delar, spelar in eller castar har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas på skärmen eller spelas upp på enheten. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"När du delar, spelar in eller castar en app har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsätt"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dela eller spela in en app"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Hantera"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index c823f04238cb..dbb90b3c6761 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"திரை ரெக்கார்டிங் அமர்விற்கான தொடர் அறிவிப்பு"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"ரெக்கார்டிங்கைத் தொடங்கவா?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"ரெக்கார்டு செய்யும்போது, உங்கள் திரையில் தோன்றக்கூடிய அல்லது சாதனத்தில் பிளே ஆகக்கூடிய பாதுகாக்கப்பட வேண்டிய தகவலை Android சிஸ்டம் படமெடுக்க முடியும். கடவுச்சொற்கள், பேமெண்ட் தகவல், படங்கள், மெசேஜ்கள், ஆடியோ ஆகியவை இதில் அடங்கும்."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"முழு திரையை ரெக்கார்டு செய்தல்"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ஓர் ஆப்ஸை ரெக்கார்டு செய்தல்"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"நீங்கள் ரெக்கார்டு செய்யும்போது அந்தச் சாதனத்தில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ஓர் ஆப்ஸை நீங்கள் ரெக்கார்டு செய்யும்போது அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"ரெக்கார்டிங்கைத் தொடங்கு"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"ஆடியோவை ரெக்கார்டு செய்"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"சாதன ஆடியோ"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"இசை, அழைப்புகள், ரிங்டோன்கள் போன்ற உங்கள் சாதனத்திலிருந்து வரும் ஒலி"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"இந்தச் செயல்பாட்டை வழங்கும் சேவையானது உங்கள் திரையில் தெரியும் தகவல்கள், ரெக்கார்டு செய்யும்போதோ அனுப்பும்போதோ உங்கள் சாதனத்திலிருந்து பிளே ஆகும் அனைத்துத் தகவல்கள் ஆகியவற்றுக்கான அணுகலைக் கொண்டிருக்கும். கடவுச்சொற்கள், பேமெண்ட் தொடர்பான தகவல்கள், படங்கள், மெசேஜ்கள், நீங்கள் பிளே செய்யும் ஆடியோ போன்ற அனைத்துத் தகவல்களும் இதில் அடங்கும்."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ரெக்கார்டிங் செய்யவோ அனுப்புவோ தொடங்கவா?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> மூலம் ரெக்கார்டிங் செய்யவோ அனுப்புவதற்கோ தொடங்கிவீட்டீர்களா?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"பகிர அல்லது ரெக்கார்டு செய்ய <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸை அனுமதிக்கலாமா?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"திரை முழுவதும்"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ஓர் ஆப்ஸ்"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ உங்கள் சாதனத்தில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ஓர் ஆப்ஸை நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"தொடர்க"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ஆப்ஸைப் பகிர்தல் அல்லது ரெக்கார்டு செய்தல்"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"நிர்வகி"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"இதுவரை வந்த அறிவிப்புகள்"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index e911bb2fbb16..a238f3f64300 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekran kaydı oturumu için devam eden bildirim"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Kayıt başlatılsın mı?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Kayıt sırasında Android Sistemi, ekranınızda görünen veya cihazınızda oynatılan hassas bilgileri yakalayabilir. Buna şifreler, ödeme bilgileri, fotoğraflar, mesajlar ve sesler dahildir."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Tüm ekranı kaydedin"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Tek bir uygulamayı kaydedin"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Kayıt özelliğini kullandığınızda Android, ekranınızda görünen veya cihazınızda oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Bir uygulamayı kaydetme özelliğini kullandığınızda Android, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Kaydı başlat"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Ses kaydet"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Cihaz sesi"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Müzik, aramalar, zil sesleri gibi cihazınızdan sesler"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu işlevi sağlayan hizmet, ekranınızda görünen veya kayıt ya da yayın sırasında cihazınızdan oynatılan tüm bilgilere erişecektir. Bu bilgiler arasında şifreler, ödeme detayları, fotoğraflar, mesajlar ve çaldığınız sesler gibi bilgiler yer alır."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Kayıt veya yayınlama başlatılsın mı?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ile kayıt veya yayınlama başlatılsın mı?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> uygulamasının paylaşmasına veya kaydetmesine izin verilsin mi?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tüm ekran"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Tek bir uygulama"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Paylaşım, kayıt ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, ekranınızda görünen veya cihazınızda oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Bir uygulamayı paylaşma, kaydetme ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Devam"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Uygulamayı paylaşın veya kaydedin"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Yönet"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Geçmiş"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 011c8bc6e901..d3f392ae7a26 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"Сповіщення про сеанс запису екрана"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Почати запис?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"Під час запису система Android може фіксувати будь-яку конфіденційну інформацію, яка з\'являється на екрані або відтворюється на пристрої, зокрема паролі, платіжну інформацію, фотографії, повідомлення та звуки."</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записувати весь екран"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записувати окремий додаток"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Коли ви записуєте вміст екрана, ОС Android отримує доступ до всього, що відображається на ньому або відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Коли ви записуєте додаток, ОС Android отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"Почати запис"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Записувати звук"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук із пристрою"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук із пристрою, зокрема музика, виклики та сигнали дзвінка"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Сервіс, що надає цю функцію, матиме доступ до всієї інформації, яка з\'являється на екрані або відтворюється на пристрої під час запису чи трансляції, зокрема до паролів, інформації про платежі, фотографій, повідомлень і аудіофайлів."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Почати запис або трансляцію?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Почати запис або трансляцію за допомогою додатка <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Дозволити додатку <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> показувати або записувати?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Увесь екран"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Окремий додаток"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Коли ви показуєте, записуєте або транслюєте екран, додаток <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається на екрані чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Коли ви показуєте, записуєте або транслюєте додаток, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продовжити"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Показувати або записувати додаток"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Керувати"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Історія"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 7d0df6c3e3d3..1e71b14ac717 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"اسکرین ریکارڈ سیشن کیلئے جاری اطلاع"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"ریکارڈنگ شروع کریں؟"</string> <string name="screenrecord_description" msgid="1123231719680353736">"ریکارڈ کرنے کے دوران، Android سسٹم آپ کی اسکرین پر نظر آنے والی یا آپ کے آلہ پر چلنے والی کسی بھی حساس معلومات کو کیپچر کر سکتا ہے۔ اس میں پاس ورڈز، ادائیگی کی معلومات، تصاویر، پیغامات اور آڈیو شامل ہیں۔"</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"پوری اسکرین کو ریکارڈ کریں"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"واحد ایپ کو ریکارڈ کریں"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"جب آپ ریکارڈنگ کر رہے ہوتے ہیں تو Android کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"جب آپ کسی ایپ کو ریکارڈ کر رہے ہوتے ہیں تو Android کو اس ایپ پر دکھائی گئی یا چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"ریکارڈنگ شروع کریں"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"آڈیو ریکارڈ کریں"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"آلہ کا آڈیو"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"آپ کے آلے سے آواز، جیسے موسیقی، کالز اور رِنگ ٹونز"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"یہ فنکشن فراہم کرنے والی سروس کو اس تمام معلومات تک رسائی حاصل ہوگی جو آپ کی اسکرین پر نظر آتی ہے یا ریکارڈنگ یا کاسٹنگ کے دوران آپ کے آلے سے چلائی جاتی ہے۔ اس میں پاس ورڈز، ادائیگی کی تفصیلات، تصاویر، پیغامات اور وہ آڈیو جو آپ چلاتے ہیں جیسی معلومات شامل ہے۔"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ریکارڈنگ یا کاسٹنگ شروع کریں؟"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کے ذریعے ریکارڈنگ یا کاسٹنگ شروع کریں؟"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو اشتراک یا ریکارڈ کرنے کی اجازت دیں؟"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"پوری اسکرین"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"واحد ایپ"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی گئی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"جاری رکھیں"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ایپ کا اشتراک یا ریکارڈ کریں"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"نظم کریں"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"سرگزشت"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index d881fa20644c..87821bffc087 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -94,16 +94,11 @@ <string name="screenrecord_channel_description" msgid="4147077128486138351">"持续显示屏幕录制会话通知"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"要开始录制吗?"</string> <string name="screenrecord_description" msgid="1123231719680353736">"在录制内容时,Android 系统可以捕捉到您屏幕上显示或设备中播放的敏感信息,其中包括密码、付款信息、照片、消息和音频。"</string> - <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) --> - <skip /> - <!-- no translation found for screenrecord_option_single_app (5954863081500035825) --> - <skip /> - <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) --> - <skip /> - <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) --> - <skip /> - <!-- no translation found for screenrecord_start_recording (348286842544768740) --> - <skip /> + <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"录制整个屏幕"</string> + <string name="screenrecord_option_single_app" msgid="5954863081500035825">"录制单个应用"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"在您进行录制时,Android 可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"在您录制某个应用时,Android 可以访问此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string> + <string name="screenrecord_start_recording" msgid="348286842544768740">"开始录制"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"录制音频"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"设备音频"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"设备发出的声音,例如音乐、通话和铃声"</string> @@ -371,20 +366,13 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"在录制或投放内容时,提供此功能的服务将可获取您屏幕上显示或设备中播放的所有信息,其中包括密码、付款明细、照片、消息以及您播放的音频等信息。"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要开始录制或投放内容吗?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"要开始使用<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>录制或投放内容吗?"</string> - <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) --> - <skip /> - <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) --> - <skip /> - <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) --> - <skip /> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允许 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或录制吗?"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整个屏幕"</string> + <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"单个应用"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问通过此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string> + <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"继续"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或录制应用"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"历史记录"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index d934786b4551..c7036192b418 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -96,8 +96,8 @@ <string name="screenrecord_description" msgid="1123231719680353736">"錄影時,Android 系統可擷取螢幕上顯示或裝置播放的任何敏感資料,包括密碼、付款資料、相片、訊息和音訊。"</string> <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"錄製整個螢幕畫面"</string> <string name="screenrecord_option_single_app" msgid="5954863081500035825">"錄製單一應用程式"</string> - <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"進行錄製時,Android 可以存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string> - <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"錄製應用程式時,Android 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string> + <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"進行錄製時,Android 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string> + <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"錄製應用程式時,Android 可存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string> <string name="screenrecord_start_recording" msgid="348286842544768740">"開始錄製"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"錄音"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"裝置音訊"</string> @@ -366,11 +366,11 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"在錄影或投放時,此功能的服務供應商可以存取螢幕顯示或裝置播放的任何資料,當中包括密碼、付款詳情、相片、訊息和播放的語音等。"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要開始錄影或投放嗎?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"要使用「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」開始錄影或投放嗎?"</string> - <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允許 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或錄製?"</string> + <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允許 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或錄製嗎?"</string> <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整個螢幕畫面"</string> <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"單一應用程式"</string> - <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string> - <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放應用程式時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string> <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string> <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string> diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml index c67ac8d34aa6..8221d78fbfd7 100644 --- a/packages/SystemUI/res/values/bools.xml +++ b/packages/SystemUI/res/values/bools.xml @@ -18,6 +18,13 @@ <resources> <!-- Whether to show the user switcher in quick settings when only a single user is present. --> <bool name="qs_show_user_switcher_for_single_user">false</bool> + <!-- Whether to show a custom biometric prompt size--> <bool name="use_custom_bp_size">false</bool> + + <!-- Whether to enable clipping on Quick Settings --> + <bool name="qs_enable_clipping">true</bool> + + <!-- Whether to enable transparent background for notification scrims --> + <bool name="notification_scrim_transparent">false</bool> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 97cc45c9169c..93926ef9e780 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -519,7 +519,7 @@ <dimen name="qs_tile_margin_horizontal">8dp</dimen> <dimen name="qs_tile_margin_vertical">@dimen/qs_tile_margin_horizontal</dimen> <dimen name="qs_tile_margin_top_bottom">4dp</dimen> - <dimen name="qs_brightness_margin_top">8dp</dimen> + <dimen name="qs_brightness_margin_top">12dp</dimen> <dimen name="qs_brightness_margin_bottom">16dp</dimen> <dimen name="qqs_layout_margin_top">16dp</dimen> <dimen name="qqs_layout_padding_bottom">24dp</dimen> @@ -572,6 +572,7 @@ <dimen name="qs_header_row_min_height">48dp</dimen> <dimen name="qs_header_non_clickable_element_height">24dp</dimen> + <dimen name="new_qs_header_non_clickable_element_height">20dp</dimen> <dimen name="qs_footer_padding">20dp</dimen> <dimen name="qs_security_footer_height">88dp</dimen> diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml index 3164ed1e6751..e30d4415a0c4 100644 --- a/packages/SystemUI/res/values/integers.xml +++ b/packages/SystemUI/res/values/integers.xml @@ -28,4 +28,11 @@ <!-- The time it takes for the over scroll release animation to complete, in milli seconds. --> <integer name="lockscreen_shade_over_scroll_release_duration">0</integer> + + <!-- Values for transition of QS Headers --> + <integer name="fade_out_complete_frame">14</integer> + <integer name="fade_in_start_frame">58</integer> + <!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at + fade_out_complete_frame --> + <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 887df4ade62a..e76887babc50 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -128,11 +128,10 @@ <!-- This is hard coded to be sans-serif-condensed to match the icons --> <style name="TextAppearance.QS.Status"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textSize">14sp</item> <item name="android:letterSpacing">0.01</item> - <item name="android:lineHeight">20sp</item> </style> <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status"> @@ -143,12 +142,10 @@ <style name="TextAppearance.QS.Status.Carriers" /> <style name="TextAppearance.QS.Status.Carriers.NoCarrierText"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> <item name="android:textColor">?android:attr/textColorSecondary</item> </style> <style name="TextAppearance.QS.Status.Build"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> <item name="android:textColor">?android:attr/textColorSecondary</item> </style> diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml index f3866c08cbfc..de855e275f5f 100644 --- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml +++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml @@ -27,67 +27,60 @@ <KeyPosition app:keyPositionType="deltaRelative" app:percentX="0" - app:percentY="0" - app:framePosition="49" + app:percentY="@dimen/percent_displacement_at_fade_out" + app:framePosition="@integer/fade_out_complete_frame" app:sizePercent="0" app:curveFit="linear" app:motionTarget="@id/date" /> <KeyPosition app:keyPositionType="deltaRelative" app:percentX="1" - app:percentY="0.51" + app:percentY="0.5" app:sizePercent="1" - app:framePosition="51" + app:framePosition="50" app:curveFit="linear" app:motionTarget="@id/date" /> <KeyAttribute app:motionTarget="@id/date" - app:framePosition="30" + app:framePosition="14" android:alpha="0" /> <KeyAttribute app:motionTarget="@id/date" - app:framePosition="70" + app:framePosition="@integer/fade_in_start_frame" android:alpha="0" /> <KeyPosition - app:keyPositionType="pathRelative" - app:percentX="0" - app:percentY="0" - app:framePosition="0" - app:curveFit="linear" - app:motionTarget="@id/statusIcons" /> - <KeyPosition - app:keyPositionType="pathRelative" + app:keyPositionType="deltaRelative" app:percentX="0" - app:percentY="0" - app:framePosition="50" + app:percentY="@dimen/percent_displacement_at_fade_out" + app:framePosition="@integer/fade_out_complete_frame" app:sizePercent="0" app:curveFit="linear" app:motionTarget="@id/statusIcons" /> <KeyPosition app:keyPositionType="deltaRelative" app:percentX="1" - app:percentY="0.51" - app:framePosition="51" + app:percentY="0.5" + app:framePosition="50" app:sizePercent="1" app:curveFit="linear" app:motionTarget="@id/statusIcons" /> <KeyAttribute app:motionTarget="@id/statusIcons" - app:framePosition="30" + app:framePosition="@integer/fade_out_complete_frame" android:alpha="0" /> <KeyAttribute app:motionTarget="@id/statusIcons" - app:framePosition="70" + app:framePosition="@integer/fade_in_start_frame" android:alpha="0" /> <KeyPosition app:keyPositionType="deltaRelative" app:percentX="0" - app:percentY="0" - app:framePosition="50" + app:percentY="@dimen/percent_displacement_at_fade_out" + app:framePosition="@integer/fade_out_complete_frame" app:percentWidth="1" app:percentHeight="1" app:curveFit="linear" @@ -95,27 +88,27 @@ <KeyPosition app:keyPositionType="deltaRelative" app:percentX="1" - app:percentY="0.51" - app:framePosition="51" + app:percentY="0.5" + app:framePosition="50" app:percentWidth="1" app:percentHeight="1" app:curveFit="linear" app:motionTarget="@id/batteryRemainingIcon" /> <KeyAttribute app:motionTarget="@id/batteryRemainingIcon" - app:framePosition="30" + app:framePosition="@integer/fade_out_complete_frame" android:alpha="0" /> <KeyAttribute app:motionTarget="@id/batteryRemainingIcon" - app:framePosition="70" + app:framePosition="@integer/fade_in_start_frame" android:alpha="0" /> <KeyPosition app:motionTarget="@id/carrier_group" app:percentX="1" - app:percentY="0.51" - app:framePosition="51" + app:percentY="0.5" + app:framePosition="50" app:percentWidth="1" app:percentHeight="1" app:curveFit="linear" @@ -126,7 +119,7 @@ android:alpha="0" /> <KeyAttribute app:motionTarget="@id/carrier_group" - app:framePosition="70" + app:framePosition="@integer/fade_in_start_frame" android:alpha="0" /> </KeyFrameSet> </Transition> diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml index a82684d0358b..88b4f43b440b 100644 --- a/packages/SystemUI/res/xml/qqs_header.xml +++ b/packages/SystemUI/res/xml/qqs_header.xml @@ -43,7 +43,8 @@ android:id="@+id/date"> <Layout android:layout_width="0dp" - android:layout_height="@dimen/qs_header_non_clickable_element_height" + android:layout_height="@dimen/new_qs_header_non_clickable_element_height" + android:layout_marginStart="8dp" app:layout_constrainedWidth="true" app:layout_constraintStart_toEndOf="@id/clock" app:layout_constraintEnd_toStartOf="@id/barrier" @@ -57,8 +58,8 @@ android:id="@+id/statusIcons"> <Layout android:layout_width="0dp" - android:layout_height="@dimen/qs_header_non_clickable_element_height" - app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height" + android:layout_height="@dimen/new_qs_header_non_clickable_element_height" + app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height" app:layout_constraintStart_toEndOf="@id/date" app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" app:layout_constraintTop_toTopOf="parent" @@ -71,9 +72,9 @@ android:id="@+id/batteryRemainingIcon"> <Layout android:layout_width="wrap_content" - android:layout_height="@dimen/qs_header_non_clickable_element_height" + android:layout_height="@dimen/new_qs_header_non_clickable_element_height" app:layout_constrainedWidth="true" - app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height" + app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height" app:layout_constraintStart_toEndOf="@id/statusIcons" app:layout_constraintEnd_toEndOf="@id/end_guide" app:layout_constraintTop_toTopOf="parent" diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml index f39e6bd65b86..d8a4e7752960 100644 --- a/packages/SystemUI/res/xml/qs_header_new.xml +++ b/packages/SystemUI/res/xml/qs_header_new.xml @@ -40,13 +40,13 @@ android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/privacy_container" - app:layout_constraintBottom_toTopOf="@id/date" + app:layout_constraintBottom_toBottomOf="@id/carrier_group" app:layout_constraintEnd_toStartOf="@id/carrier_group" app:layout_constraintHorizontal_bias="0" /> <Transform - android:scaleX="2.4" - android:scaleY="2.4" + android:scaleX="2.57" + android:scaleY="2.57" /> </Constraint> @@ -54,11 +54,11 @@ android:id="@+id/date"> <Layout android:layout_width="0dp" - android:layout_height="@dimen/qs_header_non_clickable_element_height" + android:layout_height="@dimen/new_qs_header_non_clickable_element_height" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/space" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/clock" + app:layout_constraintTop_toBottomOf="@id/carrier_group" app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_chainStyle="spread_inside" /> @@ -87,7 +87,7 @@ android:id="@+id/statusIcons"> <Layout android:layout_width="0dp" - android:layout_height="@dimen/qs_header_non_clickable_element_height" + android:layout_height="@dimen/new_qs_header_non_clickable_element_height" app:layout_constrainedWidth="true" app:layout_constraintStart_toEndOf="@id/space" app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" @@ -101,8 +101,8 @@ android:id="@+id/batteryRemainingIcon"> <Layout android:layout_width="wrap_content" - android:layout_height="@dimen/qs_header_non_clickable_element_height" - app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height" + android:layout_height="@dimen/new_qs_header_non_clickable_element_height" + app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height" app:layout_constraintStart_toEndOf="@id/statusIcons" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/date" diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt index 2e391c7aacbe..49cc48321d77 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt @@ -19,6 +19,7 @@ package com.android.systemui.testing.screenshot import android.app.Activity import android.graphics.Color import android.view.View +import android.view.Window import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat @@ -51,13 +52,14 @@ class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestR /** * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in - * the context of [emulationSpec]. + * the context of [emulationSpec]. Window must be specified to capture views that render + * hardware buffers. */ - fun screenshotTest(goldenIdentifier: String, view: View) { + fun screenshotTest(goldenIdentifier: String, view: View, window: Window? = null) { view.removeElevationRecursively() ScreenshotRuleAsserter.Builder(screenshotRule) - .setScreenshotProvider { view.toBitmap() } + .setScreenshotProvider { view.toBitmap(window) } .withMatcher(matcher) .build() .assertGoldenImage(goldenIdentifier) @@ -94,6 +96,6 @@ class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestR activity.currentFocus?.clearFocus() } - screenshotTest(goldenIdentifier, rootView) + screenshotTest(goldenIdentifier, rootView, activity.window) } } diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 18bd6b42386a..f59a3200976a 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -59,6 +59,9 @@ android_library { resource_dirs: [ "res", ], + optimize: { + proguard_flags_files: ["proguard.flags"], + }, java_version: "1.8", min_sdk_version: "current", plugins: ["dagger2-compiler"], diff --git a/packages/SystemUI/shared/proguard.flags b/packages/SystemUI/shared/proguard.flags new file mode 100644 index 000000000000..5eda04500190 --- /dev/null +++ b/packages/SystemUI/shared/proguard.flags @@ -0,0 +1,4 @@ +# Retain signatures of TypeToken and its subclasses for gson usage in ClockRegistry +-keepattributes Signature +-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken +-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt index d7a0b473ead6..3efdc5acb00c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt @@ -17,6 +17,7 @@ package com.android.systemui.shared.animation import android.graphics.Point import android.view.Surface +import android.view.Surface.Rotation import android.view.View import android.view.WindowManager import com.android.systemui.unfold.UnfoldTransitionProgressProvider @@ -58,14 +59,14 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( * Updates display properties in order to calculate the initial position for the views * Must be called before [registerViewForAnimation] */ - fun updateDisplayProperties() { + @JvmOverloads + fun updateDisplayProperties(@Rotation rotation: Int = windowManager.defaultDisplay.rotation) { windowManager.defaultDisplay.getSize(screenSize) // Simple implementation to get current fold orientation, // this might not be correct on all devices // TODO: use JetPack WindowManager library to get the fold orientation - isVerticalFold = windowManager.defaultDisplay.rotation == Surface.ROTATION_0 || - windowManager.defaultDisplay.rotation == Surface.ROTATION_180 + isVerticalFold = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 } /** diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt index f03fee4b0c2d..e3c21cca2263 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -22,6 +22,7 @@ import android.os.UserHandle import android.provider.Settings import android.util.Log import com.android.systemui.dagger.qualifiers.Main +import com.android.internal.annotations.Keep import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockId import com.android.systemui.plugins.ClockMetadata @@ -201,6 +202,7 @@ open class ClockRegistry( val provider: ClockProvider ) + @Keep private data class ClockSetting( val clockId: ClockId, val _applied_timestamp: Long? diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt index dd2e55d4e7d7..cd4b9994ccca 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.shared.regionsampling +import android.graphics.Color import android.graphics.Rect import android.view.View import androidx.annotation.VisibleForTesting @@ -33,18 +34,19 @@ open class RegionSamplingInstance( regionSamplingEnabled: Boolean, updateFun: UpdateColorCallback ) { - private var isDark = RegionDarkness.DEFAULT + 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 /** * Interface for method to be passed into RegionSamplingHelper */ @FunctionalInterface interface UpdateColorCallback { /** - * Method to update the text colors after clock darkness changed. + * Method to update the foreground colors after clock darkness changed. */ fun updateColors() } @@ -59,6 +61,30 @@ open class RegionSamplingInstance( return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor) } + /** + * Sets the colors to be used for Dark and Light Foreground. + * + * @param lightColor The color used for Light Foreground. + * @param darkColor The color used for Dark Foreground. + */ + fun setForegroundColors(lightColor: Int, darkColor: Int) { + lightForegroundColor = lightColor + darkForegroundColor = darkColor + } + + /** + * Determines which foreground color to use based on region darkness. + * + * @return the determined foreground color + */ + fun currentForegroundColor(): Int{ + return if (regionDarkness.isDark) { + lightForegroundColor + } else { + darkForegroundColor + } + } + private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness { return if (isRegionDark) { RegionDarkness.DARK @@ -68,7 +94,7 @@ open class RegionSamplingInstance( } fun currentRegionDarkness(): RegionDarkness { - return isDark + return regionDarkness } /** @@ -97,7 +123,7 @@ open class RegionSamplingInstance( regionSampler = createRegionSamplingHelper(sampledView, object : SamplingCallback { override fun onRegionDarknessChanged(isRegionDark: Boolean) { - isDark = convertToClockDarkness(isRegionDark) + regionDarkness = convertToClockDarkness(isRegionDark) updateFun.updateColors() } /** diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java index 5d6598d63a1b..8a2509610310 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java @@ -51,6 +51,8 @@ public final class InteractionJankMonitorWrapper { InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER; public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; + public static final int CUJ_RECENTS_SCROLLING = + InteractionJankMonitor.CUJ_RECENTS_SCROLLING; @IntDef({ CUJ_APP_LAUNCH_FROM_RECENTS, @@ -59,7 +61,8 @@ public final class InteractionJankMonitorWrapper { CUJ_APP_CLOSE_TO_PIP, CUJ_QUICK_SWITCH, CUJ_APP_LAUNCH_FROM_WIDGET, - CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION + CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION, + CUJ_RECENTS_SCROLLING }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java index 7c3b5fc52f0a..2d6bef5a6788 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -60,7 +60,7 @@ public class RemoteAnimationTargetCompat { public static final int ACTIVITY_TYPE_ASSISTANT = WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; public final int activityType; - public int taskId; + public final int taskId; public final SurfaceControl leash; public final boolean isTranslucent; public final Rect clipRect; @@ -72,7 +72,7 @@ public class RemoteAnimationTargetCompat { public final Rect startScreenSpaceBounds; public final boolean isNotInRecents; public final Rect contentInsets; - public ActivityManager.RunningTaskInfo taskInfo; + public final ActivityManager.RunningTaskInfo taskInfo; public final boolean allowEnterPip; public final int rotationChange; public final int windowType; @@ -102,7 +102,7 @@ public class RemoteAnimationTargetCompat { activityType = app.windowConfiguration.getActivityType(); taskInfo = app.taskInfo; allowEnterPip = app.allowEnterPip; - rotationChange = 0; + rotationChange = app.rotationChange; mStartLeash = app.startLeash; windowType = app.windowType; @@ -131,6 +131,7 @@ public class RemoteAnimationTargetCompat { isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType ); target.setWillShowImeOnTarget(willShowImeOnTarget); + target.setRotationChange(rotationChange); return target; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java deleted file mode 100644 index 30c062b66da9..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java +++ /dev/null @@ -1,380 +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.shared.system; - -import android.graphics.HardwareRenderer; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Message; -import android.os.Trace; -import android.view.SurfaceControl; -import android.view.SurfaceControl.Transaction; -import android.view.View; -import android.view.ViewRootImpl; - -import java.util.function.Consumer; - -/** - * Helper class to apply surface transactions in sync with RenderThread. - * - * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't - * currently reference that class from the shared lib as it is hidden. - */ -public class SyncRtSurfaceTransactionApplierCompat { - - public static final int FLAG_ALL = 0xffffffff; - public static final int FLAG_ALPHA = 1; - public static final int FLAG_MATRIX = 1 << 1; - public static final int FLAG_WINDOW_CROP = 1 << 2; - public static final int FLAG_LAYER = 1 << 3; - public static final int FLAG_CORNER_RADIUS = 1 << 4; - public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5; - public static final int FLAG_VISIBILITY = 1 << 6; - public static final int FLAG_RELATIVE_LAYER = 1 << 7; - public static final int FLAG_SHADOW_RADIUS = 1 << 8; - - private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0; - - private final SurfaceControl mBarrierSurfaceControl; - private final ViewRootImpl mTargetViewRootImpl; - private final Handler mApplyHandler; - - private int mSequenceNumber = 0; - private int mPendingSequenceNumber = 0; - private Runnable mAfterApplyCallback; - - /** - * @param targetView The view in the surface that acts as synchronization anchor. - */ - public SyncRtSurfaceTransactionApplierCompat(View targetView) { - mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null; - mBarrierSurfaceControl = mTargetViewRootImpl != null - ? mTargetViewRootImpl.getSurfaceControl() : null; - - mApplyHandler = new Handler(new Callback() { - @Override - public boolean handleMessage(Message msg) { - if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) { - onApplyMessage(msg.arg1); - return true; - } - return false; - } - }); - } - - private void onApplyMessage(int seqNo) { - mSequenceNumber = seqNo; - if (mSequenceNumber == mPendingSequenceNumber && mAfterApplyCallback != null) { - Runnable r = mAfterApplyCallback; - mAfterApplyCallback = null; - r.run(); - } - } - - /** - * Schedules applying surface parameters on the next frame. - * - * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into - * this method to avoid synchronization issues. - */ - public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) { - if (mTargetViewRootImpl == null || mTargetViewRootImpl.getView() == null) { - return; - } - - mPendingSequenceNumber++; - final int toApplySeqNo = mPendingSequenceNumber; - mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() { - @Override - public void onFrameDraw(long frame) { - if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) { - Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) - .sendToTarget(); - return; - } - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Sync transaction frameNumber=" + frame); - Transaction t = new Transaction(); - for (int i = params.length - 1; i >= 0; i--) { - SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams = - params[i]; - surfaceParams.applyTo(t); - } - if (mTargetViewRootImpl != null) { - mTargetViewRootImpl.mergeWithNextTransaction(t, frame); - } else { - t.apply(); - } - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) - .sendToTarget(); - } - }); - - // Make sure a frame gets scheduled. - mTargetViewRootImpl.getView().invalidate(); - } - - /** - * Calls the runnable when any pending apply calls have completed - */ - public void addAfterApplyCallback(final Runnable afterApplyCallback) { - if (mSequenceNumber == mPendingSequenceNumber) { - afterApplyCallback.run(); - } else { - if (mAfterApplyCallback == null) { - mAfterApplyCallback = afterApplyCallback; - } else { - final Runnable oldCallback = mAfterApplyCallback; - mAfterApplyCallback = new Runnable() { - @Override - public void run() { - afterApplyCallback.run(); - oldCallback.run(); - } - }; - } - } - } - - public static void applyParams(TransactionCompat t, - SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) { - params.applyTo(t.mTransaction); - } - - /** - * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is - * attached if necessary. - */ - public static void create(final View targetView, - final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) { - if (targetView == null) { - // No target view, no applier - callback.accept(null); - } else if (targetView.getViewRootImpl() != null) { - // Already attached, we're good to go - callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView)); - } else { - // Haven't been attached before we can get the view root - targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - targetView.removeOnAttachStateChangeListener(this); - callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView)); - } - - @Override - public void onViewDetachedFromWindow(View v) { - // Do nothing - } - }); - } - } - - public static class SurfaceParams { - public static class Builder { - final SurfaceControl surface; - int flags; - float alpha; - float cornerRadius; - int backgroundBlurRadius; - Matrix matrix; - Rect windowCrop; - int layer; - SurfaceControl relativeTo; - int relativeLayer; - boolean visible; - float shadowRadius; - - /** - * @param surface The surface to modify. - */ - public Builder(SurfaceControl surface) { - this.surface = surface; - } - - /** - * @param alpha The alpha value to apply to the surface. - * @return this Builder - */ - public Builder withAlpha(float alpha) { - this.alpha = alpha; - flags |= FLAG_ALPHA; - return this; - } - - /** - * @param matrix The matrix to apply to the surface. - * @return this Builder - */ - public Builder withMatrix(Matrix matrix) { - this.matrix = new Matrix(matrix); - flags |= FLAG_MATRIX; - return this; - } - - /** - * @param windowCrop The window crop to apply to the surface. - * @return this Builder - */ - public Builder withWindowCrop(Rect windowCrop) { - this.windowCrop = new Rect(windowCrop); - flags |= FLAG_WINDOW_CROP; - return this; - } - - /** - * @param layer The layer to assign the surface. - * @return this Builder - */ - public Builder withLayer(int layer) { - this.layer = layer; - flags |= FLAG_LAYER; - return this; - } - - /** - * @param relativeTo The surface that's set relative layer to. - * @param relativeLayer The relative layer. - * @return this Builder - */ - public Builder withRelativeLayerTo(SurfaceControl relativeTo, int relativeLayer) { - this.relativeTo = relativeTo; - this.relativeLayer = relativeLayer; - flags |= FLAG_RELATIVE_LAYER; - return this; - } - - /** - * @param radius the Radius for rounded corners to apply to the surface. - * @return this Builder - */ - public Builder withCornerRadius(float radius) { - this.cornerRadius = radius; - flags |= FLAG_CORNER_RADIUS; - return this; - } - - /** - * @param radius the Radius for the shadows to apply to the surface. - * @return this Builder - */ - public Builder withShadowRadius(float radius) { - this.shadowRadius = radius; - flags |= FLAG_SHADOW_RADIUS; - return this; - } - - /** - * @param radius the Radius for blur to apply to the background surfaces. - * @return this Builder - */ - public Builder withBackgroundBlur(int radius) { - this.backgroundBlurRadius = radius; - flags |= FLAG_BACKGROUND_BLUR_RADIUS; - return this; - } - - /** - * @param visible The visibility to apply to the surface. - * @return this Builder - */ - public Builder withVisibility(boolean visible) { - this.visible = visible; - flags |= FLAG_VISIBILITY; - return this; - } - - /** - * @return a new SurfaceParams instance - */ - public SurfaceParams build() { - return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer, - relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible, - shadowRadius); - } - } - - private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix, - Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer, - float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) { - this.flags = flags; - this.surface = surface; - this.alpha = alpha; - this.matrix = matrix; - this.windowCrop = windowCrop; - this.layer = layer; - this.relativeTo = relativeTo; - this.relativeLayer = relativeLayer; - this.cornerRadius = cornerRadius; - this.backgroundBlurRadius = backgroundBlurRadius; - this.visible = visible; - this.shadowRadius = shadowRadius; - } - - private final int flags; - private final float[] mTmpValues = new float[9]; - - public final SurfaceControl surface; - public final float alpha; - public final float cornerRadius; - public final int backgroundBlurRadius; - public final Matrix matrix; - public final Rect windowCrop; - public final int layer; - public final SurfaceControl relativeTo; - public final int relativeLayer; - public final boolean visible; - public final float shadowRadius; - - public void applyTo(SurfaceControl.Transaction t) { - if ((flags & FLAG_MATRIX) != 0) { - t.setMatrix(surface, matrix, mTmpValues); - } - if ((flags & FLAG_WINDOW_CROP) != 0) { - t.setWindowCrop(surface, windowCrop); - } - if ((flags & FLAG_ALPHA) != 0) { - t.setAlpha(surface, alpha); - } - if ((flags & FLAG_LAYER) != 0) { - t.setLayer(surface, layer); - } - if ((flags & FLAG_CORNER_RADIUS) != 0) { - t.setCornerRadius(surface, cornerRadius); - } - if ((flags & FLAG_BACKGROUND_BLUR_RADIUS) != 0) { - t.setBackgroundBlurRadius(surface, backgroundBlurRadius); - } - if ((flags & FLAG_VISIBILITY) != 0) { - if (visible) { - t.show(surface); - } else { - t.hide(surface); - } - } - if ((flags & FLAG_RELATIVE_LAYER) != 0) { - t.setRelativeLayer(surface, relativeTo, relativeLayer); - } - if ((flags & FLAG_SHADOW_RADIUS) != 0) { - t.setShadowRadius(surface, shadowRadius); - } - } - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java deleted file mode 100644 index 43a882a5f6be..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java +++ /dev/null @@ -1,108 +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.shared.system; - -import android.graphics.Matrix; -import android.graphics.Rect; -import android.view.SurfaceControl; -import android.view.SurfaceControl.Transaction; - -public class TransactionCompat { - - final Transaction mTransaction; - - final float[] mTmpValues = new float[9]; - - public TransactionCompat() { - mTransaction = new Transaction(); - } - - public void apply() { - mTransaction.apply(); - } - - public TransactionCompat show(SurfaceControl surfaceControl) { - mTransaction.show(surfaceControl); - return this; - } - - public TransactionCompat hide(SurfaceControl surfaceControl) { - mTransaction.hide(surfaceControl); - return this; - } - - public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) { - mTransaction.setPosition(surfaceControl, x, y); - return this; - } - - public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) { - mTransaction.setBufferSize(surfaceControl, w, h); - return this; - } - - public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) { - mTransaction.setLayer(surfaceControl, z); - return this; - } - - public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) { - mTransaction.setAlpha(surfaceControl, alpha); - return this; - } - - public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) { - mTransaction.setOpaque(surfaceControl, opaque); - return this; - } - - public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx, - float dtdy, float dsdy) { - mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy); - return this; - } - - public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) { - mTransaction.setMatrix(surfaceControl, matrix, mTmpValues); - return this; - } - - public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) { - mTransaction.setWindowCrop(surfaceControl, crop); - return this; - } - - public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) { - mTransaction.setCornerRadius(surfaceControl, radius); - return this; - } - - public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) { - mTransaction.setBackgroundBlurRadius(surfaceControl, radius); - return this; - } - - public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) { - mTransaction.setColor(surfaceControl, color); - return this; - } - - public static void setRelativeLayer(Transaction t, SurfaceControl surfaceControl, - SurfaceControl relativeTo, int z) { - t.setRelativeLayer(surfaceControl, relativeTo, z); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt index ec938b219933..aca9907fec1b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt @@ -15,12 +15,11 @@ package com.android.systemui.unfold.util import android.content.Context -import android.os.RemoteException -import android.view.IRotationWatcher -import android.view.IWindowManager import android.view.Surface import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.updates.RotationChangeProvider +import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener /** * [UnfoldTransitionProgressProvider] that emits transition progress only when the display has @@ -29,27 +28,21 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr */ class NaturalRotationUnfoldProgressProvider( private val context: Context, - private val windowManagerInterface: IWindowManager, + private val rotationChangeProvider: RotationChangeProvider, unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider ) : UnfoldTransitionProgressProvider { private val scopedUnfoldTransitionProgressProvider = ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider) - private val rotationWatcher = RotationWatcher() private var isNaturalRotation: Boolean = false fun init() { - try { - windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId) - } catch (e: RemoteException) { - throw e.rethrowFromSystemServer() - } - - onRotationChanged(context.display.rotation) + rotationChangeProvider.addCallback(rotationListener) + rotationListener.onRotationChanged(context.display.rotation) } - private fun onRotationChanged(rotation: Int) { + private val rotationListener = RotationListener { rotation -> val isNewRotationNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 @@ -60,12 +53,7 @@ class NaturalRotationUnfoldProgressProvider( } override fun destroy() { - try { - windowManagerInterface.removeRotationWatcher(rotationWatcher) - } catch (e: RemoteException) { - e.rethrowFromSystemServer() - } - + rotationChangeProvider.removeCallback(rotationListener) scopedUnfoldTransitionProgressProvider.destroy() } @@ -76,10 +64,4 @@ class NaturalRotationUnfoldProgressProvider( override fun removeCallback(listener: TransitionProgressListener) { scopedUnfoldTransitionProgressProvider.removeCallback(listener) } - - private inner class RotationWatcher : IRotationWatcher.Stub() { - override fun onRotationChanged(rotation: Int) { - this@NaturalRotationUnfoldProgressProvider.onRotationChanged(rotation) - } - } } diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java deleted file mode 100644 index 6064be94bc0a..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.Rect; -import android.icu.text.NumberFormat; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; - -import com.android.settingslib.Utils; -import com.android.systemui.R; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shared.clocks.AnimatableClockView; -import com.android.systemui.shared.navigationbar.RegionSamplingHelper; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.util.ViewController; - -import java.io.PrintWriter; -import java.util.Locale; -import java.util.Objects; -import java.util.Optional; -import java.util.TimeZone; -import java.util.concurrent.Executor; - -import javax.inject.Inject; - -/** - * Controller for an AnimatableClockView on the keyguard. Instantiated by - * {@link KeyguardClockSwitchController}. - */ -public class AnimatableClockController extends ViewController<AnimatableClockView> { - private static final String TAG = "AnimatableClockCtrl"; - private static final int FORMAT_NUMBER = 1234567890; - - private final StatusBarStateController mStatusBarStateController; - private final BroadcastDispatcher mBroadcastDispatcher; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final BatteryController mBatteryController; - private final int mDozingColor = Color.WHITE; - private Optional<RegionSamplingHelper> mRegionSamplingHelper = Optional.empty(); - private Rect mSamplingBounds = new Rect(); - private int mLockScreenColor; - private final boolean mRegionSamplingEnabled; - - private boolean mIsDozing; - private boolean mIsCharging; - private float mDozeAmount; - boolean mKeyguardShowing; - private Locale mLocale; - - private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my")); - private final String mBurmeseNumerals; - private final float mBurmeseLineSpacing; - private final float mDefaultLineSpacing; - - @Inject - public AnimatableClockController( - AnimatableClockView view, - StatusBarStateController statusBarStateController, - BroadcastDispatcher broadcastDispatcher, - BatteryController batteryController, - KeyguardUpdateMonitor keyguardUpdateMonitor, - @Main Resources resources, - @Main Executor mainExecutor, - @Background Executor bgExecutor, - FeatureFlags featureFlags - ) { - super(view); - mStatusBarStateController = statusBarStateController; - mBroadcastDispatcher = broadcastDispatcher; - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mBatteryController = batteryController; - - mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER); - mBurmeseLineSpacing = resources.getFloat( - R.dimen.keyguard_clock_line_spacing_scale_burmese); - mDefaultLineSpacing = resources.getFloat( - R.dimen.keyguard_clock_line_spacing_scale); - - mRegionSamplingEnabled = featureFlags.isEnabled(Flags.REGION_SAMPLING); - if (!mRegionSamplingEnabled) { - return; - } - - mRegionSamplingHelper = Optional.of(new RegionSamplingHelper(mView, - new RegionSamplingHelper.SamplingCallback() { - @Override - public void onRegionDarknessChanged(boolean isRegionDark) { - if (isRegionDark) { - mLockScreenColor = Color.WHITE; - } else { - mLockScreenColor = Color.BLACK; - } - initColors(); - } - - @Override - public Rect getSampledRegion(View sampledView) { - mSamplingBounds = new Rect(sampledView.getLeft(), sampledView.getTop(), - sampledView.getRight(), sampledView.getBottom()); - return mSamplingBounds; - } - - @Override - public boolean isSamplingEnabled() { - return mRegionSamplingEnabled; - } - }, mainExecutor, bgExecutor) - ); - mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> { - regionSamplingHelper.setWindowVisible(true); - }); - } - - private void reset() { - mView.animateDoze(mIsDozing, false); - } - - private final BatteryController.BatteryStateChangeCallback mBatteryCallback = - new BatteryController.BatteryStateChangeCallback() { - @Override - public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { - if (mKeyguardShowing && !mIsCharging && charging) { - mView.animateCharge(mStatusBarStateController::isDozing); - } - mIsCharging = charging; - } - }; - - private final BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - updateLocale(); - } - }; - - private final StatusBarStateController.StateListener mStatusBarStateListener = - new StatusBarStateController.StateListener() { - @Override - public void onDozeAmountChanged(float linear, float eased) { - boolean noAnimation = (mDozeAmount == 0f && linear == 1f) - || (mDozeAmount == 1f && linear == 0f); - boolean isDozing = linear > mDozeAmount; - mDozeAmount = linear; - if (mIsDozing != isDozing) { - mIsDozing = isDozing; - mView.animateDoze(mIsDozing, !noAnimation); - } - } - }; - - private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = - new KeyguardUpdateMonitorCallback() { - @Override - public void onKeyguardVisibilityChanged(boolean showing) { - mKeyguardShowing = showing; - if (!mKeyguardShowing) { - // reset state (ie: after weight animations) - reset(); - } - } - - @Override - public void onTimeFormatChanged(String timeFormat) { - mView.refreshFormat(); - } - - @Override - public void onTimeZoneChanged(TimeZone timeZone) { - mView.onTimeZoneChanged(timeZone); - } - - @Override - public void onUserSwitchComplete(int userId) { - mView.refreshFormat(); - } - }; - - @Override - protected void onInit() { - mIsDozing = mStatusBarStateController.isDozing(); - } - - @Override - protected void onViewAttached() { - updateLocale(); - mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver, - new IntentFilter(Intent.ACTION_LOCALE_CHANGED)); - mDozeAmount = mStatusBarStateController.getDozeAmount(); - mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0; - mBatteryController.addCallback(mBatteryCallback); - mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); - - mStatusBarStateController.addCallback(mStatusBarStateListener); - - mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> { - regionSamplingHelper.start(mSamplingBounds); - }); - - mView.onTimeZoneChanged(TimeZone.getDefault()); - initColors(); - mView.animateDoze(mIsDozing, false); - } - - @Override - protected void onViewDetached() { - mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver); - mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); - mBatteryController.removeCallback(mBatteryCallback); - mStatusBarStateController.removeCallback(mStatusBarStateListener); - mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> { - regionSamplingHelper.stop(); - }); - } - - /** Animate the clock appearance */ - public void animateAppear() { - if (!mIsDozing) mView.animateAppearOnLockscreen(); - } - - /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to - * fully folded state and it goes to sleep (always on display screen) */ - public void animateFoldAppear() { - mView.animateFoldAppear(true); - } - - /** - * Updates the time for the view. - */ - public void refreshTime() { - mView.refreshTime(); - } - - /** - * Return locallly stored dozing state. - */ - @VisibleForTesting - public boolean isDozing() { - return mIsDozing; - } - - private void updateLocale() { - Locale currLocale = Locale.getDefault(); - if (!Objects.equals(currLocale, mLocale)) { - mLocale = currLocale; - NumberFormat nf = NumberFormat.getInstance(mLocale); - if (nf.format(FORMAT_NUMBER).equals(mBurmeseNumerals)) { - mView.setLineSpacingScale(mBurmeseLineSpacing); - } else { - mView.setLineSpacingScale(mDefaultLineSpacing); - } - mView.refreshFormat(); - } - } - - private void initColors() { - if (!mRegionSamplingEnabled) { - mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(), - com.android.systemui.R.attr.wallpaperTextColorAccent); - } - mView.setColors(mDozingColor, mLockScreenColor); - mView.animateDoze(mIsDozing, false); - } - - /** - * Dump information for debugging - */ - public void dump(@NonNull PrintWriter pw) { - pw.println(this); - mView.dump(pw); - mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> { - regionSamplingHelper.dump(pw); - }); - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt index 0075ddd73cd3..5277e40492e4 100644 --- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt +++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt @@ -16,19 +16,29 @@ package com.android.keyguard +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.animation.ObjectAnimator import android.content.Context import android.content.res.ColorStateList import android.content.res.TypedArray import android.graphics.Color import android.util.AttributeSet +import android.view.View import com.android.settingslib.Utils +import com.android.systemui.animation.Interpolators /** Displays security messages for the keyguard bouncer. */ -class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) : +open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) : KeyguardMessageArea(context, attrs) { private val DEFAULT_COLOR = -1 private var mDefaultColorState: ColorStateList? = null private var mNextMessageColorState: ColorStateList? = ColorStateList.valueOf(DEFAULT_COLOR) + private val animatorSet = AnimatorSet() + private var textAboutToShow: CharSequence? = null + protected open val SHOW_DURATION_MILLIS = 150L + protected open val HIDE_DURATION_MILLIS = 200L override fun updateTextColor() { var colorState = mDefaultColorState @@ -58,4 +68,46 @@ class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) : mDefaultColorState = Utils.getColorAttr(context, android.R.attr.textColorPrimary) super.reloadColor() } + + override fun setMessage(msg: CharSequence?) { + if (msg == textAboutToShow || msg == text) { + return + } + textAboutToShow = msg + + if (animatorSet.isRunning) { + animatorSet.cancel() + textAboutToShow = null + } + + val hideAnimator = + ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f).apply { + duration = HIDE_DURATION_MILLIS + interpolator = Interpolators.STANDARD_ACCELERATE + } + + hideAnimator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + super@BouncerKeyguardMessageArea.setMessage(msg) + } + } + ) + val showAnimator = + ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f).apply { + duration = SHOW_DURATION_MILLIS + interpolator = Interpolators.STANDARD_DECELERATE + } + + showAnimator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + textAboutToShow = null + } + } + ) + + animatorSet.playSequentially(hideAnimator, showAnimator) + animatorSet.start() + } } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 9151238499dc..910955a45f7b 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -23,12 +23,18 @@ import android.content.res.Resources import android.text.format.DateFormat import android.util.TypedValue import android.view.View +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags.REGION_SAMPLING +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ClockController -import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shared.regionsampling.RegionSamplingInstance import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback @@ -38,13 +44,20 @@ 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.launch /** * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController]. */ open class ClockEventController @Inject constructor( - private val statusBarStateController: StatusBarStateController, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val broadcastDispatcher: BroadcastDispatcher, private val batteryController: BatteryController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, @@ -53,7 +66,7 @@ open class ClockEventController @Inject constructor( private val context: Context, @Main private val mainExecutor: Executor, @Background private val bgExecutor: Executor, - private val featureFlags: FeatureFlags, + private val featureFlags: FeatureFlags ) { var clock: ClockController? = null set(value) { @@ -70,9 +83,9 @@ open class ClockEventController @Inject constructor( private var isCharging = false private var dozeAmount = 0f private var isKeyguardVisible = false - - private val regionSamplingEnabled = - featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING) + private var isRegistered = false + private var disposableHandle: DisposableHandle? = null + private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING) private fun updateColors() { if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) { @@ -165,15 +178,6 @@ open class ClockEventController @Inject constructor( } } - private val statusBarStateListener = object : StatusBarStateController.StateListener { - override fun onDozeAmountChanged(linear: Float, eased: Float) { - clock?.animations?.doze(linear) - - isDozing = linear > dozeAmount - dozeAmount = linear - } - } - private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { override fun onKeyguardVisibilityChanged(visible: Boolean) { isKeyguardVisible = visible @@ -195,13 +199,11 @@ open class ClockEventController @Inject constructor( } } - init { - isDozing = statusBarStateController.isDozing - } - - fun registerListeners() { - dozeAmount = statusBarStateController.dozeAmount - isDozing = statusBarStateController.isDozing || dozeAmount != 0f + fun registerListeners(parent: View) { + if (isRegistered) { + return + } + isRegistered = true broadcastDispatcher.registerReceiver( localeBroadcastReceiver, @@ -210,17 +212,28 @@ open class ClockEventController @Inject constructor( configurationController.addCallback(configListener) batteryController.addCallback(batteryCallback) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) - statusBarStateController.addCallback(statusBarStateListener) smallRegionSampler?.startRegionSampler() largeRegionSampler?.startRegionSampler() + disposableHandle = parent.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + listenForDozing(this) + listenForDozeAmount(this) + listenForDozeAmountTransition(this) + } + } } fun unregisterListeners() { + if (!isRegistered) { + return + } + isRegistered = false + + disposableHandle?.dispose() broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver) configurationController.removeCallback(configListener) batteryController.removeCallback(batteryCallback) keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) - statusBarStateController.removeCallback(statusBarStateListener) smallRegionSampler?.stopRegionSampler() largeRegionSampler?.stopRegionSampler() } @@ -235,8 +248,39 @@ open class ClockEventController @Inject constructor( largeRegionSampler?.dump(pw) } - companion object { - private val TAG = ClockEventController::class.simpleName - private const val FORMAT_NUMBER = 1234567890 + @VisibleForTesting + internal fun listenForDozeAmount(scope: CoroutineScope): Job { + return scope.launch { + keyguardInteractor.dozeAmount.collect { + dozeAmount = it + clock?.animations?.doze(dozeAmount) + } + } + } + + @VisibleForTesting + internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job { + return scope.launch { + keyguardTransitionInteractor.aodToLockscreenTransition.collect { + // Would eventually run this: + // dozeAmount = it.value + // clock?.animations?.doze(dozeAmount) + } + } + } + + @VisibleForTesting + internal fun listenForDozing(scope: CoroutineScope): Job { + return scope.launch { + combine ( + keyguardInteractor.dozeAmount, + keyguardInteractor.isDozing, + ) { localDozeAmount, localIsDozing -> + localDozeAmount > dozeAmount || localIsDozing + } + .collect { localIsDozing -> + isDozing = localIsDozing + } + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt index 692fe83ee2b8..e6a2bfa1af12 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt @@ -17,7 +17,6 @@ package com.android.keyguard import android.app.StatusBarManager.SESSION_KEYGUARD -import android.content.Context import android.hardware.biometrics.BiometricSourceType import com.android.internal.annotations.VisibleForTesting import com.android.internal.logging.UiEvent @@ -41,11 +40,10 @@ import javax.inject.Inject */ @SysUISingleton class KeyguardBiometricLockoutLogger @Inject constructor( - context: Context?, private val uiEventLogger: UiEventLogger, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val sessionTracker: SessionTracker -) : CoreStartable(context) { +) : CoreStartable { private var fingerprintLockedOut = false private var faceLockedOut = false private var encryptedOrLockdown = false @@ -169,4 +167,4 @@ class KeyguardBiometricLockoutLogger @Inject constructor( return strongAuthFlags and flagCheck != 0 } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index b450ec35db32..202134b699d7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -164,7 +164,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS protected void onViewAttached() { mClockRegistry.registerClockChangeListener(mClockChangedListener); setClock(mClockRegistry.createCurrentClock()); - mClockEventController.registerListeners(); + mClockEventController.registerListeners(mView); mKeyguardClockTopMargin = mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index f26b9057dc7c..73229c321079 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -152,6 +152,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> } public void startAppearAnimation() { + mMessageAreaController.setMessage(getInitialMessageResId()); mView.startAppearAnimation(); } @@ -169,6 +170,11 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> return view.indexOfChild(mView); } + /** Determines the message to show in the bouncer when it first appears. */ + protected int getInitialMessageResId() { + return 0; + } + /** Factory for a {@link KeyguardInputViewController}. */ public static class Factory { private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index c2802f7b6843..2bd3ca59b740 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -18,7 +18,6 @@ package com.android.keyguard; import android.content.res.ColorStateList; import android.content.res.Configuration; -import android.text.TextUtils; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -100,15 +99,6 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> mView.setMessage(resId); } - /** - * Set Text if KeyguardMessageArea is empty. - */ - public void setMessageIfEmpty(int resId) { - if (TextUtils.isEmpty(mView.getText())) { - setMessage(resId); - } - } - public void setNextMessageColor(ColorStateList colorState) { mView.setNextMessageColor(colorState); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 29e912fdab32..0025986c0e5c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -187,7 +187,7 @@ public class KeyguardPasswordViewController @Override void resetState() { mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); - mMessageAreaController.setMessage(""); + mMessageAreaController.setMessage(getInitialMessageResId()); final boolean wasDisabled = mPasswordEntry.isEnabled(); mView.setPasswordEntryEnabled(true); mView.setPasswordEntryInputEnabled(true); @@ -207,7 +207,6 @@ public class KeyguardPasswordViewController if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { showInput(); } - mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_password); } private void showInput() { @@ -324,4 +323,9 @@ public class KeyguardPasswordViewController //enabled input method subtype (The current IME should be LatinIME.) || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; } + + @Override + protected int getInitialMessageResId() { + return R.string.keyguard_enter_your_password; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 987164557a7a..1f0bd54f8e09 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -298,12 +298,6 @@ public class KeyguardPatternViewController } @Override - public void onResume(int reason) { - super.onResume(reason); - mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pattern); - } - - @Override public boolean needsInput() { return false; } @@ -361,7 +355,7 @@ public class KeyguardPatternViewController } private void displayDefaultSecurityMessage() { - mMessageAreaController.setMessage(""); + mMessageAreaController.setMessage(getInitialMessageResId()); } private void handleAttemptLockout(long elapsedRealtimeDeadline) { @@ -392,4 +386,9 @@ public class KeyguardPatternViewController }.start(); } + + @Override + protected int getInitialMessageResId() { + return R.string.keyguard_enter_your_pattern; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index 59a018ad51df..f7423ed12e68 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -127,7 +127,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB public void onResume(int reason) { super.onResume(reason); mPasswordEntry.requestFocus(); - mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 89fcc47caf57..7876f071fdf5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -76,20 +76,13 @@ public class KeyguardPinViewController } @Override - void resetState() { - super.resetState(); - mMessageAreaController.setMessage(""); - } - - @Override - public void startAppearAnimation() { - mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin); - super.startAppearAnimation(); - } - - @Override public boolean startDisappearAnimation(Runnable finishRunnable) { return mView.startDisappearAnimation( mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); } + + @Override + protected int getInitialMessageResId() { + return R.string.keyguard_enter_your_pin; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index c34db1532d6c..93ee151f26c5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -67,7 +67,6 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.view.WindowManager; -import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; @@ -318,7 +317,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager, - UserSwitcherController userSwitcherController) { + UserSwitcherController userSwitcherController, + UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback) { if (mCurrentMode == mode) return; Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to " + modeToString(mode)); @@ -330,7 +330,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { mViewMode = new OneHandedViewMode(); break; case MODE_USER_SWITCHER: - mViewMode = new UserSwitcherViewMode(); + mViewMode = new UserSwitcherViewMode(userSwitcherCallback); break; default: mViewMode = new DefaultViewMode(); @@ -864,6 +864,12 @@ public class KeyguardSecurityContainer extends ConstraintLayout { private UserSwitcherController.UserSwitchCallback mUserSwitchCallback = this::setupUserSwitcher; + private UserSwitcherCallback mUserSwitcherCallback; + + UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) { + mUserSwitcherCallback = userSwitcherCallback; + } + @Override public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @@ -1040,34 +1046,25 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } }; - if (adapter.getCount() < 2) { - // The drop down arrow is at index 1 - ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(0); - anchor.setClickable(false); - return; - } else { - ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(255); - } - anchor.setOnClickListener((v) -> { if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager); mPopup.setAnchorView(anchor); mPopup.setAdapter(adapter); - mPopup.setOnItemClickListener(new AdapterView.OnItemClickListener() { - public void onItemClick(AdapterView parent, View view, int pos, long id) { - if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; - if (!view.isEnabled()) return; - - // Subtract one for the header - UserRecord user = adapter.getItem(pos - 1); - if (!user.isCurrent) { - adapter.onUserListItemClicked(user); - } - mPopup.dismiss(); - mPopup = null; - } - }); + mPopup.setOnItemClickListener((parent, view, pos, id) -> { + if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; + if (!view.isEnabled()) return; + // Subtract one for the header + UserRecord user = adapter.getItem(pos - 1); + if (user.isManageUsers || user.isAddSupervisedUser) { + mUserSwitcherCallback.showUnlockToContinueMessage(); + } + if (!user.isCurrent) { + adapter.onUserListItemClicked(user); + } + mPopup.dismiss(); + mPopup = null; + }); mPopup.show(); }); } @@ -1122,6 +1119,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout { constraintSet.applyTo(mView); } } + + interface UserSwitcherCallback { + void showUnlockToContinueMessage(); + } } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index d448f40ed529..bcd1a1ee2696 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -620,7 +620,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mode = KeyguardSecurityContainer.MODE_ONE_HANDED; } - mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController); + mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController, + () -> showMessage(getContext().getString(R.string.keyguard_unlock_to_continue), + null)); } public void reportFailedUnlockAttempt(int userId, int timeoutMs) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt index 9eb2c118abac..c9128e5881cc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt @@ -109,12 +109,13 @@ class KeyguardSecurityViewTransition : Transition() { object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { runningSecurityShiftAnimator = null + if (shouldRestoreLayerType) { + v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null) + } } } ) - var finishedFadingOutNonSecurityView = false - runningSecurityShiftAnimator.addUpdateListener { animation: ValueAnimator -> val switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION val isFadingOut = animation.animatedFraction < switchPoint @@ -153,6 +154,13 @@ class KeyguardSecurityViewTransition : Transition() { startRect.right + currentTranslation, startRect.bottom ) + } else { + v.setLeftTopRightBottom( + startRect.left, + startRect.top, + startRect.right, + startRect.bottom + ) } } else { // And in again over the remaining (100-X)%. @@ -175,32 +183,13 @@ class KeyguardSecurityViewTransition : Transition() { endRect.right - translationRemaining, endRect.bottom ) - } - } - if (animation.animatedFraction == 1.0f && shouldRestoreLayerType) { - v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null) - } - - // For views that are not the security view flipper, we do not want to apply - // an x translation animation. Instead, we want to fade out, move to final position and - // then fade in. - if (v !is KeyguardSecurityViewFlipper) { - // Opacity goes close to 0 but does not fully get to 0. - if (opacity - 0.001f < 0f) { + } else { v.setLeftTopRightBottom( endRect.left, endRect.top, endRect.right, endRect.bottom ) - finishedFadingOutNonSecurityView = true - } else if (!finishedFadingOutNonSecurityView) { - v.setLeftTopRightBottom( - startRect.left, - startRect.top, - startRect.right, - startRect.bottom - ) } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 8792a211ad5b..46e187e041e4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1428,6 +1428,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + private void notifyNonStrongBiometricStateChanged(int userId) { + Assert.isMainThread(); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onNonStrongBiometricAllowedChanged(userId); + } + } + } + private void dispatchErrorMessage(CharSequence message) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1778,11 +1788,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { private final Consumer<Integer> mStrongAuthRequiredChangedCallback; + private final Consumer<Integer> mNonStrongBiometricAllowedChanged; public StrongAuthTracker(Context context, - Consumer<Integer> strongAuthRequiredChangedCallback) { + Consumer<Integer> strongAuthRequiredChangedCallback, + Consumer<Integer> nonStrongBiometricAllowedChanged) { super(context); mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback; + mNonStrongBiometricAllowedChanged = nonStrongBiometricAllowedChanged; } public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) { @@ -1800,6 +1813,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void onStrongAuthRequiredChanged(int userId) { mStrongAuthRequiredChangedCallback.accept(userId); } + + // TODO(b/247091681): Renaming the inappropriate onIsNonStrongBiometricAllowedChanged + // callback wording for Weak/Convenience idle timeout constraint that only allow + // Strong-Auth + @Override + public void onIsNonStrongBiometricAllowedChanged(int userId) { + mNonStrongBiometricAllowedChanged.accept(userId); + } } protected void handleStartedWakingUp() { @@ -1948,7 +1969,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mSubscriptionManager = subscriptionManager; mTelephonyListenerManager = telephonyListenerManager; mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); - mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged); + mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged, + this::notifyNonStrongBiometricStateChanged); mBackgroundExecutor = backgroundExecutor; mBroadcastDispatcher = broadcastDispatcher; mInteractionJankMonitor = interactionJankMonitor; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index bc5ab88b586b..c06e1dcf08c2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -291,4 +291,9 @@ public class KeyguardUpdateMonitorCallback { * Called when the notification shade is expanded or collapsed. */ public void onShadeExpandedChanged(boolean expanded) { } + + /** + * Called when the non-strong biometric state changed. + */ + public void onNonStrongBiometricAllowedChanged(int userId) { } } diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt index 2c2ab7b39161..6264ce7273f1 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt @@ -17,9 +17,9 @@ package com.android.keyguard.logging import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG import com.android.systemui.log.dagger.BiometricMessagesLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG import javax.inject.Inject /** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */ diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index 50012a589b5a..46f3d4e5f6aa 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -16,15 +16,15 @@ package com.android.keyguard.logging -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.VERBOSE -import com.android.systemui.log.LogLevel.WARNING -import com.android.systemui.log.MessageInitializer -import com.android.systemui.log.MessagePrinter import com.android.systemui.log.dagger.KeyguardLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.VERBOSE +import com.android.systemui.plugins.log.LogLevel.WARNING +import com.android.systemui.plugins.log.MessageInitializer +import com.android.systemui.plugins.log.MessagePrinter import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 2eee95738b7b..82b32cf616ec 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -22,13 +22,13 @@ import android.telephony.SubscriptionInfo import com.android.keyguard.ActiveUnlockConfig import com.android.keyguard.KeyguardListenModel import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.VERBOSE -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.VERBOSE +import com.android.systemui.plugins.log.LogLevel.WARNING import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt index 37829f25d179..a89cbf57f95b 100644 --- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt +++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt @@ -19,11 +19,11 @@ import kotlinx.coroutines.withContext @SysUISingleton class ChooserSelector @Inject constructor( - context: Context, + private val context: Context, private val featureFlags: FeatureFlags, @Application private val coroutineScope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher -) : CoreStartable(context) { +) : CoreStartable { private val packageManager = context.packageManager private val chooserComponent = ComponentName.unflattenFromString( diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java index 0201cdc25319..929ebea37eef 100644 --- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java +++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java @@ -16,39 +16,41 @@ package com.android.systemui; -import android.content.Context; import android.content.res.Configuration; import androidx.annotation.NonNull; -import com.android.internal.annotations.VisibleForTesting; - import java.io.PrintWriter; /** - * A top-level module of system UI code (sometimes called "system UI services" elsewhere in code). - * Which CoreStartable modules are loaded can be controlled via a config resource. + * Code that needs to be run when SystemUI is started. + * + * Which CoreStartable modules are loaded is controlled via the dagger graph. Bind them into the + * CoreStartable map with code such as: + * + * <pre> + * @Binds + * @IntoMap + * @ClassKey(FoobarStartable::class) + * abstract fun bind(impl: FoobarStartable): CoreStartable + * </pre> * * @see SystemUIApplication#startServicesIfNeeded() */ -public abstract class CoreStartable implements Dumpable { - protected final Context mContext; - - public CoreStartable(Context context) { - mContext = context; - } +public interface CoreStartable extends Dumpable { /** Main entry point for implementations. Called shortly after app startup. */ - public abstract void start(); + void start(); - protected void onConfigurationChanged(Configuration newConfig) { + /** */ + default void onConfigurationChanged(Configuration newConfig) { } @Override - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + default void dump(@NonNull PrintWriter pw, @NonNull String[] args) { } - @VisibleForTesting - protected void onBootCompleted() { + /** Called when the device reports BOOT_COMPLETED. */ + default void onBootCompleted() { } } diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt index a3351e1a6440..5d52056d8b17 100644 --- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt @@ -86,30 +86,38 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { onUpdate() } - fun onDisplayChanged(newDisplayUniqueId: String?) { + fun updateConfiguration(newDisplayUniqueId: String?) { + val info = DisplayInfo() + context.display?.getDisplayInfo(info) val oldMode: Display.Mode? = displayMode - val display: Display? = context.display - displayMode = display?.mode + displayMode = info.mode - if (displayUniqueId != display?.uniqueId) { - displayUniqueId = display?.uniqueId - shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout( - context.resources, displayUniqueId - ) - } + updateDisplayUniqueId(info.uniqueId) // Skip if display mode or cutout hasn't changed. if (!displayModeChanged(oldMode, displayMode) && - display?.cutout == displayInfo.displayCutout) { + displayInfo.displayCutout == info.displayCutout && + displayRotation == info.rotation) { return } - if (newDisplayUniqueId == display?.uniqueId) { + if (newDisplayUniqueId == info.uniqueId) { + displayRotation = info.rotation updateCutout() updateProtectionBoundingPath() onUpdate() } } + open fun updateDisplayUniqueId(newDisplayUniqueId: String?) { + if (displayUniqueId != newDisplayUniqueId) { + displayUniqueId = newDisplayUniqueId + shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout( + context.resources, displayUniqueId + ) + invalidate() + } + } + open fun updateRotation(rotation: Int) { displayRotation = rotation updateCutout() diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java index 9cdce6400e56..8f419560c78d 100644 --- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java +++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java @@ -46,7 +46,7 @@ import javax.inject.Inject; * system that are used for testing the latency. */ @SysUISingleton -public class LatencyTester extends CoreStartable { +public class LatencyTester implements CoreStartable { private static final boolean DEFAULT_ENABLED = Build.IS_ENG; private static final String ACTION_FINGERPRINT_WAKE = @@ -62,13 +62,11 @@ public class LatencyTester extends CoreStartable { @Inject public LatencyTester( - Context context, BiometricUnlockController biometricUnlockController, BroadcastDispatcher broadcastDispatcher, DeviceConfigProxy deviceConfigProxy, @Main DelayableExecutor mainExecutor ) { - super(context); mBiometricUnlockController = biometricUnlockController; mBroadcastDispatcher = broadcastDispatcher; mDeviceConfigProxy = deviceConfigProxy; diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 2e13903814a5..11d579d481c1 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -105,7 +105,7 @@ import kotlin.Pair; * for antialiasing and emulation purposes. */ @SysUISingleton -public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable { +public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { private static final boolean DEBUG = false; private static final String TAG = "ScreenDecorations"; @@ -130,6 +130,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab @VisibleForTesting protected boolean mIsRegistered; private final BroadcastDispatcher mBroadcastDispatcher; + private final Context mContext; private final Executor mMainExecutor; private final TunerService mTunerService; private final SecureSettings mSecureSettings; @@ -308,7 +309,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab ThreadFactory threadFactory, PrivacyDotDecorProviderFactory dotFactory, FaceScanningProviderFactory faceScanningFactory) { - super(context); + mContext = context; mMainExecutor = mainExecutor; mSecureSettings = secureSettings; mBroadcastDispatcher = broadcastDispatcher; @@ -455,7 +456,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } - boolean needToUpdateProviderViews = false; final String newUniqueId = mDisplayInfo.uniqueId; if (!Objects.equals(newUniqueId, mDisplayUniqueId)) { mDisplayUniqueId = newUniqueId; @@ -473,37 +473,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab setupDecorations(); return; } - - if (mScreenDecorHwcLayer != null) { - updateHwLayerRoundedCornerDrawable(); - updateHwLayerRoundedCornerExistAndSize(); - } - needToUpdateProviderViews = true; - } - - final float newRatio = getPhysicalPixelDisplaySizeRatio(); - if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) { - mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio); - if (mScreenDecorHwcLayer != null) { - updateHwLayerRoundedCornerExistAndSize(); - } - needToUpdateProviderViews = true; - } - - if (needToUpdateProviderViews) { - updateOverlayProviderViews(null); - } else { - updateOverlayProviderViews(new Integer[] { - mFaceScanningViewId, - R.id.display_cutout, - R.id.display_cutout_left, - R.id.display_cutout_right, - R.id.display_cutout_bottom, - }); - } - - if (mScreenDecorHwcLayer != null) { - mScreenDecorHwcLayer.onDisplayChanged(newUniqueId); } } }; @@ -973,7 +942,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } @Override - protected void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(Configuration newConfig) { if (DEBUG_DISABLE_SCREEN_DECORATIONS) { Log.i(TAG, "ScreenDecorations is disabled"); return; @@ -1069,9 +1038,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) { mRotation = newRotation; mDisplayMode = newMod; + mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( + getPhysicalPixelDisplaySizeRatio()); if (mScreenDecorHwcLayer != null) { mScreenDecorHwcLayer.pendingConfigChange = false; - mScreenDecorHwcLayer.updateRotation(mRotation); + mScreenDecorHwcLayer.updateConfiguration(mDisplayUniqueId); updateHwLayerRoundedCornerExistAndSize(); updateHwLayerRoundedCornerDrawable(); } @@ -1110,7 +1081,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab context.getResources(), context.getDisplay().getUniqueId()); } - private void updateOverlayProviderViews(@Nullable Integer[] filterIds) { + @VisibleForTesting + void updateOverlayProviderViews(@Nullable Integer[] filterIds) { if (mOverlays == null) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java index 1f2de4cfc346..5bd85a72b06f 100644 --- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java +++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java @@ -38,16 +38,17 @@ import javax.inject.Inject; * @see SliceBroadcastRelay */ @SysUISingleton -public class SliceBroadcastRelayHandler extends CoreStartable { +public class SliceBroadcastRelayHandler implements CoreStartable { private static final String TAG = "SliceBroadcastRelay"; private static final boolean DEBUG = false; private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>(); + private final Context mContext; private final BroadcastDispatcher mBroadcastDispatcher; @Inject public SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher) { - super(context); + mContext = context; mBroadcastDispatcher = broadcastDispatcher; } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 9138b2346ab8..8415ef88f6a0 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -47,8 +47,6 @@ import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dump.DumpManager; import com.android.systemui.util.NotificationChannels; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; @@ -296,14 +294,10 @@ public class SystemUIApplication extends Application implements CoreStartable startable; if (DEBUG) Log.d(TAG, "loading: " + clsName); try { - Constructor<?> constructor = Class.forName(clsName).getConstructor( - Context.class); - startable = (CoreStartable) constructor.newInstance(this); + startable = (CoreStartable) Class.forName(clsName).newInstance(); } catch (ClassNotFoundException - | NoSuchMethodException | IllegalAccessException - | InstantiationException - | InvocationTargetException ex) { + | InstantiationException ex) { throw new RuntimeException(ex); } diff --git a/packages/SystemUI/src/com/android/systemui/VendorServices.java b/packages/SystemUI/src/com/android/systemui/VendorServices.java index 139448c0fab4..a3209396ac27 100644 --- a/packages/SystemUI/src/com/android/systemui/VendorServices.java +++ b/packages/SystemUI/src/com/android/systemui/VendorServices.java @@ -16,15 +16,12 @@ package com.android.systemui; -import android.content.Context; - /** * Placeholder for any vendor-specific services. */ -public class VendorServices extends CoreStartable { +public class VendorServices implements CoreStartable { - public VendorServices(Context context) { - super(context); + public VendorServices() { } @Override diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index a1288b531955..9f1c9b45e6cd 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -69,7 +69,7 @@ import dagger.Lazy; * Class to register system actions with accessibility framework. */ @SysUISingleton -public class SystemActions extends CoreStartable { +public class SystemActions implements CoreStartable { private static final String TAG = "SystemActions"; /** @@ -177,6 +177,7 @@ public class SystemActions extends CoreStartable { private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; private final SystemActionsBroadcastReceiver mReceiver; + private final Context mContext; private final Optional<Recents> mRecentsOptional; private Locale mLocale; private final AccessibilityManager mA11yManager; @@ -190,7 +191,7 @@ public class SystemActions extends CoreStartable { NotificationShadeWindowController notificationShadeController, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, Optional<Recents> recentsOptional) { - super(context); + mContext = context; mRecentsOptional = recentsOptional; mReceiver = new SystemActionsBroadcastReceiver(); mLocale = mContext.getResources().getConfiguration().getLocales().get(0); @@ -219,7 +220,6 @@ public class SystemActions extends CoreStartable { @Override public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0); if (!locale.equals(mLocale)) { mLocale = locale; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 6e14ddc671a3..ab11fcea7b28 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -52,11 +52,12 @@ import javax.inject.Inject; * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called. */ @SysUISingleton -public class WindowMagnification extends CoreStartable implements WindowMagnifierCallback, +public class WindowMagnification implements CoreStartable, WindowMagnifierCallback, CommandQueue.Callbacks { private static final String TAG = "WindowMagnification"; private final ModeSwitchesController mModeSwitchesController; + private final Context mContext; private final Handler mHandler; private final AccessibilityManager mAccessibilityManager; private final CommandQueue mCommandQueue; @@ -102,7 +103,7 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie public WindowMagnification(Context context, @Main Handler mainHandler, CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, SysUiState sysUiState, OverviewProxyService overviewProxyService) { - super(context); + mContext = context; mHandler = mainHandler; mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mCommandQueue = commandQueue; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index aae92adc5880..d43e5d9d09cc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -102,7 +102,7 @@ import kotlin.Unit; * {@link com.android.keyguard.KeyguardUpdateMonitor} */ @SysUISingleton -public class AuthController extends CoreStartable implements CommandQueue.Callbacks, +public class AuthController implements CoreStartable, CommandQueue.Callbacks, AuthDialogCallback, DozeReceiver { private static final String TAG = "AuthController"; @@ -110,6 +110,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba private static final int SENSOR_PRIVACY_DELAY = 500; private final Handler mHandler; + private final Context mContext; private final Execution mExecution; private final CommandQueue mCommandQueue; private final StatusBarStateController mStatusBarStateController; @@ -669,7 +670,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @NonNull InteractionJankMonitor jankMonitor, @Main Handler handler, @Background DelayableExecutor bgExecutor) { - super(context); + mContext = context; mExecution = execution; mUserManager = userManager; mLockPatternUtils = lockPatternUtils; @@ -1099,8 +1100,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); + public void onConfigurationChanged(Configuration newConfig) { updateSensorLocations(); // Save the state of the current dialog (buttons showing, etc) diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt index 96af42bfda22..d99625a9fbf2 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt @@ -17,9 +17,9 @@ package com.android.systemui.bluetooth import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.BluetoothLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject /** Helper class for logging bluetooth events. */ diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt index d7b263a323ca..c536e81ef20a 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt @@ -16,16 +16,14 @@ package com.android.systemui.broadcast -import android.content.Context import com.android.systemui.CoreStartable import javax.inject.Inject class BroadcastDispatcherStartable @Inject constructor( - context: Context, val broadcastDispatcher: BroadcastDispatcher -) : CoreStartable(context) { +) : CoreStartable { override fun start() { broadcastDispatcher.initialize() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt index 5b3a982ab5e2..d27708fc04d7 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt @@ -20,11 +20,11 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogMessage +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogMessage import com.android.systemui.log.dagger.BroadcastDispatcherLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index d53e56f7b852..500f28004429 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -18,6 +18,7 @@ package com.android.systemui.classifier; import static com.android.systemui.classifier.Classifier.BACK_GESTURE; import static com.android.systemui.classifier.Classifier.GENERIC; +import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR; import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_SUCCESS; import static com.android.systemui.classifier.FalsingModule.BRIGHT_LINE_GESTURE_CLASSIFERS; @@ -220,6 +221,11 @@ public class BrightLineFalsingManager implements FalsingManager { return r; }).collect(Collectors.toList()); + // check for false tap if it is a seekbar interaction + if (interactionType == MEDIA_SEEKBAR) { + localResult[0] &= isFalseTap(LOW_PENALTY); + } + logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]); return localResult[0]; diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index f526277a0a37..82e570438dab 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -31,16 +31,19 @@ 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; /** * ClipboardListener brings up a clipboard overlay when something is copied to the clipboard. */ @SysUISingleton -public class ClipboardListener extends CoreStartable - implements ClipboardManager.OnPrimaryClipChangedListener { +public class ClipboardListener implements + CoreStartable, ClipboardManager.OnPrimaryClipChangedListener { private static final String TAG = "ClipboardListener"; @VisibleForTesting @@ -49,21 +52,32 @@ public class ClipboardListener extends CoreStartable static final String EXTRA_SUPPRESS_OVERLAY = "com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY"; + private final Context mContext; private final DeviceConfigProxy mDeviceConfig; - private final ClipboardOverlayControllerFactory mOverlayFactory; + private final Provider<ClipboardOverlayController> mOverlayProvider; + private final ClipboardOverlayControllerLegacyFactory mOverlayFactory; private final ClipboardManager mClipboardManager; private final UiEventLogger mUiEventLogger; - private ClipboardOverlayController mClipboardOverlayController; + private final FeatureFlags mFeatureFlags; + private boolean mUsingNewOverlay; + private ClipboardOverlay mClipboardOverlay; @Inject public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy, - ClipboardOverlayControllerFactory overlayFactory, ClipboardManager clipboardManager, - UiEventLogger uiEventLogger) { - super(context); + Provider<ClipboardOverlayController> clipboardOverlayControllerProvider, + ClipboardOverlayControllerLegacyFactory overlayFactory, + ClipboardManager clipboardManager, + UiEventLogger uiEventLogger, + FeatureFlags featureFlags) { + mContext = context; mDeviceConfig = deviceConfigProxy; + mOverlayProvider = clipboardOverlayControllerProvider; mOverlayFactory = overlayFactory; mClipboardManager = clipboardManager; mUiEventLogger = uiEventLogger; + mFeatureFlags = featureFlags; + + mUsingNewOverlay = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR); } @Override @@ -88,16 +102,22 @@ public class ClipboardListener extends CoreStartable return; } - if (mClipboardOverlayController == null) { - mClipboardOverlayController = mOverlayFactory.create(mContext); + 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); + } mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource); } else { mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource); } - mClipboardOverlayController.setClipData(clipData, clipSource); - mClipboardOverlayController.setOnSessionCompleteListener(() -> { + mClipboardOverlay.setClipData(clipData, clipSource); + mClipboardOverlay.setOnSessionCompleteListener(() -> { // Session is complete, free memory until it's needed again. - mClipboardOverlayController = null; + mClipboardOverlay = null; }); } @@ -119,4 +139,10 @@ public class ClipboardListener extends CoreStartable private static boolean isEmulator() { return SystemProperties.getBoolean("ro.boot.qemu", false); } + + interface ClipboardOverlay { + void setClipData(ClipData clipData, String clipSource); + + void setOnSessionCompleteListener(Runnable runnable); + } } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 7e499ebdf691..bfb27a4c87b8 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.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; @@ -37,11 +36,6 @@ 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; @@ -52,14 +46,7 @@ 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; @@ -67,57 +54,37 @@ 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.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext; import com.android.systemui.screenshot.TimeoutHandler; import java.io.IOException; import java.util.ArrayList; +import java.util.Optional; + +import javax.inject.Inject; /** * Controls state and UI for the overlay that appears when something is added to the clipboard */ -public class ClipboardOverlayController { +public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay { private static final String TAG = "ClipboardOverlayCtrlr"; /** Constants for screenshot/copy deconflicting */ @@ -126,36 +93,22 @@ public class ClipboardOverlayController { public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY"; 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 ClipboardOverlayWindow 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 final ClipboardOverlayView mView; private Runnable mOnSessionCompleteListener; + private Runnable mOnRemoteCopyTapped; + private Runnable mOnShareTapped; + private Runnable mOnEditTapped; + private Runnable mOnPreviewTapped; private InputMonitor mInputMonitor; private InputEventReceiver mInputEventReceiver; @@ -163,14 +116,66 @@ public class ClipboardOverlayController { private BroadcastReceiver mCloseDialogsReceiver; private BroadcastReceiver mScreenshotReceiver; - private boolean mBlockAttach = false; private Animator mExitAnimator; private Animator mEnterAnimator; - private final int mOrientation; - private boolean mKeyboardVisible; + private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks = + new ClipboardOverlayView.ClipboardOverlayCallbacks() { + @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(); + } + + @Override + public void onPreviewTapped() { + if (mOnPreviewTapped != null) { + mOnPreviewTapped.run(); + } + } + + @Override + public void onShareButtonTapped() { + if (mOnShareTapped != null) { + mOnShareTapped.run(); + } + } + + @Override + public void onEditButtonTapped() { + if (mOnEditTapped != null) { + mOnEditTapped.run(); + } + } + + @Override + public void onRemoteCopyButtonTapped() { + if (mOnRemoteCopyTapped != null) { + mOnRemoteCopyTapped.run(); + } + } + + @Override + public void onDismissButtonTapped() { + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED); + animateOut(); + } + }; - public ClipboardOverlayController(Context context, + @Inject + public ClipboardOverlayController(@OverlayWindowContext Context context, + ClipboardOverlayView clipboardOverlayView, + ClipboardOverlayWindow clipboardOverlayWindow, BroadcastDispatcher broadcastDispatcher, BroadcastSender broadcastSender, TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) { @@ -181,121 +186,26 @@ public class ClipboardOverlayController { mClipboardLogger = new ClipboardLogger(uiEventLogger); - mAccessibilityManager = AccessibilityManager.getInstance(mContext); + mView = clipboardOverlayView; + mWindow = clipboardOverlayWindow; + mWindow.init(mView::setInsets, () -> { + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER); + hideImmediate(); + }); + 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, 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; - } + mView.setCallbacks(mClipboardCallbacks); - @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.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"); - } - }); + mView.setInsets(mWindow.getWindowInsets(), + mContext.getResources().getConfiguration().orientation); }); mTimeoutHandler.setOnTimeoutRunnable(() -> { @@ -336,21 +246,19 @@ public class ClipboardOverlayController { broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION); } - void setClipData(ClipData clipData, String clipSource) { + @Override // ClipboardListener.ClipboardOverlay + public void setClipData(ClipData clipData, String clipSource) { if (mExitAnimator != null && mExitAnimator.isRunning()) { mExitAnimator.cancel(); } reset(); - String accessibilityAnnouncement; + String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); 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); + mView.showDefaultTextPreview(); } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) { ClipData.Item item = clipData.getItemAt(0); if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, @@ -360,53 +268,47 @@ public class ClipboardOverlayController { } } if (isSensitive) { - showEditableText( - mContext.getResources().getString(R.string.clipboard_asterisks), true); + showEditableText(mContext.getString(R.string.clipboard_asterisks), true); } else { showEditableText(item.getText(), false); } - showShareChip(clipData); + mOnShareTapped = () -> shareContent(clipData); + mView.showShareChip(); accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied); } else if (clipData.getItemAt(0).getUri() != null) { if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) { - showShareChip(clipData); + mOnShareTapped = () -> shareContent(clipData); + mView.showShareChip(); 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); + mView.showDefaultTextPreview(); } + maybeShowRemoteCopy(clipData); + animateIn(); + mView.announceForAccessibility(accessibilityAnnouncement); + mTimeoutHandler.resetTimeout(); + } + + private void maybeShowRemoteCopy(ClipData clipData) { 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) -> { + mView.setRemoteCopyVisibility(true); + mOnRemoteCopyTapped = () -> { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED); mContext.startActivity(remoteCopyIntent); animateOut(); - }); - mActionContainerBackground.setVisibility(View.VISIBLE); + }; } else { - mRemoteCopyChip.setVisibility(View.GONE); + mView.setRemoteCopyVisibility(false); } - withWindowAttached(() -> { - if (mEnterAnimator == null || !mEnterAnimator.isRunning()) { - mView.post(this::animateIn); - } - mView.announceForAccessibility(accessibilityAnnouncement); - }); - mTimeoutHandler.resetTimeout(); } - void setOnSessionCompleteListener(Runnable runnable) { + @Override // ClipboardListener.ClipboardOverlay + public void setOnSessionCompleteListener(Runnable runnable) { mOnSessionCompleteListener = runnable; } @@ -418,72 +320,29 @@ public class ClipboardOverlayController { 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(); + Optional<RemoteAction> action = actions.stream().filter(remoteAction -> { + ComponentName component = remoteAction.getActionIntent().getIntent().getComponent(); + return component != null && !TextUtils.equals(source, component.getPackageName()); + }).findFirst(); + mView.resetActionChips(); + action.ifPresent(remoteAction -> mView.setActionChip(remoteAction, () -> { + 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()) { + 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( + if (!mView.isInTouchRegion( (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE); animateOut(); @@ -513,95 +372,27 @@ public class ClipboardOverlayController { 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); + mView.showTextPreview(text, hidden); + mView.setEditAccessibilityAction(true); + mOnPreviewTapped = this::editText; 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); + mOnEditTapped = this::editText; + mView.showEditChip(mContext.getString(R.string.clipboard_edit_text_description)); } - textView.setOnClickListener(listener); } private boolean tryShowEditableImage(Uri uri, boolean isSensitive) { - View.OnClickListener listener = v -> editImage(uri); + Runnable listener = () -> 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); + mView.showImagePreview(null); if (isEditableImage) { - mHiddenPreview.setOnClickListener(listener); - setAccessibilityActionToEdit(mHiddenPreview); + mOnPreviewTapped = listener; + mView.setEditAccessibilityAction(true); } } else if (isEditableImage) { // if the MIMEtype is image, try to load try { @@ -609,44 +400,36 @@ public class ClipboardOverlayController { // 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); + mView.showImagePreview(thumbnail); + mView.setEditAccessibilityAction(true); + mOnPreviewTapped = listener; } catch (IOException e) { Log.e(TAG, "Thumbnail loading failed", e); - showTextPreview( - mContext.getResources().getString(R.string.clipboard_overlay_text_copied), - mTextPreview); + mView.showDefaultTextPreview(); isEditableImage = false; } } else { - showTextPreview( - mContext.getResources().getString(R.string.clipboard_overlay_text_copied), - mTextPreview); + mView.showDefaultTextPreview(); } 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)); + mView.showEditChip(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); + if (mEnterAnimator != null && mEnterAnimator.isRunning()) { + return; } - mEnterAnimator = getEnterAnimation(); + mEnterAnimator = mView.getEnterAnimation(); + mEnterAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mTimeoutHandler.resetTimeout(); + } + }); mEnterAnimator.start(); } @@ -654,7 +437,7 @@ public class ClipboardOverlayController { if (mExitAnimator != null && mExitAnimator.isRunning()) { return; } - Animator anim = getExitAnimation(); + Animator anim = mView.getExitAnimation(); anim.addListener(new AnimatorListenerAdapter() { private boolean mCancelled; @@ -676,122 +459,11 @@ public class ClipboardOverlayController { 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); - } + mWindow.remove(); if (mCloseDialogsReceiver != null) { mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver); mCloseDialogsReceiver = null; @@ -813,129 +485,20 @@ public class ClipboardOverlayController { } } - 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(); + mOnRemoteCopyTapped = null; + mOnShareTapped = null; + mOnEditTapped = null; + mOnPreviewTapped = null; + mView.reset(); 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; diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java new file mode 100644 index 000000000000..3a040829ba0c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java @@ -0,0 +1,963 @@ +/* + * 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/ClipboardOverlayControllerFactory.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java index 8b0b2a59dd92..0d989a78947d 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java @@ -27,17 +27,17 @@ import com.android.systemui.screenshot.TimeoutHandler; import javax.inject.Inject; /** - * A factory that churns out ClipboardOverlayControllers on demand. + * A factory that churns out ClipboardOverlayControllerLegacys on demand. */ @SysUISingleton -public class ClipboardOverlayControllerFactory { +public class ClipboardOverlayControllerLegacyFactory { private final UiEventLogger mUiEventLogger; private final BroadcastDispatcher mBroadcastDispatcher; private final BroadcastSender mBroadcastSender; @Inject - public ClipboardOverlayControllerFactory(BroadcastDispatcher broadcastDispatcher, + public ClipboardOverlayControllerLegacyFactory(BroadcastDispatcher broadcastDispatcher, BroadcastSender broadcastSender, UiEventLogger uiEventLogger) { this.mBroadcastDispatcher = broadcastDispatcher; this.mBroadcastSender = broadcastSender; @@ -45,10 +45,10 @@ public class ClipboardOverlayControllerFactory { } /** - * One new ClipboardOverlayController, coming right up! + * One new ClipboardOverlayControllerLegacy, coming right up! */ - public ClipboardOverlayController create(Context context) { - return new ClipboardOverlayController(context, mBroadcastDispatcher, mBroadcastSender, + 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/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java new file mode 100644 index 000000000000..2d3315759371 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java @@ -0,0 +1,482 @@ +/* + * 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 static android.content.res.Configuration.ORIENTATION_PORTRAIT; + +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.Nullable; +import android.app.RemoteAction; +import android.content.Context; +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.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.MathUtils; +import android.util.TypedValue; +import android.view.DisplayCutout; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowInsets; +import android.view.accessibility.AccessibilityManager; +import android.view.animation.LinearInterpolator; +import android.view.animation.PathInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.core.view.ViewCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; + +import com.android.systemui.R; +import com.android.systemui.screenshot.DraggableConstraintLayout; +import com.android.systemui.screenshot.FloatingWindowUtil; +import com.android.systemui.screenshot.OverlayActionChip; + +import java.util.ArrayList; + +/** + * Handles the visual elements and animations for the clipboard overlay. + */ +public class ClipboardOverlayView extends DraggableConstraintLayout { + + interface ClipboardOverlayCallbacks extends SwipeDismissCallbacks { + void onDismissButtonTapped(); + + void onRemoteCopyButtonTapped(); + + void onEditButtonTapped(); + + void onShareButtonTapped(); + + void onPreviewTapped(); + } + + private static final String TAG = "ClipboardView"; + + 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 DisplayMetrics mDisplayMetrics; + private final AccessibilityManager mAccessibilityManager; + private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>(); + + private View mClipboardPreview; + private ImageView mImagePreview; + private TextView mTextPreview; + private TextView mHiddenPreview; + private View mPreviewBorder; + private OverlayActionChip mEditChip; + private OverlayActionChip mShareChip; + private OverlayActionChip mRemoteCopyChip; + private View mActionContainerBackground; + private View mDismissButton; + private LinearLayout mActionContainer; + + public ClipboardOverlayView(Context context) { + this(context, null); + } + + public ClipboardOverlayView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ClipboardOverlayView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mDisplayMetrics = new DisplayMetrics(); + mContext.getDisplay().getRealMetrics(mDisplayMetrics); + mAccessibilityManager = AccessibilityManager.getInstance(mContext); + } + + @Override + protected void onFinishInflate() { + mActionContainerBackground = + requireNonNull(findViewById(R.id.actions_container_background)); + mActionContainer = requireNonNull(findViewById(R.id.actions)); + mClipboardPreview = requireNonNull(findViewById(R.id.clipboard_preview)); + mImagePreview = requireNonNull(findViewById(R.id.image_preview)); + mTextPreview = requireNonNull(findViewById(R.id.text_preview)); + mHiddenPreview = requireNonNull(findViewById(R.id.hidden_preview)); + mPreviewBorder = requireNonNull(findViewById(R.id.preview_border)); + mEditChip = requireNonNull(findViewById(R.id.edit_chip)); + mShareChip = requireNonNull(findViewById(R.id.share_chip)); + mRemoteCopyChip = requireNonNull(findViewById(R.id.remote_copy_chip)); + mDismissButton = requireNonNull(findViewById(R.id.dismiss_button)); + + mEditChip.setAlpha(1); + mShareChip.setAlpha(1); + mRemoteCopyChip.setAlpha(1); + mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share)); + + 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); + + mRemoteCopyChip.setContentDescription( + mContext.getString(R.string.clipboard_send_nearby_description)); + + mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> { + int availableHeight = mTextPreview.getHeight() + - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom()); + mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight()); + return true; + }); + super.onFinishInflate(); + } + + @Override + public void setCallbacks(SwipeDismissCallbacks callbacks) { + super.setCallbacks(callbacks); + ClipboardOverlayCallbacks clipboardCallbacks = (ClipboardOverlayCallbacks) callbacks; + mEditChip.setOnClickListener(v -> clipboardCallbacks.onEditButtonTapped()); + mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped()); + mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped()); + mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped()); + mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped()); + } + + void setEditAccessibilityAction(boolean editable) { + if (editable) { + ViewCompat.replaceAccessibilityAction(mClipboardPreview, + AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK, + mContext.getString(R.string.clipboard_edit), null); + } else { + ViewCompat.replaceAccessibilityAction(mClipboardPreview, + AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK, + null, null); + } + } + + void setInsets(WindowInsets insets, int orientation) { + FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams(); + if (p == null) { + return; + } + Rect margins = computeMargins(insets, orientation); + p.setMargins(margins.left, margins.top, margins.right, margins.bottom); + setLayoutParams(p); + requestLayout(); + } + + boolean isInTouchRegion(int x, int y) { + 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); + + return touchRegion.contains(x, y); + } + + void setRemoteCopyVisibility(boolean visible) { + if (visible) { + mRemoteCopyChip.setVisibility(View.VISIBLE); + mActionContainerBackground.setVisibility(View.VISIBLE); + } else { + mRemoteCopyChip.setVisibility(View.GONE); + } + } + + void showDefaultTextPreview() { + String copied = mContext.getString(R.string.clipboard_overlay_text_copied); + showTextPreview(copied, false); + } + + void showTextPreview(CharSequence text, boolean hidden) { + TextView textView = hidden ? mHiddenPreview : mTextPreview; + showSinglePreview(textView); + textView.setText(text.subSequence(0, Math.min(500, text.length()))); + updateTextSize(text, textView); + textView.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + if (right - left != oldRight - oldLeft) { + updateTextSize(text, textView); + } + }); + mEditChip.setVisibility(View.GONE); + } + + void showImagePreview(@Nullable Bitmap thumbnail) { + if (thumbnail == null) { + mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden)); + showSinglePreview(mHiddenPreview); + } else { + mImagePreview.setImageBitmap(thumbnail); + showSinglePreview(mImagePreview); + } + } + + void showEditChip(String contentDescription) { + mEditChip.setVisibility(View.VISIBLE); + mActionContainerBackground.setVisibility(View.VISIBLE); + mEditChip.setContentDescription(contentDescription); + } + + void showShareChip() { + mShareChip.setVisibility(View.VISIBLE); + mActionContainerBackground.setVisibility(View.VISIBLE); + } + + void reset() { + setTranslationX(0); + setAlpha(0); + mActionContainerBackground.setVisibility(View.GONE); + mDismissButton.setVisibility(View.GONE); + mShareChip.setVisibility(View.GONE); + mEditChip.setVisibility(View.GONE); + mRemoteCopyChip.setVisibility(View.GONE); + setEditAccessibilityAction(false); + resetActionChips(); + } + + void resetActionChips() { + for (OverlayActionChip chip : mActionChips) { + mActionContainer.removeView(chip); + } + mActionChips.clear(); + } + + Animator getEnterAnimation() { + if (mAccessibilityManager.isEnabled()) { + mDismissButton.setVisibility(View.VISIBLE); + } + 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 -> { + 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); + setAlpha(1); + } + }); + return enterAnim; + } + + 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 -> 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; + } + + void setActionChip(RemoteAction action, Runnable onFinish) { + mActionContainerBackground.setVisibility(View.VISIBLE); + OverlayActionChip chip = constructActionChip(action, onFinish); + mActionContainer.addView(chip); + mActionChips.add(chip); + } + + private void showSinglePreview(View v) { + mTextPreview.setVisibility(View.GONE); + mImagePreview.setVisibility(View.GONE); + mHiddenPreview.setVisibility(View.GONE); + v.setVisibility(View.VISIBLE); + } + + private OverlayActionChip constructActionChip(RemoteAction action, Runnable onFinish) { + 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(), onFinish); + chip.setAlpha(1); + return chip; + } + + private static 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 static Rect computeMargins(WindowInsets insets, int orientation) { + DisplayCutout cutout = insets.getDisplayCutout(); + Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars()); + Insets imeInsets = insets.getInsets(WindowInsets.Type.ime()); + if (cutout == null) { + return new Rect(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom)); + } else { + Insets waterfall = cutout.getWaterfallInsets(); + if (orientation == ORIENTATION_PORTRAIT) { + return new Rect( + 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 { + return new Rect( + waterfall.left, + waterfall.top, + waterfall.right, + Math.max(imeInsets.bottom, + Math.max(navBarInsets.bottom, waterfall.bottom))); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java new file mode 100644 index 000000000000..9dac9b393d60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java @@ -0,0 +1,174 @@ +/* + * 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.annotation.MainThread; +import android.annotation.NonNull; +import android.app.ICompatCameraControlCallback; +import android.content.Context; +import android.content.res.Configuration; +import android.util.Log; +import android.view.View; +import android.view.ViewRootImpl; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; + +import com.android.internal.policy.PhoneWindow; +import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext; +import com.android.systemui.screenshot.FloatingWindowUtil; + +import java.util.function.BiConsumer; + +import javax.inject.Inject; + +/** + * Handles attaching the window and the window insets for the clipboard overlay. + */ +public class ClipboardOverlayWindow extends PhoneWindow + implements ViewRootImpl.ActivityConfigCallback { + private static final String TAG = "ClipboardOverlayWindow"; + + private final Context mContext; + private final WindowManager mWindowManager; + private final WindowManager.LayoutParams mWindowLayoutParams; + + private boolean mKeyboardVisible; + private final int mOrientation; + private BiConsumer<WindowInsets, Integer> mOnKeyboardChangeListener; + private Runnable mOnOrientationChangeListener; + + @Inject + ClipboardOverlayWindow(@OverlayWindowContext Context context) { + super(context); + mContext = context; + mOrientation = mContext.getResources().getConfiguration().orientation; + + // Setup the window that we are going to use + requestFeature(Window.FEATURE_NO_TITLE); + requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); + setBackgroundDrawableResource(android.R.color.transparent); + mWindowManager = mContext.getSystemService(WindowManager.class); + mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams(); + mWindowLayoutParams.setTitle("ClipboardOverlay"); + setWindowManager(mWindowManager, null, null); + setWindowFocusable(false); + } + + /** + * Set callbacks for keyboard state change and orientation change and attach the window + * + * @param onKeyboardChangeListener callback for IME visibility changes + * @param onOrientationChangeListener callback for device orientation changes + */ + public void init(@NonNull BiConsumer<WindowInsets, Integer> onKeyboardChangeListener, + @NonNull Runnable onOrientationChangeListener) { + mOnKeyboardChangeListener = onKeyboardChangeListener; + mOnOrientationChangeListener = onOrientationChangeListener; + + attach(); + withWindowAttached(() -> { + WindowInsets currentInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets(); + mKeyboardVisible = currentInsets.isVisible(WindowInsets.Type.ime()); + peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(() -> { + WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets(); + boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime()); + if (keyboardVisible != mKeyboardVisible) { + mKeyboardVisible = keyboardVisible; + mOnKeyboardChangeListener.accept(insets, mOrientation); + } + }); + peekDecorView().getViewRootImpl().setActivityConfigCallback(this); + }); + } + + @Override // ViewRootImpl.ActivityConfigCallback + public void onConfigurationChanged(Configuration overrideConfig, int newDisplayId) { + if (mContext.getResources().getConfiguration().orientation != mOrientation) { + mOnOrientationChangeListener.run(); + } + } + + @Override // ViewRootImpl.ActivityConfigCallback + public void requestCompatCameraControl(boolean showControl, boolean transformationApplied, + ICompatCameraControlCallback callback) { + Log.w(TAG, "unexpected requestCompatCameraControl call"); + } + + void remove() { + final View decorView = peekDecorView(); + if (decorView != null && decorView.isAttachedToWindow()) { + mWindowManager.removeViewImmediate(decorView); + } + } + + WindowInsets getWindowInsets() { + return mWindowManager.getCurrentWindowMetrics().getWindowInsets(); + } + + void withWindowAttached(Runnable action) { + View decorView = getDecorView(); + if (decorView.isAttachedToWindow()) { + action.run(); + } else { + decorView.getViewTreeObserver().addOnWindowAttachListener( + new ViewTreeObserver.OnWindowAttachListener() { + @Override + public void onWindowAttached() { + decorView.getViewTreeObserver().removeOnWindowAttachListener(this); + action.run(); + } + + @Override + public void onWindowDetached() { + } + }); + } + } + + @MainThread + private void attach() { + View decorView = getDecorView(); + if (decorView.isAttachedToWindow()) { + return; + } + mWindowManager.addView(decorView, mWindowLayoutParams); + decorView.requestApplyInsets(); + } + + /** + * 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 = peekDecorView(); + if (decorView != null && decorView.isAttachedToWindow()) { + mWindowManager.updateViewLayout(decorView, mWindowLayoutParams); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java new file mode 100644 index 000000000000..22448130f7e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java @@ -0,0 +1,68 @@ +/* + * 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.dagger; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.view.Display; +import android.view.LayoutInflater; + +import com.android.systemui.R; +import com.android.systemui.clipboardoverlay.ClipboardOverlayView; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +import dagger.Module; +import dagger.Provides; + +/** Module for {@link com.android.systemui.clipboardoverlay}. */ +@Module +public interface ClipboardOverlayModule { + + /** + * + */ + @Provides + @OverlayWindowContext + static Context provideWindowContext(DisplayManager displayManager, Context context) { + Display display = displayManager.getDisplay(DEFAULT_DISPLAY); + return context.createWindowContext(display, TYPE_SCREENSHOT, null); + } + + /** + * + */ + @Provides + static ClipboardOverlayView provideClipboardOverlayView(@OverlayWindowContext Context context) { + return (ClipboardOverlayView) LayoutInflater.from(context).inflate( + R.layout.clipboard_overlay, null); + } + + @Qualifier + @Documented + @Retention(RUNTIME) + @interface OverlayWindowContext { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt index bebade0cc484..08e8293cbe9c 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt @@ -17,6 +17,7 @@ package com.android.systemui.common.shared.model import android.annotation.StringRes +import android.content.Context /** * Models a content description, that can either be already [loaded][ContentDescription.Loaded] or @@ -30,4 +31,20 @@ sealed class ContentDescription { data class Resource( @StringRes val res: Int, ) : ContentDescription() + + companion object { + /** + * Returns the loaded content description string, or null if we don't have one. + * + * Prefer [com.android.systemui.common.ui.binder.ContentDescriptionViewBinder.bind] over + * this method. This should only be used for testing or concatenation purposes. + */ + fun ContentDescription?.loadContentDescription(context: Context): String? { + return when (this) { + null -> null + is Loaded -> this.description + is Resource -> context.getString(this.res) + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt index 5d0e08ffc307..4a5693202dba 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt @@ -18,6 +18,7 @@ package com.android.systemui.common.shared.model import android.annotation.StringRes +import android.content.Context /** * Models a text, that can either be already [loaded][Text.Loaded] or be a [reference] @@ -31,4 +32,20 @@ sealed class Text { data class Resource( @StringRes val res: Int, ) : Text() + + companion object { + /** + * Returns the loaded test string, or null if we don't have one. + * + * Prefer [com.android.systemui.common.ui.binder.TextViewBinder.bind] over this method. This + * should only be used for testing or concatenation purposes. + */ + fun Text?.loadText(context: Context): String? { + return when (this) { + null -> null + is Loaded -> this.text + is Resource -> context.getString(this.res) + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index 77b65233c112..d3b5d0edd222 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -21,6 +21,8 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.Bundle +import android.os.RemoteException +import android.service.dreams.IDreamManager import android.view.View import android.view.ViewGroup import android.view.WindowInsets @@ -40,11 +42,13 @@ import javax.inject.Inject */ class ControlsActivity @Inject constructor( private val uiController: ControlsUiController, - private val broadcastDispatcher: BroadcastDispatcher + private val broadcastDispatcher: BroadcastDispatcher, + private val dreamManager: IDreamManager, ) : ComponentActivity() { private lateinit var parent: ViewGroup private lateinit var broadcastReceiver: BroadcastReceiver + private var mExitToDream: Boolean = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -81,17 +85,36 @@ class ControlsActivity @Inject constructor( parent = requireViewById<ViewGroup>(R.id.global_actions_controls) parent.alpha = 0f - uiController.show(parent, { finish() }, this) + uiController.show(parent, { finishOrReturnToDream() }, this) ControlsAnimations.enterAnimation(parent).start() } - override fun onBackPressed() { + override fun onResume() { + super.onResume() + mExitToDream = intent.getBooleanExtra(ControlsUiController.EXIT_TO_DREAM, false) + } + + fun finishOrReturnToDream() { + if (mExitToDream) { + try { + mExitToDream = false + dreamManager.dream() + return + } catch (e: RemoteException) { + // Fall through + } + } finish() } + override fun onBackPressed() { + finishOrReturnToDream() + } + override fun onStop() { super.onStop() + mExitToDream = false uiController.hide() } @@ -106,7 +129,8 @@ class ControlsActivity @Inject constructor( broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val action = intent.getAction() - if (Intent.ACTION_SCREEN_OFF.equals(action)) { + if (action == Intent.ACTION_SCREEN_OFF || + action == Intent.ACTION_DREAMING_STARTED) { finish() } } @@ -114,6 +138,7 @@ class ControlsActivity @Inject constructor( val filter = IntentFilter() filter.addAction(Intent.ACTION_SCREEN_OFF) + filter.addAction(Intent.ACTION_DREAMING_STARTED) broadcastDispatcher.registerReceiver(broadcastReceiver, filter) } } 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 822f8f2e6191..c1cfbcb0c211 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -27,6 +27,7 @@ interface ControlsUiController { companion object { public const val TAG = "ControlsUiController" public const val EXTRA_ANIMATE = "extra_animate" + public const val EXIT_TO_DREAM = "extra_exit_to_dream" } fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 55eda0a0cb8a..721c0ba4f865 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -34,13 +34,14 @@ import com.android.systemui.log.SessionTracker import com.android.systemui.media.RingtonePlayer import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver -import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender +import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator import com.android.systemui.power.PowerUI import com.android.systemui.recents.Recents import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController +import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.theme.ThemeOverlayController import com.android.systemui.toast.ToastUI import com.android.systemui.usb.StorageNotification @@ -217,6 +218,12 @@ abstract class SystemUICoreStartableModule { @ClassKey(KeyguardLiftController::class) abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable + /** Inject into MediaTttSenderCoordinator. */ + @Binds + @IntoMap + @ClassKey(MediaTttSenderCoordinator::class) + abstract fun bindMediaTttSenderCoordinator(sysui: MediaTttSenderCoordinator): CoreStartable + /** Inject into MediaTttChipControllerReceiver. */ @Binds @IntoMap @@ -225,17 +232,15 @@ abstract class SystemUICoreStartableModule { sysui: MediaTttChipControllerReceiver ): CoreStartable - /** Inject into MediaTttChipControllerSender. */ - @Binds - @IntoMap - @ClassKey(MediaTttChipControllerSender::class) - abstract fun bindMediaTttChipControllerSender( - sysui: MediaTttChipControllerSender - ): CoreStartable - /** Inject into MediaTttCommandLineHelper. */ @Binds @IntoMap @ClassKey(MediaTttCommandLineHelper::class) abstract fun bindMediaTttCommandLineHelper(sysui: MediaTttCommandLineHelper): CoreStartable + + /** Inject into ChipbarCoordinator. */ + @Binds + @IntoMap + @ClassKey(ChipbarCoordinator::class) + abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index dc3dadb32669..d7638d663dc9 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -33,6 +33,7 @@ import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; import com.android.systemui.biometrics.UdfpsDisplayModeProvider; import com.android.systemui.biometrics.dagger.BiometricsModule; import com.android.systemui.classifier.FalsingModule; +import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; import com.android.systemui.controls.dagger.ControlsModule; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.dagger.DemoModeModule; @@ -118,6 +119,7 @@ import dagger.Provides; AssistModule.class, BiometricsModule.class, BouncerViewModule.class, + ClipboardOverlayModule.class, ClockModule.class, CoroutinesModule.class, DreamModule.class, @@ -165,12 +167,16 @@ public abstract class SystemUIModule { @Binds abstract BootCompleteCache bindBootCompleteCache(BootCompleteCacheImpl bootCompleteCache); - /** */ + /** + * + */ @Binds public abstract ContextComponentHelper bindComponentHelper( ContextComponentResolver componentHelper); - /** */ + /** + * + */ @Binds public abstract NotificationRowBinder bindNotificationRowBinder( NotificationRowBinderImpl notificationRowBinder); @@ -209,6 +215,7 @@ public abstract class SystemUIModule { abstract SystemClock bindSystemClock(SystemClockImpl systemClock); // TODO: This should provided by the WM component + /** Provides Optional of BubbleManager */ @SysUISingleton @Provides diff --git a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt index 991b54e8035e..ded0fb79cd6f 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt @@ -59,7 +59,7 @@ class CutoutDecorProviderImpl( (view as? DisplayCutoutView)?.let { cutoutView -> cutoutView.setColor(tintColor) cutoutView.updateRotation(rotation) - cutoutView.onDisplayChanged(displayUniqueId) + cutoutView.updateConfiguration(displayUniqueId) } } } diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt index ec0013bb5000..5fdd198c19d7 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -124,7 +124,7 @@ class FaceScanningOverlayProviderImpl( view.layoutParams = it (view as? FaceScanningOverlay)?.let { overlay -> overlay.setColor(tintColor) - overlay.onDisplayChanged(displayUniqueId) + overlay.updateConfiguration(displayUniqueId) } } } diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt index a25286438387..8b4aeefb6ed4 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt @@ -78,23 +78,18 @@ class RoundedCornerResDelegate( reloadMeasures() } - private fun reloadAll(newReloadToken: Int) { - if (reloadToken == newReloadToken) { - return - } - reloadToken = newReloadToken - reloadRes() - reloadMeasures() - } - fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) { if (displayUniqueId != newDisplayUniqueId) { displayUniqueId = newDisplayUniqueId newReloadToken ?.let { reloadToken = it } reloadRes() reloadMeasures() - } else { - newReloadToken?.let { reloadAll(it) } + } else if (newReloadToken != null) { + if (reloadToken == newReloadToken) { + return + } + reloadToken = newReloadToken + reloadMeasures() } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index cc5766210406..0e1bfba8aadb 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -19,10 +19,10 @@ package com.android.systemui.doze import android.view.Display import com.android.systemui.doze.DozeLog.Reason import com.android.systemui.doze.DozeLog.reasonToString -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.INFO import com.android.systemui.log.dagger.DozeLog import com.android.systemui.statusbar.policy.DevicePostureController import java.text.SimpleDateFormat diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index ae412152b10e..96c35d43052e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -20,7 +20,6 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWA import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; import android.annotation.MainThread; -import android.app.UiModeManager; import android.content.res.Configuration; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Trace; @@ -145,10 +144,9 @@ public class DozeMachine { private final Service mDozeService; private final WakeLock mWakeLock; - private final AmbientDisplayConfiguration mConfig; + private final AmbientDisplayConfiguration mAmbientDisplayConfig; private final WakefulnessLifecycle mWakefulnessLifecycle; private final DozeHost mDozeHost; - private final UiModeManager mUiModeManager; private final DockManager mDockManager; private final Part[] mParts; @@ -156,18 +154,18 @@ public class DozeMachine { private State mState = State.UNINITIALIZED; private int mPulseReason; private boolean mWakeLockHeldForCurrentState = false; + private int mUiModeType = Configuration.UI_MODE_TYPE_NORMAL; @Inject - public DozeMachine(@WrappedService Service service, AmbientDisplayConfiguration config, + public DozeMachine(@WrappedService Service service, + AmbientDisplayConfiguration ambientDisplayConfig, WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle, - UiModeManager uiModeManager, DozeLog dozeLog, DockManager dockManager, DozeHost dozeHost, Part[] parts) { mDozeService = service; - mConfig = config; + mAmbientDisplayConfig = ambientDisplayConfig; mWakefulnessLifecycle = wakefulnessLifecycle; mWakeLock = wakeLock; - mUiModeManager = uiModeManager; mDozeLog = dozeLog; mDockManager = dockManager; mDozeHost = dozeHost; @@ -187,6 +185,18 @@ public class DozeMachine { } /** + * Notifies the {@link DozeMachine} that {@link Configuration} has changed. + */ + public void onConfigurationChanged(Configuration newConfiguration) { + int newUiModeType = newConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK; + if (mUiModeType == newUiModeType) return; + mUiModeType = newUiModeType; + for (Part part : mParts) { + part.onUiModeTypeChanged(mUiModeType); + } + } + + /** * Requests transitioning to {@code requestedState}. * * This can be called during a state transition, in which case it will be queued until all @@ -211,6 +221,14 @@ public class DozeMachine { requestState(State.DOZE_REQUEST_PULSE, pulseReason); } + /** + * @return true if {@link DozeMachine} is currently in either {@link State#UNINITIALIZED} + * or {@link State#FINISH} + */ + public boolean isUninitializedOrFinished() { + return mState == State.UNINITIALIZED || mState == State.FINISH; + } + void onScreenState(int state) { mDozeLog.traceDisplayState(state); for (Part part : mParts) { @@ -360,7 +378,7 @@ public class DozeMachine { if (mState == State.FINISH) { return State.FINISH; } - if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR + if (mUiModeType == Configuration.UI_MODE_TYPE_CAR && (requestedState.canPulse() || requestedState.staysAwake())) { Log.i(TAG, "Doze is suppressed with all triggers disabled as car mode is active"); mDozeLog.traceCarModeStarted(); @@ -411,7 +429,7 @@ public class DozeMachine { nextState = State.FINISH; } else if (mDockManager.isDocked()) { nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED; - } else if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) { + } else if (mAmbientDisplayConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) { nextState = State.DOZE_AOD; } else { nextState = State.DOZE; @@ -427,6 +445,7 @@ public class DozeMachine { /** Dumps the current state */ public void dump(PrintWriter pw) { pw.print(" state="); pw.println(mState); + pw.print(" mUiModeType="); pw.println(mUiModeType); pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState); pw.print(" wakeLock="); pw.println(mWakeLock); pw.println("Parts:"); @@ -459,6 +478,19 @@ public class DozeMachine { /** Sets the {@link DozeMachine} when this Part is associated with one. */ default void setDozeMachine(DozeMachine dozeMachine) {} + + /** + * Notifies the Part about a change in {@link Configuration#uiMode}. + * + * @param newUiModeType {@link Configuration#UI_MODE_TYPE_NORMAL}, + * {@link Configuration#UI_MODE_TYPE_DESK}, + * {@link Configuration#UI_MODE_TYPE_CAR}, + * {@link Configuration#UI_MODE_TYPE_TELEVISION}, + * {@link Configuration#UI_MODE_TYPE_APPLIANCE}, + * {@link Configuration#UI_MODE_TYPE_WATCH}, + * or {@link Configuration#UI_MODE_TYPE_VR_HEADSET} + */ + default void onUiModeTypeChanged(int newUiModeType) {} } /** A wrapper interface for {@link android.service.dreams.DreamService} */ diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index a2eb4e3bb640..e8d7e4642e3e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -17,6 +17,7 @@ package com.android.systemui.doze; import android.content.Context; +import android.content.res.Configuration; import android.os.PowerManager; import android.os.SystemClock; import android.service.dreams.DreamService; @@ -59,6 +60,7 @@ public class DozeService extends DreamService mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */); DozeComponent dozeComponent = mDozeComponentBuilder.build(this); mDozeMachine = dozeComponent.getDozeMachine(); + mDozeMachine.onConfigurationChanged(getResources().getConfiguration()); } @Override @@ -127,6 +129,12 @@ public class DozeService extends DreamService } @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mDozeMachine.onConfigurationChanged(newConfig); + } + + @Override public void onRequestHideDoze() { if (mDozeMachine != null) { mDozeMachine.requestState(DozeMachine.State.DOZE); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java index 7ed4b35e1ee7..e6d98655b119 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java @@ -16,21 +16,13 @@ package com.android.systemui.doze; -import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE; -import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE; - -import android.app.UiModeManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration; +import static android.content.res.Configuration.UI_MODE_TYPE_CAR; + import android.hardware.display.AmbientDisplayConfiguration; import android.os.PowerManager; import android.os.UserHandle; import android.text.TextUtils; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.statusbar.phone.BiometricUnlockController; @@ -43,7 +35,9 @@ import dagger.Lazy; /** * Handles suppressing doze on: * 1. INITIALIZED, don't allow dozing at all when: - * - in CAR_MODE + * - in CAR_MODE, in this scenario the device is asleep and won't listen for any triggers + * to wake up. In this state, no UI shows. Unlike other conditions, this suppression is only + * temporary and stops when the device exits CAR_MODE * - device is NOT provisioned * - there's a pending authentication * 2. PowerSaveMode active @@ -57,35 +51,47 @@ import dagger.Lazy; */ @DozeScope public class DozeSuppressor implements DozeMachine.Part { - private static final String TAG = "DozeSuppressor"; private DozeMachine mMachine; private final DozeHost mDozeHost; private final AmbientDisplayConfiguration mConfig; private final DozeLog mDozeLog; - private final BroadcastDispatcher mBroadcastDispatcher; - private final UiModeManager mUiModeManager; private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; - private boolean mBroadcastReceiverRegistered; + private boolean mIsCarModeEnabled = false; @Inject public DozeSuppressor( DozeHost dozeHost, AmbientDisplayConfiguration config, DozeLog dozeLog, - BroadcastDispatcher broadcastDispatcher, - UiModeManager uiModeManager, Lazy<BiometricUnlockController> biometricUnlockControllerLazy) { mDozeHost = dozeHost; mConfig = config; mDozeLog = dozeLog; - mBroadcastDispatcher = broadcastDispatcher; - mUiModeManager = uiModeManager; mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; } @Override + public void onUiModeTypeChanged(int newUiModeType) { + boolean isCarModeEnabled = newUiModeType == UI_MODE_TYPE_CAR; + if (mIsCarModeEnabled == isCarModeEnabled) { + return; + } + mIsCarModeEnabled = isCarModeEnabled; + // Do not handle the event if doze machine is not initialized yet. + // It will be handled upon initialization. + if (mMachine.isUninitializedOrFinished()) { + return; + } + if (mIsCarModeEnabled) { + handleCarModeStarted(); + } else { + handleCarModeExited(); + } + } + + @Override public void setDozeMachine(DozeMachine dozeMachine) { mMachine = dozeMachine; } @@ -94,7 +100,6 @@ public class DozeSuppressor implements DozeMachine.Part { public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { switch (newState) { case INITIALIZED: - registerBroadcastReceiver(); mDozeHost.addCallback(mHostCallback); checkShouldImmediatelyEndDoze(); checkShouldImmediatelySuspendDoze(); @@ -108,14 +113,12 @@ public class DozeSuppressor implements DozeMachine.Part { @Override public void destroy() { - unregisterBroadcastReceiver(); mDozeHost.removeCallback(mHostCallback); } private void checkShouldImmediatelySuspendDoze() { - if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) { - mDozeLog.traceCarModeStarted(); - mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS); + if (mIsCarModeEnabled) { + handleCarModeStarted(); } } @@ -135,7 +138,7 @@ public class DozeSuppressor implements DozeMachine.Part { @Override public void dump(PrintWriter pw) { - pw.println(" uiMode=" + mUiModeManager.getCurrentModeType()); + pw.println(" isCarModeEnabled=" + mIsCarModeEnabled); pw.println(" hasPendingAuth=" + mBiometricUnlockControllerLazy.get().hasPendingAuthentication()); pw.println(" isProvisioned=" + mDozeHost.isProvisioned()); @@ -143,40 +146,18 @@ public class DozeSuppressor implements DozeMachine.Part { pw.println(" aodPowerSaveActive=" + mDozeHost.isPowerSaveActive()); } - private void registerBroadcastReceiver() { - if (mBroadcastReceiverRegistered) { - return; - } - IntentFilter filter = new IntentFilter(ACTION_ENTER_CAR_MODE); - filter.addAction(ACTION_EXIT_CAR_MODE); - mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter); - mBroadcastReceiverRegistered = true; + private void handleCarModeExited() { + mDozeLog.traceCarModeEnded(); + mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) + ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE); } - private void unregisterBroadcastReceiver() { - if (!mBroadcastReceiverRegistered) { - return; - } - mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); - mBroadcastReceiverRegistered = false; + private void handleCarModeStarted() { + mDozeLog.traceCarModeStarted(); + mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS); } - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (ACTION_ENTER_CAR_MODE.equals(action)) { - mDozeLog.traceCarModeStarted(); - mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS); - } else if (ACTION_EXIT_CAR_MODE.equals(action)) { - mDozeLog.traceCarModeEnded(); - mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) - ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE); - } - } - }; - - private DozeHost.Callback mHostCallback = new DozeHost.Callback() { + private final DozeHost.Callback mHostCallback = new DozeHost.Callback() { @Override public void onPowerSaveChanged(boolean active) { // handles suppression changes, while DozeMachine#transitionPolicy handles gating diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java index 99ca3c76cf8d..d145f5c14917 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java @@ -40,11 +40,12 @@ import javax.inject.Inject; * {@link DreamOverlayRegistrant} is responsible for telling system server that SystemUI should be * the designated dream overlay component. */ -public class DreamOverlayRegistrant extends CoreStartable { +public class DreamOverlayRegistrant implements CoreStartable { private static final String TAG = "DreamOverlayRegistrant"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final IDreamManager mDreamManager; private final ComponentName mOverlayServiceComponent; + private final Context mContext; private final Resources mResources; private boolean mCurrentRegisteredState = false; @@ -98,7 +99,7 @@ public class DreamOverlayRegistrant extends CoreStartable { @Inject public DreamOverlayRegistrant(Context context, @Main Resources resources) { - super(context); + mContext = context; mResources = resources; mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.getService(DreamService.DREAM_SERVICE)); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java index bbcab60d7ba2..ee2f1af6a99b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java @@ -16,7 +16,6 @@ package com.android.systemui.dreams.complication; -import android.content.Context; import android.database.ContentObserver; import android.os.UserHandle; import android.provider.Settings; @@ -37,7 +36,7 @@ import javax.inject.Inject; * user, and pushes updates to {@link DreamOverlayStateController}. */ @SysUISingleton -public class ComplicationTypesUpdater extends CoreStartable { +public class ComplicationTypesUpdater implements CoreStartable { private final DreamBackend mDreamBackend; private final Executor mExecutor; private final SecureSettings mSecureSettings; @@ -45,13 +44,11 @@ public class ComplicationTypesUpdater extends CoreStartable { private final DreamOverlayStateController mDreamOverlayStateController; @Inject - ComplicationTypesUpdater(Context context, + ComplicationTypesUpdater( DreamBackend dreamBackend, @Main Executor executor, SecureSettings secureSettings, DreamOverlayStateController dreamOverlayStateController) { - super(context); - mDreamBackend = dreamBackend; mExecutor = executor; mSecureSettings = secureSettings; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java index 675a2f46d310..77e1fc91e6ee 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java @@ -19,7 +19,6 @@ package com.android.systemui.dreams.complication; import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW; import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS; -import android.content.Context; import android.view.View; import com.android.systemui.CoreStartable; @@ -61,7 +60,7 @@ public class DreamClockTimeComplication implements Complication { * {@link CoreStartable} responsible for registering {@link DreamClockTimeComplication} with * SystemUI. */ - public static class Registrant extends CoreStartable { + public static class Registrant implements CoreStartable { private final DreamOverlayStateController mDreamOverlayStateController; private final DreamClockTimeComplication mComplication; @@ -69,10 +68,9 @@ public class DreamClockTimeComplication implements Complication { * Default constructor to register {@link DreamClockTimeComplication}. */ @Inject - public Registrant(Context context, + public Registrant( DreamOverlayStateController dreamOverlayStateController, DreamClockTimeComplication dreamClockTimeComplication) { - super(context); mDreamOverlayStateController = dreamOverlayStateController; mComplication = dreamClockTimeComplication; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java index 821e13ef733c..cedd850ac2ef 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java @@ -71,7 +71,7 @@ public class DreamHomeControlsComplication implements Complication { /** * {@link CoreStartable} for registering the complication with SystemUI on startup. */ - public static class Registrant extends CoreStartable { + public static class Registrant implements CoreStartable { private final DreamHomeControlsComplication mComplication; private final DreamOverlayStateController mDreamOverlayStateController; private final ControlsComponent mControlsComponent; @@ -90,11 +90,9 @@ public class DreamHomeControlsComplication implements Complication { }; @Inject - public Registrant(Context context, DreamHomeControlsComplication complication, + public Registrant(DreamHomeControlsComplication complication, DreamOverlayStateController dreamOverlayStateController, ControlsComponent controlsComponent) { - super(context); - mComplication = complication; mControlsComponent = controlsComponent; mDreamOverlayStateController = dreamOverlayStateController; @@ -212,7 +210,8 @@ public class DreamHomeControlsComplication implements Complication { final Intent intent = new Intent(mContext, ControlsActivity.class) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra(ControlsUiController.EXTRA_ANIMATE, true); + .putExtra(ControlsUiController.EXTRA_ANIMATE, true) + .putExtra(ControlsUiController.EXIT_TO_DREAM, true); final ActivityLaunchAnimator.Controller controller = v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */) diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java index a981f255a873..c3aaf0cbf2d7 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java @@ -61,7 +61,7 @@ public class SmartSpaceComplication implements Complication { * {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with * SystemUI. */ - public static class Registrant extends CoreStartable { + public static class Registrant implements CoreStartable { private final DreamSmartspaceController mSmartSpaceController; private final DreamOverlayStateController mDreamOverlayStateController; private final SmartSpaceComplication mComplication; @@ -78,11 +78,10 @@ public class SmartSpaceComplication implements Complication { * Default constructor for {@link SmartSpaceComplication}. */ @Inject - public Registrant(Context context, + public Registrant( DreamOverlayStateController dreamOverlayStateController, SmartSpaceComplication smartSpaceComplication, DreamSmartspaceController smartSpaceController) { - super(context); mDreamOverlayStateController = dreamOverlayStateController; mComplication = smartSpaceComplication; mSmartSpaceController = smartSpaceController; diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt index 08ef8f3d025f..478f86169718 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt @@ -24,7 +24,7 @@ import com.android.systemui.R import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL -import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager import java.io.PrintWriter import javax.inject.Inject @@ -235,6 +235,7 @@ class DumpHandler @Inject constructor( pw.println("$ <invocation> buffers") pw.println("$ <invocation> bugreport-critical") pw.println("$ <invocation> bugreport-normal") + pw.println("$ <invocation> config") pw.println() pw.println("Targets can be listed:") @@ -313,13 +314,21 @@ class DumpHandler @Inject constructor( const val PRIORITY_ARG_CRITICAL = "CRITICAL" const val PRIORITY_ARG_HIGH = "HIGH" const val PRIORITY_ARG_NORMAL = "NORMAL" + const val PROTO = "--sysui_proto" } } private val PRIORITY_OPTIONS = arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL) -private val COMMANDS = arrayOf("bugreport-critical", "bugreport-normal", "buffers", "dumpables") +private val COMMANDS = arrayOf( + "bugreport-critical", + "bugreport-normal", + "buffers", + "dumpables", + "config", + "help" +) private class ParsedArgs( val rawArgs: Array<String>, diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt index cca04da8f426..dbca65122fcb 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt @@ -18,7 +18,7 @@ package com.android.systemui.dump import android.util.ArrayMap import com.android.systemui.Dumpable -import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.log.LogBuffer import java.io.PrintWriter import javax.inject.Inject import javax.inject.Singleton diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt index 0eab1afc4119..8299b13d305f 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt @@ -19,7 +19,7 @@ package com.android.systemui.dump import android.content.Context import android.util.Log import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.util.io.Files import com.android.systemui.util.time.SystemClock import java.io.IOException diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt index c0e30211e018..560dcbd78c42 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt @@ -16,9 +16,7 @@ package com.android.systemui.flags -import android.content.Context import com.android.systemui.CoreStartable -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.commandline.CommandRegistry import dagger.Binds @@ -30,12 +28,11 @@ import javax.inject.Inject class FeatureFlagsDebugStartable @Inject constructor( - @Application context: Context, dumpManager: DumpManager, private val commandRegistry: CommandRegistry, private val flagCommand: FlagCommand, featureFlags: FeatureFlags -) : CoreStartable(context) { +) : CoreStartable { init { dumpManager.registerDumpable(FeatureFlagsDebug.TAG) { pw, args -> diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt index f138f1e8aa79..e7d8cc362c56 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt @@ -16,9 +16,7 @@ package com.android.systemui.flags -import android.content.Context import com.android.systemui.CoreStartable -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager import dagger.Binds import dagger.Module @@ -28,8 +26,7 @@ import javax.inject.Inject class FeatureFlagsReleaseStartable @Inject -constructor(@Application context: Context, dumpManager: DumpManager, featureFlags: FeatureFlags) : - CoreStartable(context) { +constructor(dumpManager: DumpManager, featureFlags: FeatureFlags) : CoreStartable { init { dumpManager.registerDumpable(FeatureFlagsRelease.TAG) { pw, args -> diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 3868b6f063e8..027187c889b4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -45,35 +45,45 @@ public class Flags { /***************************************/ // 100 - notification + // TODO(b/254512751): Tracking Bug public static final UnreleasedFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = new UnreleasedFlag(103); + // TODO(b/254512732): Tracking Bug public static final UnreleasedFlag NSSL_DEBUG_LINES = new UnreleasedFlag(105); + // TODO(b/254512505): Tracking Bug public static final UnreleasedFlag NSSL_DEBUG_REMOVE_ANIMATION = new UnreleasedFlag(106); - public static final UnreleasedFlag NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE = - new UnreleasedFlag(107); - + // TODO(b/254512624): Tracking Bug public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS = new ResourceBooleanFlag(108, R.bool.config_notificationToContents); + // TODO(b/254512703): Tracking Bug public static final ReleasedFlag REMOVE_UNRANKED_NOTIFICATIONS = new ReleasedFlag(109); + // TODO(b/254512517): Tracking Bug public static final UnreleasedFlag FSI_REQUIRES_KEYGUARD = new UnreleasedFlag(110, true); + // TODO(b/254512538): Tracking Bug public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true); + // TODO(b/254512425): Tracking Bug public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112, false); + // TODO(b/254512731): Tracking Bug public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true); - // next id: 114 + public static final UnreleasedFlag STABILITY_INDEX_FIX = new UnreleasedFlag(114, true); + + public static final UnreleasedFlag SEMI_STABLE_SORT = new UnreleasedFlag(115, true); + + // next id: 116 /***************************************/ // 200 - keyguard/lockscreen @@ -82,39 +92,42 @@ public class Flags { // public static final BooleanFlag KEYGUARD_LAYOUT = // new BooleanFlag(200, true); + // TODO(b/254512713): Tracking Bug public static final ReleasedFlag LOCKSCREEN_ANIMATIONS = new ReleasedFlag(201); + // TODO(b/254512750): Tracking Bug public static final ReleasedFlag NEW_UNLOCK_SWIPE_ANIMATION = new ReleasedFlag(202); public static final ResourceBooleanFlag CHARGING_RIPPLE = new ResourceBooleanFlag(203, R.bool.flag_charging_ripple); + // TODO(b/254512281): Tracking Bug public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER = new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher); + // TODO(b/254512694): Tracking Bug public static final ResourceBooleanFlag FACE_SCANNING_ANIM = new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation); + // TODO(b/254512676): Tracking Bug public static final UnreleasedFlag LOCKSCREEN_CUSTOM_CLOCKS = new UnreleasedFlag(207); /** * 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 public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208); - /** Whether UserSwitcherActivity should use modern architecture. */ - public static final ReleasedFlag MODERN_USER_SWITCHER_ACTIVITY = - new ReleasedFlag(209, true); - /** * Whether the user interactor and repository should use `UserSwitcherController`. * * <p>If this is {@code false}, the interactor and repo skip the controller and directly access * the framework APIs. */ + // TODO(b/254513286): Tracking Bug public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER = new UnreleasedFlag(210); @@ -127,18 +140,22 @@ public class Flags { * <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is * {@code true} as it would created a cycle between controller -> interactor -> controller. */ + // TODO(b/254513102): Tracking Bug public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211); /***************************************/ // 300 - power menu + // TODO(b/254512600): Tracking Bug public static final ReleasedFlag POWER_MENU_LITE = new ReleasedFlag(300); /***************************************/ // 400 - smartspace + // TODO(b/254513080): Tracking Bug public static final ReleasedFlag SMARTSPACE_DEDUPING = new ReleasedFlag(400); + // TODO(b/254513100): Tracking Bug public static final ReleasedFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = new ReleasedFlag(401); @@ -154,6 +171,7 @@ public class Flags { public static final ReleasedFlag NEW_USER_SWITCHER = new ReleasedFlag(500); + // TODO(b/254512321): Tracking Bug public static final UnreleasedFlag COMBINED_QS_HEADERS = new UnreleasedFlag(501, true); @@ -166,37 +184,48 @@ public class Flags { /** * @deprecated Not needed anymore */ + // TODO(b/254512699): Tracking Bug @Deprecated public static final ReleasedFlag NEW_FOOTER = new ReleasedFlag(504); + // TODO(b/254512747): Tracking Bug public static final UnreleasedFlag NEW_HEADER = new UnreleasedFlag(505, true); + // TODO(b/254512383): Tracking Bug public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER = new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher); + // TODO(b/254512678): Tracking Bug public static final ReleasedFlag NEW_FOOTER_ACTIONS = new ReleasedFlag(507); /***************************************/ // 600- status bar + // TODO(b/254513246): Tracking Bug public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER = new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip); + // TODO(b/254513025): Tracking Bug public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE = new ReleasedFlag(603, false); + // TODO(b/254512623): Tracking Bug public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_BACKEND = new UnreleasedFlag(604, false); + // TODO(b/254512660): Tracking Bug public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_FRONTEND = new UnreleasedFlag(605, false); /***************************************/ // 700 - dialer/calls + // TODO(b/254512734): Tracking Bug public static final ReleasedFlag ONGOING_CALL_STATUS_BAR_CHIP = new ReleasedFlag(700); + // TODO(b/254512681): Tracking Bug public static final ReleasedFlag ONGOING_CALL_IN_IMMERSIVE = new ReleasedFlag(701); + // TODO(b/254512753): Tracking Bug public static final ReleasedFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = new ReleasedFlag(702); @@ -207,30 +236,49 @@ public class Flags { /***************************************/ // 801 - region sampling + // TODO(b/254512848): Tracking Bug public static final UnreleasedFlag REGION_SAMPLING = new UnreleasedFlag(801); // 802 - wallpaper rendering + // TODO(b/254512923): Tracking Bug public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802, true); // 803 - screen contents translation + // TODO(b/254513187): Tracking Bug public static final UnreleasedFlag SCREEN_CONTENTS_TRANSLATION = new UnreleasedFlag(803); + // 804 - monochromatic themes + public static final UnreleasedFlag MONOCHROMATIC_THEMES = new UnreleasedFlag(804); + /***************************************/ // 900 - media + // TODO(b/254512697): Tracking Bug public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900); + // TODO(b/254512502): Tracking Bug public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901); + // TODO(b/254512726): Tracking Bug public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903); + // TODO(b/254512695): Tracking Bug public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904); + // TODO(b/254512654): Tracking Bug public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905); + // TODO(b/254512673): Tracking Bug public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906); + // TODO(b/254513168): Tracking Bug + public static final UnreleasedFlag UMO_SURFACE_RIPPLE = new UnreleasedFlag(907); // 1000 - dock public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING = new ReleasedFlag(1000); + + // TODO(b/254512444): Tracking Bug public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001); - public static final UnreleasedFlag ROUNDED_BOX_RIPPLE = - new UnreleasedFlag(1002, /* teamfood= */ true); + // TODO(b/254512758): Tracking Bug + public static final ReleasedFlag ROUNDED_BOX_RIPPLE = new ReleasedFlag(1002); + + // TODO(b/254512525): Tracking Bug + public static final UnreleasedFlag REFACTORED_DOCK_SETUP = new UnreleasedFlag(1003, true); // 1100 - windowing @Keep @@ -244,11 +292,13 @@ public class Flags { public static final SysPropBooleanFlag BUBBLES_HOME_GESTURE = new SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true); + // TODO(b/254513207): Tracking Bug @Keep public static final DeviceConfigBooleanFlag WM_ENABLE_PARTIAL_SCREEN_SHARING = new DeviceConfigBooleanFlag(1102, "record_task_content", NAMESPACE_WINDOW_MANAGER, false, true); + // TODO(b/254512674): Tracking Bug @Keep public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW = new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false); @@ -291,20 +341,28 @@ public class Flags { public static final SysPropBooleanFlag WM_ALWAYS_ENFORCE_PREDICTIVE_BACK = new SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false); + // TODO(b/254512728): Tracking Bug public static final UnreleasedFlag NEW_BACK_AFFORDANCE = new UnreleasedFlag(1203, false /* teamfood */); // 1300 - screenshots + // TODO(b/254512719): Tracking Bug public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300); + // TODO(b/254513155): Tracking Bug public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301); // 1400 - columbus + // TODO(b/254512756): Tracking Bug public static final ReleasedFlag QUICK_TAP_IN_PCC = new ReleasedFlag(1400); // 1500 - chooser + // TODO(b/254512507): Tracking Bug public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500); + // 1700 - clipboard + public static final UnreleasedFlag CLIPBOARD_OVERLAY_REFACTOR = new UnreleasedFlag(1700); + // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== // | | diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java index 74d5bd577cf4..9f321d83d292 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java @@ -36,8 +36,7 @@ import javax.inject.Provider; * Manages power menu plugins and communicates power menu actions to the CentralSurfaces. */ @SysUISingleton -public class GlobalActionsComponent extends CoreStartable - implements Callbacks, GlobalActionsManager { +public class GlobalActionsComponent implements CoreStartable, Callbacks, GlobalActionsManager { private final CommandQueue mCommandQueue; private final ExtensionController mExtensionController; @@ -48,11 +47,10 @@ public class GlobalActionsComponent extends CoreStartable private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Inject - public GlobalActionsComponent(Context context, CommandQueue commandQueue, + public GlobalActionsComponent(CommandQueue commandQueue, ExtensionController extensionController, Provider<GlobalActions> globalActionsProvider, StatusBarKeyguardViewManager statusBarKeyguardViewManager) { - super(context); mCommandQueue = commandQueue; mExtensionController = extensionController; mGlobalActionsProvider = globalActionsProvider; diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index 568143c8df71..4f1a2b34f07c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -27,7 +27,6 @@ import android.bluetooth.le.ScanSettings; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; -import android.content.res.Configuration; import android.hardware.input.InputManager; import android.os.Handler; import android.os.HandlerThread; @@ -66,7 +65,7 @@ import javax.inject.Provider; /** */ @SysUISingleton -public class KeyboardUI extends CoreStartable implements InputManager.OnTabletModeChangedListener { +public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChangedListener { private static final String TAG = "KeyboardUI"; private static final boolean DEBUG = false; @@ -127,13 +126,12 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo @Inject public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider) { - super(context); + mContext = context; this.mBluetoothManagerProvider = bluetoothManagerProvider; } @Override public void start() { - mContext = super.mContext; HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mHandler = new KeyboardHandler(thread.getLooper()); @@ -141,10 +139,6 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo } @Override - protected void onConfigurationChanged(Configuration newConfig) { - } - - @Override public void dump(PrintWriter pw, String[] args) { pw.println("KeyboardUI:"); pw.println(" mEnabled=" + mEnabled); @@ -156,7 +150,7 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo } @Override - protected void onBootCompleted() { + public void onBootCompleted() { mHandler.sendEmptyMessage(MSG_ON_BOOT_COMPLETED); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 9a118e018bfd..a3632705d865 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -186,7 +186,7 @@ import dagger.Lazy; * directly to the keyguard UI is posted to a {@link android.os.Handler} to ensure it is taken on the UI * thread of the keyguard. */ -public class KeyguardViewMediator extends CoreStartable implements Dumpable, +public class KeyguardViewMediator implements CoreStartable, Dumpable, StatusBarStateController.StateListener { private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000; @@ -272,6 +272,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private boolean mShuttingDown; private boolean mDozing; private boolean mAnimatingScreenOff; + private final Context mContext; private final FalsingCollector mFalsingCollector; /** High level access to the power manager for WakeLocks */ @@ -793,6 +794,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker = mUpdateMonitor.getStrongAuthTracker(); int strongAuth = strongAuthTracker.getStrongAuthForUser(currentUser); + boolean allowedNonStrongAfterIdleTimeout = + strongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(currentUser); if (any && !strongAuthTracker.hasUserAuthenticatedSinceBoot()) { return KeyguardSecurityView.PROMPT_REASON_RESTART; @@ -811,6 +814,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT) != 0) { return KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT; + } else if (any && !allowedNonStrongAfterIdleTimeout) { + return KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT; } return KeyguardSecurityView.PROMPT_REASON_NONE; } @@ -1128,7 +1133,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, DreamOverlayStateController dreamOverlayStateController, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy, Lazy<ActivityLaunchAnimator> activityLaunchAnimator) { - super(context); + mContext = context; mFalsingCollector = falsingCollector; mLockPatternUtils = lockPatternUtils; mBroadcastDispatcher = broadcastDispatcher; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 56f1ac46a875..56a1f1ae936e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -43,6 +43,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule; +import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule; import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -72,6 +73,7 @@ import dagger.Provides; FalsingModule.class, KeyguardQuickAffordanceModule.class, KeyguardRepositoryModule.class, + StartKeyguardTransitionModule.class, }) public class KeyguardModule { /** 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 45b668e609ea..b186ae0ceec4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -21,6 +21,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.doze.DozeHost +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject @@ -85,6 +86,9 @@ interface KeyguardRepository { */ val dozeAmount: Flow<Float> + /** Observable for the [StatusBarState] */ + val statusBarState: Flow<StatusBarState> + /** * Returns `true` if the keyguard is showing; `false` otherwise. * @@ -185,6 +189,24 @@ constructor( return keyguardStateController.isShowing } + override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow { + val callback = + object : StatusBarStateController.StateListener { + override fun onStateChanged(state: Int) { + trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state") + } + } + + statusBarStateController.addCallback(callback) + trySendWithFailureLogging( + statusBarStateIntToObject(statusBarStateController.getState()), + TAG, + "initial state" + ) + + awaitClose { statusBarStateController.removeCallback(callback) } + } + override fun setAnimateDozingTransitions(animate: Boolean) { _animateBottomAreaDozingTransitions.value = animate } @@ -197,6 +219,15 @@ constructor( _clockPosition.value = Position(x, y) } + private fun statusBarStateIntToObject(value: Int): StatusBarState { + return when (value) { + 0 -> StatusBarState.SHADE + 1 -> StatusBarState.KEYGUARD + 2 -> StatusBarState.SHADE_LOCKED + else -> throw IllegalArgumentException("Invalid StatusBarState value: $value") + } + } + companion object { private const val TAG = "KeyguardRepositoryImpl" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt new file mode 100644 index 000000000000..e8532ecfdc37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -0,0 +1,169 @@ +/* + * 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.repository + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.animation.ValueAnimator.AnimatorUpdateListener +import android.annotation.FloatRange +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.KeyguardState +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 javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filter + +@SysUISingleton +class KeyguardTransitionRepository @Inject constructor() { + /* + * Each transition between [KeyguardState]s will have an associated Flow. + * In order to collect these events, clients should call [transition]. + */ + private val _transitions = MutableStateFlow(TransitionStep()) + val transitions = _transitions.asStateFlow() + + /* Information about the active transition. */ + private var currentTransitionInfo: TransitionInfo? = null + /* + * When manual control of the transition is requested, a unique [UUID] is used as the handle + * to permit calls to [updateTransition] + */ + private var updateTransitionId: UUID? = null + + /** + * Interactors that require information about changes between [KeyguardState]s will call this to + * register themselves for flowable [TransitionStep]s when that transition occurs. + */ + fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> { + return transitions.filter { step -> step.from == from && step.to == to } + } + + /** + * Begin a transition from one state to another. The [info.from] must match + * [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid + * unplanned transitions. + */ + fun startTransition(info: TransitionInfo): UUID? { + if (currentTransitionInfo != null) { + // Open questions: + // * Queue of transitions? buffer of 1? + // * Are transitions cancellable if a new one is triggered? + // * What validation does this need to do? + Log.wtf(TAG, "Transition still active: $currentTransitionInfo") + return null + } + currentTransitionInfo?.animator?.cancel() + + currentTransitionInfo = info + info.animator?.let { animator -> + // An animator was provided, so use it to run the transition + animator.setFloatValues(0f, 1f) + val updateListener = + object : AnimatorUpdateListener { + override fun onAnimationUpdate(animation: ValueAnimator) { + emitTransition( + info, + (animation.getAnimatedValue() as Float), + TransitionState.RUNNING + ) + } + } + val adapter = + object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator) { + Log.i(TAG, "Starting transition: $info") + emitTransition(info, 0f, TransitionState.STARTED) + } + override fun onAnimationCancel(animation: Animator) { + Log.i(TAG, "Cancelling transition: $info") + } + override fun onAnimationEnd(animation: Animator) { + Log.i(TAG, "Ending transition: $info") + emitTransition(info, 1f, TransitionState.FINISHED) + animator.removeListener(this) + animator.removeUpdateListener(updateListener) + } + } + animator.addListener(adapter) + animator.addUpdateListener(updateListener) + animator.start() + return@startTransition null + } + ?: run { + Log.i(TAG, "Starting transition (manual): $info") + emitTransition(info, 0f, TransitionState.STARTED) + + // No animator, so it's manual. Provide a mechanism to callback + updateTransitionId = UUID.randomUUID() + return@startTransition updateTransitionId + } + } + + /** + * Allows manual control of a transition. When calling [startTransition], the consumer must pass + * in a null animator. In return, it will get a unique [UUID] that will be validated to allow + * further updates. + * + * When the transition is over, TransitionState.FINISHED must be passed into the [state] + * parameter. + */ + fun updateTransition( + transitionId: UUID, + @FloatRange(from = 0.0, to = 1.0) value: Float, + state: TransitionState + ) { + if (updateTransitionId != transitionId) { + Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId") + return + } + + if (currentTransitionInfo == null) { + Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'") + return + } + + currentTransitionInfo?.let { info -> + if (state == TransitionState.FINISHED) { + updateTransitionId = null + Log.i(TAG, "Ending transition: $info") + } + + emitTransition(info, value, state) + } + } + + private fun emitTransition( + info: TransitionInfo, + value: Float, + transitionState: TransitionState + ) { + if (transitionState == TransitionState.FINISHED) { + currentTransitionInfo = null + } + _transitions.value = TransitionStep(info.from, info.to, value, transitionState) + } + + companion object { + private const val TAG = "KeyguardTransitionRepository" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt new file mode 100644 index 000000000000..400376663f1a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class AodLockscreenTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardRepository: KeyguardRepository, + private val keyguardTransitionRepository: KeyguardTransitionRepository, +) : TransitionInteractor("AOD<->LOCKSCREEN") { + + override fun start() { + scope.launch { + keyguardRepository.isDozing.collect { isDozing -> + if (isDozing) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.AOD, + getAnimator(), + ) + ) + } else { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.AOD, + KeyguardState.LOCKSCREEN, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 500L + } +} 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 192919e32cf6..fc2269c6b01c 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 @@ -38,7 +38,7 @@ constructor( val dozeAmount: Flow<Float> = repository.dozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing - /** Whether the keyguard is showing ot not. */ + /** Whether the keyguard is showing to not. */ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing fun isKeyguardShowing(): Boolean { 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 01cd3e28472e..f663b0dd23cd 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 @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Intent import com.android.internal.widget.LockPatternUtils -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition @@ -67,19 +67,19 @@ constructor( * * @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of * the affordance that was clicked - * @param animationController An optional controller for the activity-launch animation + * @param expandable An optional [Expandable] for the activity- or dialog-launch animation */ fun onQuickAffordanceClicked( configKey: KClass<out KeyguardQuickAffordanceConfig>, - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ) { @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>) - when (val result = config.onQuickAffordanceClicked(animationController)) { + when (val result = config.onQuickAffordanceClicked(expandable)) { is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity -> launchQuickAffordance( intent = result.intent, canShowWhileLocked = result.canShowWhileLocked, - animationController = animationController + expandable = expandable, ) is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit } @@ -104,6 +104,7 @@ constructor( KeyguardQuickAffordanceModel.Visible( configKey = configs[index]::class, icon = visibleState.icon, + toggle = visibleState.toggle, ) } else { KeyguardQuickAffordanceModel.Hidden @@ -114,7 +115,7 @@ constructor( private fun launchQuickAffordance( intent: Intent, canShowWhileLocked: Boolean, - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ) { @LockPatternUtils.StrongAuthTracker.StrongAuthFlags val strongAuthFlags = @@ -130,13 +131,13 @@ constructor( activityStarter.postStartActivityDismissingKeyguard( intent, 0 /* delay */, - animationController + expandable?.activityLaunchController(), ) } else { activityStarter.startActivity( intent, true /* dismissShade */, - animationController, + expandable?.activityLaunchController(), true /* showOverLockscreenWhenLocked */, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt new file mode 100644 index 000000000000..b166681433a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.keyguard.domain.interactor + +import android.util.Log +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import java.util.Set +import javax.inject.Inject + +@SysUISingleton +class KeyguardTransitionCoreStartable +@Inject +constructor( + private val interactors: Set<TransitionInteractor>, +) : CoreStartable { + + override fun start() { + // By listing the interactors in a when, the compiler will help enforce all classes + // extending the sealed class [TransitionInteractor] will be initialized. + interactors.forEach { + // `when` needs to be an expression in order for the compiler to enforce it being + // exhaustive + val ret = + when (it) { + is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it") + is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it") + } + it.start() + } + } + + companion object { + private const val TAG = "KeyguardTransitionCoreStartable" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt new file mode 100644 index 000000000000..59bb22786917 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -0,0 +1,37 @@ +/* + * 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.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.TransitionStep +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** Encapsulates business-logic related to the keyguard transitions. */ +@SysUISingleton +class KeyguardTransitionInteractor +@Inject +constructor( + repository: KeyguardTransitionRepository, +) { + /** AOD->LOCKSCREEN transition information. */ + val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt new file mode 100644 index 000000000000..3c2a12e3836a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.util.kotlin.sample +import java.util.UUID +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +@SysUISingleton +class LockscreenBouncerTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardRepository: KeyguardRepository, + private val shadeRepository: ShadeRepository, + private val keyguardTransitionRepository: KeyguardTransitionRepository, +) : TransitionInteractor("LOCKSCREEN<->BOUNCER") { + + private var transitionId: UUID? = null + + override fun start() { + scope.launch { + shadeRepository.shadeModel.sample( + combine( + keyguardTransitionRepository.transitions, + keyguardRepository.statusBarState, + ) { transitions, statusBarState -> + Pair(transitions, statusBarState) + } + ) { shadeModel, pair -> + val (transitions, statusBarState) = pair + + val id = transitionId + if (id != null) { + // An existing `id` means a transition is started, and calls to + // `updateTransition` will control it until FINISHED + keyguardTransitionRepository.updateTransition( + id, + shadeModel.expansionAmount, + if (shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f) { + transitionId = null + TransitionState.FINISHED + } else { + TransitionState.RUNNING + } + ) + } else { + // TODO (b/251849525): Remove statusbarstate check when that state is integrated + // into KeyguardTransitionRepository + val isOnLockscreen = + transitions.transitionState == TransitionState.FINISHED && + transitions.to == KeyguardState.LOCKSCREEN + if ( + isOnLockscreen && + shadeModel.isUserDragging && + statusBarState != SHADE_LOCKED + ) { + transitionId = + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.BOUNCER, + animator = null, + ) + ) + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt new file mode 100644 index 000000000000..74c542c0043f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt @@ -0,0 +1,42 @@ +/* + * 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.domain.interactor + +import com.android.systemui.CoreStartable +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import dagger.multibindings.IntoSet + +@Module +abstract class StartKeyguardTransitionModule { + + @Binds + @IntoMap + @ClassKey(KeyguardTransitionCoreStartable::class) + abstract fun bind(impl: KeyguardTransitionCoreStartable): CoreStartable + + @Binds + @IntoSet + abstract fun lockscreenBouncer( + impl: LockscreenBouncerTransitionInteractor + ): TransitionInteractor + + @Binds + @IntoSet + abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt new file mode 100644 index 000000000000..a2a46d9e3a71 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -0,0 +1,32 @@ +/* + * 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.domain.interactor +/** + * Each TransitionInteractor is responsible for determining under which conditions to notify + * [KeyguardTransitionRepository] to signal a transition. When (and if) the transition occurs is + * determined by [KeyguardTransitionRepository]. + * + * [name] field should be a unique identifiable string representing this state, used primarily for + * logging + * + * MUST list implementing classes in dagger module [StartKeyguardTransitionModule] and also in the + * 'when' clause of [KeyguardTransitionCoreStartable] + */ +sealed class TransitionInteractor(val name: String) { + + abstract fun start() +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt index eb6bb92dd912..e56b25967910 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.model import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState import kotlin.reflect.KClass /** @@ -35,5 +36,7 @@ sealed class KeyguardQuickAffordanceModel { val configKey: KClass<out KeyguardQuickAffordanceConfig>, /** An icon for the affordance. */ val icon: Icon, + /** The toggle state for the affordance. */ + val toggle: KeyguardQuickAffordanceToggleState, ) : KeyguardQuickAffordanceModel() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt index 89604f054f9b..83842602cbee 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt @@ -20,7 +20,7 @@ package com.android.systemui.keyguard.domain.quickaffordance import android.content.Context import android.content.Intent import androidx.annotation.DrawableRes -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription @@ -61,7 +61,7 @@ constructor( } override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ): KeyguardQuickAffordanceConfig.OnClickedResult { return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity( intent = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt index 8e1c6b76079f..95027d00c46c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt @@ -18,8 +18,9 @@ package com.android.systemui.keyguard.domain.quickaffordance import android.content.Intent -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState import kotlinx.coroutines.flow.Flow /** Defines interface that can act as data source for a single quick affordance model. */ @@ -27,9 +28,7 @@ interface KeyguardQuickAffordanceConfig { val state: Flow<State> - fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller? - ): OnClickedResult + fun onQuickAffordanceClicked(expandable: Expandable?): OnClickedResult /** * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a @@ -44,6 +43,9 @@ interface KeyguardQuickAffordanceConfig { data class Visible( /** An icon for the affordance. */ val icon: Icon, + /** The toggle state for the affordance. */ + val toggle: KeyguardQuickAffordanceToggleState = + KeyguardQuickAffordanceToggleState.NotSupported, ) : State() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt index d97deaf3b69e..502a6070a422 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.domain.quickaffordance import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription @@ -66,7 +66,7 @@ constructor( } override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ): KeyguardQuickAffordanceConfig.OnClickedResult { return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity( intent = controller.intent, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index 9196b0920bb4..a24a0d62465f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -23,7 +23,7 @@ import android.service.quickaccesswallet.GetWalletCardsResponse import android.service.quickaccesswallet.QuickAccessWalletClient import android.util.Log import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription @@ -84,11 +84,11 @@ constructor( } override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ): KeyguardQuickAffordanceConfig.OnClickedResult { walletController.startQuickAccessUiIntent( activityStarter, - animationController, + expandable?.activityLaunchController(), /* hasCard= */ true, ) return KeyguardQuickAffordanceConfig.OnClickedResult.Handled diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt new file mode 100644 index 000000000000..f66d5d3650c8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.keyguard.shared.model + +/** List of all possible states to transition to/from */ +enum class KeyguardState { + /** For initialization only */ + NONE, + /* Always-on Display. The device is in a low-power mode with a minimal UI visible */ + AOD, + /* + * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS + * (Fingerprint Sensor) variations, for the user to verify their credentials + */ + BOUNCER, + /* + * Device is actively displaying keyguard UI and is not in low-power mode. Device may be + * unlocked if SWIPE security method is used, or if face lockscreen bypass is false. + */ + LOCKSCREEN, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt new file mode 100644 index 000000000000..bb953477583d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt @@ -0,0 +1,23 @@ +/* + * 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.shared.model + +/** See [com.android.systemui.statusbar.StatusBarState] for definitions */ +enum class StatusBarState { + SHADE, + KEYGUARD, + SHADE_LOCKED, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt new file mode 100644 index 000000000000..bfccf3fe076c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt @@ -0,0 +1,35 @@ +/* + * 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.shared.model + +import android.animation.ValueAnimator + +/** Tracks who is controlling the current transition, and how to run it. */ +data class TransitionInfo( + val ownerName: String, + val from: KeyguardState, + val to: KeyguardState, + val animator: ValueAnimator?, // 'null' animator signal manual control +) { + override fun toString(): String = + "TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " + + (if (animator != null) { + "animated" + } else { + "manual" + }) + + ")" +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt new file mode 100644 index 000000000000..d8691c17f53d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt @@ -0,0 +1,24 @@ +/* + * 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.shared.model + +/** Possible states for a running transition between [State] */ +enum class TransitionState { + NONE, + STARTED, + RUNNING, + FINISHED +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt new file mode 100644 index 000000000000..688ec912aac8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt @@ -0,0 +1,24 @@ +/* + * 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.shared.model + +/** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */ +data class TransitionStep( + val from: KeyguardState = KeyguardState.NONE, + val to: KeyguardState = KeyguardState.NONE, + val value: Float = 0f, // constrained [0.0, 1.0] + val transitionState: TransitionState = TransitionState.NONE, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt new file mode 100644 index 000000000000..55d38a41849d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt @@ -0,0 +1,28 @@ +/* + * 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.shared.quickaffordance + +/** Enumerates all possible toggle states for a quick affordance on the lock-screen. */ +sealed class KeyguardQuickAffordanceToggleState { + /** Toggling is not supported. */ + object NotSupported : KeyguardQuickAffordanceToggleState() + /** The quick affordance is on. */ + object On : KeyguardQuickAffordanceToggleState() + /** The quick affordance is off. */ + object Off : KeyguardQuickAffordanceToggleState() +} 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 65b85c0355db..2c99ca59ba6b 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 @@ -29,7 +29,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.settingslib.Utils import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.animation.Interpolators import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel @@ -238,14 +238,26 @@ object KeyguardBottomAreaViewBinder { IconViewBinder.bind(viewModel.icon, view) + view.isActivated = viewModel.isActivated view.drawable.setTint( Utils.getColorAttrDefaultColor( view.context, - com.android.internal.R.attr.textColorPrimary + if (viewModel.isActivated) { + com.android.internal.R.attr.textColorPrimaryInverse + } else { + com.android.internal.R.attr.textColorPrimary + }, ) ) view.backgroundTintList = - Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface) + Utils.getColorAttr( + view.context, + if (viewModel.isActivated) { + com.android.internal.R.attr.colorAccentPrimary + } else { + com.android.internal.R.attr.colorSurface + } + ) view.isClickable = viewModel.isClickable if (viewModel.isClickable) { @@ -268,7 +280,7 @@ object KeyguardBottomAreaViewBinder { viewModel.onClicked( KeyguardQuickAffordanceViewModel.OnClickedParameters( configKey = viewModel.configKey, - animationController = ActivityLaunchAnimator.Controller.fromView(view), + expandable = Expandable.fromView(view), ) ) } 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 970ee4c541d7..535ca7210244 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 @@ -23,6 +23,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -119,10 +120,11 @@ constructor( onClicked = { parameters -> quickAffordanceInteractor.onQuickAffordanceClicked( configKey = parameters.configKey, - animationController = parameters.animationController, + expandable = parameters.expandable, ) }, isClickable = isClickable, + isActivated = toggle is KeyguardQuickAffordanceToggleState.On, ) is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel() } 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 0971f13e58dd..bf598ba85932 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 @@ -16,7 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig import kotlin.reflect.KClass @@ -30,9 +30,10 @@ data class KeyguardQuickAffordanceViewModel( val icon: Icon = Icon.Resource(res = 0, contentDescription = null), val onClicked: (OnClickedParameters) -> Unit = {}, val isClickable: Boolean = false, + val isActivated: Boolean = false, ) { data class OnClickedParameters( val configKey: KClass<out KeyguardQuickAffordanceConfig>, - val animationController: ActivityLaunchAnimator.Controller?, + val expandable: Expandable?, ) } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt index 5651399cb891..f9e341c8629a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt @@ -19,6 +19,9 @@ package com.android.systemui.log import android.app.ActivityManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogcatEchoTracker + import javax.inject.Inject @SysUISingleton @@ -26,7 +29,7 @@ class LogBufferFactory @Inject constructor( private val dumpManager: DumpManager, private val logcatEchoTracker: LogcatEchoTracker ) { - /* limit the size of maxPoolSize for low ram (Go) devices */ + /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */ private fun adjustMaxSize(requestedMaxSize: Int): Int { return if (ActivityManager.isLowRamDeviceStatic()) { minOf(requestedMaxSize, 20) /* low ram max log size*/ diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java index 8f9357abbba8..c7e4c5e93090 100644 --- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java +++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java @@ -21,7 +21,6 @@ import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT; import static android.app.StatusBarManager.SESSION_KEYGUARD; import android.annotation.Nullable; -import android.content.Context; import android.os.RemoteException; import android.util.Log; @@ -48,7 +47,7 @@ import javax.inject.Inject; * session. Can be used across processes via StatusBarManagerService#registerSessionListener */ @SysUISingleton -public class SessionTracker extends CoreStartable { +public class SessionTracker implements CoreStartable { private static final String TAG = "SessionTracker"; private static final boolean DEBUG = false; @@ -65,13 +64,11 @@ public class SessionTracker extends CoreStartable { @Inject public SessionTracker( - Context context, IStatusBarService statusBarService, AuthController authController, KeyguardUpdateMonitor keyguardUpdateMonitor, KeyguardStateController keyguardStateController ) { - super(context); mStatusBarManagerService = statusBarService; mAuthController = authController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java index 7f1ad6d20c16..eeadf406060d 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java @@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy; import javax.inject.Qualifier; /** - * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as + * A {@link com.android.systemui.plugins.log.LogBuffer} for BiometricMessages processing such as * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral} */ @Qualifier diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java index 7d1f1c2709fa..5cca1ab2abe7 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java index 9ca0293fbd86..1d016d837b02 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java index 7c5f4025117f..c9f78bcdeef8 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java index 08d969b5eb77..76d20bea4bdf 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; 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 529d0662028a..92a7b5129995 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -22,11 +22,11 @@ import android.os.Looper; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.log.LogBuffer; import com.android.systemui.log.LogBufferFactory; -import com.android.systemui.log.LogcatEchoTracker; -import com.android.systemui.log.LogcatEchoTrackerDebug; -import com.android.systemui.log.LogcatEchoTrackerProd; +import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.plugins.log.LogcatEchoTracker; +import com.android.systemui.plugins.log.LogcatEchoTrackerDebug; +import com.android.systemui.plugins.log.LogcatEchoTrackerProd; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.util.Compile; @@ -344,4 +344,14 @@ public class LogModule { public static LogBuffer provideKeyguardLogBuffer(LogBufferFactory factory) { return factory.create("KeyguardLog", 250); } + + /** + * Provides a {@link LogBuffer} for Udfps logs. + */ + @Provides + @SysUISingleton + @UdfpsLog + public static LogBuffer provideUdfpsLogBuffer(LogBufferFactory factory) { + return factory.create("UdfpsLog", 1000); + } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java index 1d7ba94af4ed..90ced0291805 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java index b03655a543f7..e5ac3e2e893b 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java index c67d8bebe313..73690ab6c24d 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java index 53963fc8d431..99ec05bc8d94 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java index 5c572e8ef554..1570d434bc62 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java index edab8c319f87..bf216c6991d2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java index 75a34fc22c3c..8c904eab409e 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java index b1c6dcfcb13b..6d91f0c97c8a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java index 20fc6ff445a6..26af4964f7b8 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java index fcc184a317b8..61daf9c8d71c 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java index 760fbf3928b6..a59afa0fed1b 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java index a0b686487bec..6f8ea7ff2e9b 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java index 8c8753a07339..835d3490293c 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java index 7259eebf19b6..6e2bd7b2e1b5 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java index e96e532f94bf..77b1bf5fd630 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java index 557a254e5c09..9fd166b759d2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java index dd5010cf39a8..dd168bac5654 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java index bd0d298ebdee..d24bfcb88188 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java index b237f2d74483..67cdb722055b 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java index f26b3164f488..af0f7c518e64 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java index dd6837563a74..4c276e2bfaab 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java index 8671dbfdf1fe..ba8b27c23ec1 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java @@ -18,7 +18,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java new file mode 100644 index 000000000000..14000e183c10 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface UdfpsLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 9dd18b21cc71..5977ed06b0f1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -11,6 +11,7 @@ import android.util.MathUtils import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.animation.PathInterpolator import android.widget.LinearLayout import androidx.annotation.VisibleForTesting import com.android.internal.logging.InstanceId @@ -95,7 +96,8 @@ class MediaCarouselController @Inject constructor( * finished */ @MediaLocation - private var currentEndLocation: Int = -1 + @VisibleForTesting + var currentEndLocation: Int = -1 /** * The ending location of the view where it ends when all animations and transitions have @@ -126,11 +128,12 @@ class MediaCarouselController @Inject constructor( lateinit var settingsButton: View private set private val mediaContent: ViewGroup - private val pageIndicator: PageIndicator + @VisibleForTesting + val pageIndicator: PageIndicator private val visualStabilityCallback: OnReorderingAllowedListener private var needsReordering: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() - var shouldScrollToActivePlayer: Boolean = false + var shouldScrollToKey: Boolean = false private var isRtl: Boolean = false set(value) { if (value != field) { @@ -149,6 +152,27 @@ class MediaCarouselController @Inject constructor( } } } + + companion object { + const val ANIMATION_BASE_DURATION = 2200f + const val DURATION = 167f + const val DETAILS_DELAY = 1067f + const val CONTROLS_DELAY = 1400f + const val PAGINATION_DELAY = 1900f + const val MEDIATITLES_DELAY = 1000f + const val MEDIACONTAINERS_DELAY = 967f + val TRANSFORM_BEZIER = PathInterpolator (0.68F, 0F, 0F, 1F) + val REVERSE_BEZIER = PathInterpolator (0F, 0.68F, 1F, 0F) + + fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float { + val transformStartFraction = delay / ANIMATION_BASE_DURATION + val transformDurationFraction = duration / ANIMATION_BASE_DURATION + val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction) + return MathUtils.constrain((squishinessToTime - transformStartFraction) / + transformDurationFraction, 0F, 1F) + } + } + private val configListener = object : ConfigurationController.ConfigurationListener { override fun onDensityOrFontScaleChanged() { // System font changes should only happen when UMO is offscreen or a flicker may occur @@ -412,7 +436,10 @@ class MediaCarouselController @Inject constructor( return mediaCarousel } - private fun reorderAllPlayers(previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?) { + private fun reorderAllPlayers( + previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?, + key: String? = null + ) { mediaContent.removeAllViews() for (mediaPlayer in MediaPlayerData.players()) { mediaPlayer.mediaViewHolder?.let { @@ -422,18 +449,18 @@ class MediaCarouselController @Inject constructor( } } mediaCarouselScrollHandler.onPlayersChanged() - + MediaPlayerData.updateVisibleMediaPlayers() // Automatically scroll to the active player if needed - if (shouldScrollToActivePlayer) { - shouldScrollToActivePlayer = false - val activeMediaIndex = MediaPlayerData.firstActiveMediaIndex() - if (activeMediaIndex != -1) { + if (shouldScrollToKey) { + shouldScrollToKey = false + val mediaIndex = key?.let { MediaPlayerData.getMediaPlayerIndex(it) } ?: -1 + if (mediaIndex != -1) { previousVisiblePlayerKey?.let { val previousVisibleIndex = MediaPlayerData.playerKeys() .indexOfFirst { key -> it == key } mediaCarouselScrollHandler - .scrollToPlayer(previousVisibleIndex, activeMediaIndex) - } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex) + .scrollToPlayer(previousVisibleIndex, mediaIndex) + } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex) } } } @@ -447,9 +474,8 @@ class MediaCarouselController @Inject constructor( ): Boolean = traceSection("MediaCarouselController#addOrUpdatePlayer") { MediaPlayerData.moveIfExists(oldKey, key) val existingPlayer = MediaPlayerData.getMediaPlayer(key) - val curVisibleMediaKey = MediaPlayerData.playerKeys() + val curVisibleMediaKey = MediaPlayerData.visiblePlayerKeys() .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) - val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying if (existingPlayer == null) { val newPlayer = mediaControlPanelFactory.get() newPlayer.attachPlayer(MediaViewHolder.create( @@ -464,8 +490,10 @@ class MediaCarouselController @Inject constructor( key, data, newPlayer, systemClock, isSsReactivated, debugLogger ) updatePlayerToState(newPlayer, noAnimation = true) - if (data.active) { - reorderAllPlayers(curVisibleMediaKey) + // Media data added from a recommendation card should starts playing. + if ((shouldScrollToKey && data.isPlaying == true) || + (!shouldScrollToKey && data.active)) { + reorderAllPlayers(curVisibleMediaKey, key) } else { needsReordering = true } @@ -474,14 +502,16 @@ class MediaCarouselController @Inject constructor( MediaPlayerData.addMediaPlayer( key, data, existingPlayer, systemClock, isSsReactivated, debugLogger ) - // Check the playing status of both current visible and new media players - // To make sure we scroll to the active playing media card. + val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String() + // In case of recommendations hits. + // Check the playing status of media player and the package name. + // To make sure we scroll to the right app's media player. if (isReorderingAllowed || - shouldScrollToActivePlayer && + shouldScrollToKey && data.isPlaying == true && - isCurVisibleMediaPlaying == false + packageName == data.packageName ) { - reorderAllPlayers(curVisibleMediaKey) + reorderAllPlayers(curVisibleMediaKey, key) } else { needsReordering = true } @@ -510,7 +540,7 @@ class MediaCarouselController @Inject constructor( val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey() existingSmartspaceMediaKey?.let { - val removedPlayer = MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey) + val removedPlayer = MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey, true) removedPlayer?.run { debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey) } } @@ -522,7 +552,7 @@ class MediaCarouselController @Inject constructor( ViewGroup.LayoutParams.WRAP_CONTENT) newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp) newRecs.bindRecommendation(data) - val curVisibleMediaKey = MediaPlayerData.playerKeys() + val curVisibleMediaKey = MediaPlayerData.visiblePlayerKeys() .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) MediaPlayerData.addMediaRecommendation( key, data, newRecs, shouldPrioritize, systemClock, debugLogger @@ -548,7 +578,10 @@ class MediaCarouselController @Inject constructor( logger.logRecommendationRemoved(it.packageName, it.instanceId) } } - val removed = MediaPlayerData.removeMediaPlayer(key) + val removed = MediaPlayerData.removeMediaPlayer( + key, + dismissMediaData || dismissRecommendation + ) removed?.apply { mediaCarouselScrollHandler.onPrePlayerRemoved(removed) mediaContent.removeView(removed.mediaViewHolder?.player) @@ -633,12 +666,17 @@ class MediaCarouselController @Inject constructor( } } - private fun updatePageIndicatorAlpha() { + @VisibleForTesting + fun updatePageIndicatorAlpha() { val hostStates = mediaHostStatesManager.mediaHostStates val endIsVisible = hostStates[currentEndLocation]?.visible ?: false val startIsVisible = hostStates[currentStartLocation]?.visible ?: false val startAlpha = if (startIsVisible) 1.0f else 0.0f - val endAlpha = if (endIsVisible) 1.0f else 0.0f + // when squishing in split shade, only use endState, which keeps changing + // to provide squishFraction + val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F + val endAlpha = (if (endIsVisible) 1.0f else 0.0f) * + calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION) var alpha = 1.0f if (!endIsVisible || !startIsVisible) { var progress = currentTransitionProgress @@ -687,6 +725,7 @@ class MediaCarouselController @Inject constructor( mediaCarouselScrollHandler.setCarouselBounds( currentCarouselWidth, currentCarouselHeight) updatePageIndicatorLocation() + updatePageIndicatorAlpha() } } @@ -805,18 +844,20 @@ class MediaCarouselController @Inject constructor( fun logSmartspaceImpression(qsExpanded: Boolean) { val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex if (MediaPlayerData.players().size > visibleMediaIndex) { - val mediaControlPanel = MediaPlayerData.players().elementAt(visibleMediaIndex) + val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex) val hasActiveMediaOrRecommendationCard = MediaPlayerData.hasActiveMediaOrRecommendationCard() if (!hasActiveMediaOrRecommendationCard && !qsExpanded) { // Skip logging if on LS or QQS, and there is no active media card return } - logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN - mediaControlPanel.mSmartspaceId, - mediaControlPanel.mUid, - intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging)) - mediaControlPanel.mIsImpressed = true + mediaControlPanel?.let { + logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN + it.mSmartspaceId, + it.mUid, + intArrayOf(it.surfaceForSmartspaceLogging)) + it.mIsImpressed = true + } } } @@ -855,7 +896,7 @@ class MediaCarouselController @Inject constructor( return } - val mediaControlKey = MediaPlayerData.playerKeys().elementAt(rank) + val mediaControlKey = MediaPlayerData.visiblePlayerKeys().elementAt(rank) // Only log media resume card when Smartspace data is available if (!mediaControlKey.isSsMediaRec && !mediaManager.smartspaceMediaData.isActive && @@ -930,7 +971,8 @@ class MediaCarouselController @Inject constructor( pw.apply { println("keysNeedRemoval: $keysNeedRemoval") println("dataKeys: ${MediaPlayerData.dataKeys()}") - println("playerSortKeys: ${MediaPlayerData.playerKeys()}") + println("orderedPlayerSortKeys: ${MediaPlayerData.playerKeys()}") + println("visiblePlayerSortKeys: ${MediaPlayerData.visiblePlayerKeys()}") println("smartspaceMediaData: ${MediaPlayerData.smartspaceMediaData}") println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}") println("current size: $currentCarouselWidth x $currentCarouselHeight") @@ -970,6 +1012,7 @@ internal object MediaPlayerData { data class MediaSortKey( val isSsMediaRec: Boolean, // Whether the item represents a Smartspace media recommendation. val data: MediaData, + val key: String, val updateTime: Long = 0, val isSsReactivated: Boolean = false ) @@ -988,6 +1031,8 @@ internal object MediaPlayerData { private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator) private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf() + // A map that tracks order of visible media players before they get reordered. + private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>() fun addMediaPlayer( key: String, @@ -1002,9 +1047,10 @@ internal object MediaPlayerData { debugLogger?.logPotentialMemoryLeak(key) } val sortKey = MediaSortKey(isSsMediaRec = false, - data, clock.currentTimeMillis(), isSsReactivated = isSsReactivated) + data, key, clock.currentTimeMillis(), isSsReactivated = isSsReactivated) mediaData.put(key, sortKey) mediaPlayers.put(sortKey, player) + visibleMediaPlayers.put(key, sortKey) } fun addMediaRecommendation( @@ -1020,10 +1066,16 @@ internal object MediaPlayerData { if (removedPlayer != null && removedPlayer != player) { debugLogger?.logPotentialMemoryLeak(key) } - val sortKey = MediaSortKey(isSsMediaRec = true, - EMPTY.copy(isPlaying = false), clock.currentTimeMillis(), isSsReactivated = true) + val sortKey = MediaSortKey( + isSsMediaRec = true, + EMPTY.copy(isPlaying = false), + key, + clock.currentTimeMillis(), + isSsReactivated = true + ) mediaData.put(key, sortKey) mediaPlayers.put(sortKey, player) + visibleMediaPlayers.put(key, sortKey) smartspaceMediaData = data } @@ -1037,12 +1089,18 @@ internal object MediaPlayerData { } mediaData.remove(oldKey)?.let { + // MediaPlayer should not be visible + // no need to set isDismissed flag. val removedPlayer = removeMediaPlayer(newKey) removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) } mediaData.put(newKey, it) } } + fun getMediaControlPanel(visibleIndex: Int): MediaControlPanel? { + return mediaPlayers.get(visiblePlayerKeys().elementAt(visibleIndex)) + } + fun getMediaPlayer(key: String): MediaControlPanel? { return mediaData.get(key)?.let { mediaPlayers.get(it) } } @@ -1057,10 +1115,17 @@ internal object MediaPlayerData { return -1 } - fun removeMediaPlayer(key: String) = mediaData.remove(key)?.let { + /** + * Removes media player given the key. + * @param isDismissed determines whether the media player is removed from the carousel. + */ + fun removeMediaPlayer(key: String, isDismissed: Boolean = false) = mediaData.remove(key)?.let { if (it.isSsMediaRec) { smartspaceMediaData = null } + if (isDismissed) { + visibleMediaPlayers.remove(key) + } mediaPlayers.remove(it) } @@ -1072,6 +1137,8 @@ internal object MediaPlayerData { fun playerKeys() = mediaPlayers.keys + fun visiblePlayerKeys() = visibleMediaPlayers.values + /** Returns the index of the first non-timeout media. */ fun firstActiveMediaIndex(): Int { mediaPlayers.entries.forEachIndexed { index, e -> @@ -1096,6 +1163,7 @@ internal object MediaPlayerData { fun clear() { mediaData.clear() mediaPlayers.clear() + visibleMediaPlayers.clear() } /* Returns true if there is active media player card or recommendation card */ @@ -1110,4 +1178,16 @@ internal object MediaPlayerData { } fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.isSsReactivated ?: false + + /** + * This method is called when media players are reordered. + * To make sure we have the new version of the order of + * media players visible to user. + */ + fun updateVisibleMediaPlayers() { + visibleMediaPlayers.clear() + playerKeys().forEach { + visibleMediaPlayers.put(it.key, it) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt index b1018f9544c0..d40624bfc63a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt @@ -17,9 +17,9 @@ package com.android.systemui.media import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.MediaCarouselControllerLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject /** A debug logger for [MediaCarouselController]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 759795f84963..fba51dda86e0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -1441,7 +1441,7 @@ public class MediaControlPanel { } // Automatically scroll to the active player once the media is loaded. - mMediaCarouselController.setShouldScrollToActivePlayer(true); + mMediaCarouselController.setShouldScrollToKey(true); }); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 5096a797a32b..896fb4765c57 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -1130,7 +1130,7 @@ class MediaDataManager( fun onNotificationRemoved(key: String) { Assert.isMainThread() val removed = mediaEntries.remove(key) - if (useMediaResumption && removed?.resumeAction != null && removed?.isLocalSession()) { + 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!!) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index bffb0fdec707..864592238b73 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -203,6 +203,14 @@ class MediaHost constructor( } } + override var squishFraction: Float = 1.0f + set(value) { + if (!value.equals(field)) { + field = value + changedListener?.invoke() + } + } + override var showsOnlyActiveMedia: Boolean = false set(value) { if (!value.equals(field)) { @@ -253,6 +261,7 @@ class MediaHost constructor( override fun copy(): MediaHostState { val mediaHostState = MediaHostStateHolder() mediaHostState.expansion = expansion + mediaHostState.squishFraction = squishFraction mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia mediaHostState.measurementInput = measurementInput?.copy() mediaHostState.visible = visible @@ -271,6 +280,9 @@ class MediaHost constructor( if (expansion != other.expansion) { return false } + if (squishFraction != other.squishFraction) { + return false + } if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) { return false } @@ -289,6 +301,7 @@ class MediaHost constructor( override fun hashCode(): Int { var result = measurementInput?.hashCode() ?: 0 result = 31 * result + expansion.hashCode() + result = 31 * result + squishFraction.hashCode() result = 31 * result + falsingProtectionNeeded.hashCode() result = 31 * result + showsOnlyActiveMedia.hashCode() result = 31 * result + if (visible) 1 else 2 @@ -329,6 +342,11 @@ interface MediaHostState { var expansion: Float /** + * Fraction of the height animation. + */ + var squishFraction: Float + + /** * Is this host only showing active media or is it showing all of them including resumption? */ var showsOnlyActiveMedia: Boolean diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index 1ac2a078c8a0..be357ee0ff73 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -182,8 +182,7 @@ class MediaProjectionAppSelectorActivity( override fun shouldGetOnlyDefaultActivities() = false - // TODO(b/240924732) flip the flag when the recents selector is ready - override fun shouldShowContentPreview() = false + override fun shouldShowContentPreview() = true override fun createContentPreviewView(parent: ViewGroup): ViewGroup = recentsViewController.createView(parent) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt index d9c58c0d0d76..8c9e2d88c694 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt @@ -18,11 +18,10 @@ package com.android.systemui.media import android.media.session.PlaybackState import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.MediaTimeoutListenerLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject - private const val TAG = "MediaTimeout" /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index ac59175d4646..faa7aaee3c9a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -18,8 +18,15 @@ package com.android.systemui.media import android.content.Context import android.content.res.Configuration +import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.R +import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY +import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY +import com.android.systemui.media.MediaCarouselController.Companion.DURATION +import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY +import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY +import com.android.systemui.media.MediaCarouselController.Companion.calculateAlpha import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.MeasurementOutput import com.android.systemui.util.animation.TransitionLayout @@ -50,6 +57,24 @@ class MediaViewController @Inject constructor( companion object { @JvmField val GUTS_ANIMATION_DURATION = 500L + val controlIds = setOf( + R.id.media_progress_bar, + R.id.actionNext, + R.id.actionPrev, + R.id.action0, + R.id.action1, + R.id.action2, + R.id.action3, + R.id.action4, + R.id.media_scrubbing_elapsed_time, + R.id.media_scrubbing_total_time + ) + + val detailIds = setOf( + R.id.header_title, + R.id.header_artist, + R.id.actionPlayPause, + ) } /** @@ -57,6 +82,7 @@ class MediaViewController @Inject constructor( */ lateinit var sizeChangedListener: () -> Unit private var firstRefresh: Boolean = true + @VisibleForTesting private var transitionLayout: TransitionLayout? = null private val layoutController = TransitionLayoutController() private var animationDelay: Long = 0 @@ -279,10 +305,47 @@ class MediaViewController @Inject constructor( } /** + * Apply squishFraction to a copy of viewState such that the cached version is untouched. + */ + internal fun squishViewState( + viewState: TransitionViewState, + squishFraction: Float + ): TransitionViewState { + val squishedViewState = viewState.copy() + squishedViewState.height = (squishedViewState.height * squishFraction).toInt() + controlIds.forEach { id -> + squishedViewState.widgetStates.get(id)?.let { state -> + state.alpha = calculateAlpha(squishFraction, CONTROLS_DELAY, DURATION) + } + } + + detailIds.forEach { id -> + squishedViewState.widgetStates.get(id)?.let { state -> + state.alpha = calculateAlpha(squishFraction, DETAILS_DELAY, DURATION) + } + } + + RecommendationViewHolder.mediaContainersIds.forEach { id -> + squishedViewState.widgetStates.get(id)?.let { state -> + state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION) + } + } + + RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id -> + squishedViewState.widgetStates.get(id)?.let { state -> + state.alpha = calculateAlpha(squishFraction, MEDIATITLES_DELAY, DURATION) + } + } + + return squishedViewState + } + + /** * Obtain a new viewState for a given media state. This usually returns a cached state, but if * it's not available, it will recreate one by measuring, which may be expensive. */ - private fun obtainViewState(state: MediaHostState?): TransitionViewState? { + @VisibleForTesting + fun obtainViewState(state: MediaHostState?): TransitionViewState? { if (state == null || state.measurementInput == null) { return null } @@ -291,41 +354,46 @@ class MediaViewController @Inject constructor( val viewState = viewStates[cacheKey] if (viewState != null) { // we already have cached this measurement, let's continue + if (state.squishFraction <= 1f) { + return squishViewState(viewState, state.squishFraction) + } return viewState } // Copy the key since this might call recursively into it and we're using tmpKey cacheKey = cacheKey.copy() val result: TransitionViewState? - if (transitionLayout != null) { - // Let's create a new measurement - if (state.expansion == 0.0f || state.expansion == 1.0f) { - result = transitionLayout!!.calculateViewState( - state.measurementInput!!, - constraintSetForExpansion(state.expansion), - TransitionViewState()) - - setGutsViewState(result) - // We don't want to cache interpolated or null states as this could quickly fill up - // our cache. We only cache the start and the end states since the interpolation - // is cheap - viewStates[cacheKey] = result - } else { - // This is an interpolated state - val startState = state.copy().also { it.expansion = 0.0f } - - // Given that we have a measurement and a view, let's get (guaranteed) viewstates - // from the start and end state and interpolate them - val startViewState = obtainViewState(startState) as TransitionViewState - val endState = state.copy().also { it.expansion = 1.0f } - val endViewState = obtainViewState(endState) as TransitionViewState - result = layoutController.getInterpolatedState( - startViewState, - endViewState, - state.expansion) - } + if (transitionLayout == null) { + return null + } + // Let's create a new measurement + if (state.expansion == 0.0f || state.expansion == 1.0f) { + result = transitionLayout!!.calculateViewState( + state.measurementInput!!, + constraintSetForExpansion(state.expansion), + TransitionViewState()) + + setGutsViewState(result) + // We don't want to cache interpolated or null states as this could quickly fill up + // our cache. We only cache the start and the end states since the interpolation + // is cheap + viewStates[cacheKey] = result } else { - result = null + // This is an interpolated state + val startState = state.copy().also { it.expansion = 0.0f } + + // Given that we have a measurement and a view, let's get (guaranteed) viewstates + // from the start and end state and interpolate them + val startViewState = obtainViewState(startState) as TransitionViewState + val endState = state.copy().also { it.expansion = 1.0f } + val endViewState = obtainViewState(endState) as TransitionViewState + result = layoutController.getInterpolatedState( + startViewState, + endViewState, + state.expansion) + } + if (state.squishFraction <= 1f) { + return squishViewState(result, state.squishFraction) } return result } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt index 73868189b362..51c658cb6c54 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt @@ -17,9 +17,9 @@ package com.android.systemui.media import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.MediaViewLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject private const val TAG = "MediaView" diff --git a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt index 52ac4e0682a3..8ae75fc34acb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt @@ -106,5 +106,20 @@ class RecommendationViewHolder private constructor(itemView: View) { R.id.media_subtitle2, R.id.media_subtitle3 ) + + val mediaTitlesAndSubtitlesIds = setOf( + R.id.media_title1, + R.id.media_title2, + R.id.media_title3, + R.id.media_subtitle1, + R.id.media_subtitle2, + R.id.media_subtitle3 + ) + + val mediaContainersIds = setOf( + R.id.media_cover1_container, + R.id.media_cover2_container, + R.id.media_cover3_container + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt index 41f735486c7e..a9c5c61dddbb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt @@ -18,9 +18,9 @@ package com.android.systemui.media import android.content.ComponentName import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.MediaBrowserLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject /** A logger for events in [ResumeMediaBrowser]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index 0b9b32b0d7d7..2a8168b0cb36 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -51,9 +51,10 @@ import javax.inject.Inject; * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}. */ @SysUISingleton -public class RingtonePlayer extends CoreStartable { +public class RingtonePlayer implements CoreStartable { private static final String TAG = "RingtonePlayer"; private static final boolean LOGD = false; + private final Context mContext; // TODO: support Uri switching under same IBinder @@ -64,7 +65,7 @@ public class RingtonePlayer extends CoreStartable { @Inject public RingtonePlayer(Context context) { - super(context); + mContext = context; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt index 17ebfecdd1fa..0f78a1e2ff50 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt @@ -33,7 +33,6 @@ import androidx.lifecycle.MutableLiveData import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.FalsingManager -import com.android.systemui.plugins.FalsingManager.LOW_PENALTY import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.util.concurrency.RepeatableExecutor import javax.inject.Inject @@ -333,11 +332,7 @@ class SeekBarViewModel @Inject constructor( } override fun onStopTrackingTouch(bar: SeekBar) { - // in addition to the normal functionality of both functions. - // isFalseTouch returns true if there is a real/false tap since it is not a move. - // isFalseTap returns true if there is a real/false move since it is not a tap. - if (falsingManager.isFalseTouch(MEDIA_SEEKBAR) && - falsingManager.isFalseTap(LOW_PENALTY)) { + if (falsingManager.isFalseTouch(MEDIA_SEEKBAR)) { viewModel.onSeekFalse() } viewModel.onSeek(bar.progress.toLong()) 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 66c036cee600..e15e2d3dcccf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -17,7 +17,6 @@ package com.android.systemui.media.dagger; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.log.LogBuffer; import com.android.systemui.log.dagger.MediaTttReceiverLogBuffer; import com.android.systemui.log.dagger.MediaTttSenderLogBuffer; import com.android.systemui.media.MediaDataManager; @@ -31,10 +30,9 @@ 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.MediaTttChipControllerReceiver; import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger; -import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender; import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger; +import com.android.systemui.plugins.log.LogBuffer; import java.util.Optional; @@ -94,30 +92,6 @@ public interface MediaModule { return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager); } - /** */ - @Provides - @SysUISingleton - static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender( - MediaTttFlags mediaTttFlags, - Lazy<MediaTttChipControllerSender> controllerSenderLazy) { - if (!mediaTttFlags.isMediaTttEnabled()) { - return Optional.empty(); - } - return Optional.of(controllerSenderLazy.get()); - } - - /** */ - @Provides - @SysUISingleton - static Optional<MediaTttChipControllerReceiver> providesMediaTttChipControllerReceiver( - MediaTttFlags mediaTttFlags, - Lazy<MediaTttChipControllerReceiver> controllerReceiverLazy) { - if (!mediaTttFlags.isMediaTttEnabled()) { - return Optional.empty(); - } - return Optional.of(controllerReceiverLazy.get()); - } - @Provides @SysUISingleton @MediaTttSenderLogger diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index 53b4d434bfcb..91e7b4933096 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -18,7 +18,6 @@ package com.android.systemui.media.dream; import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION; -import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; @@ -38,7 +37,7 @@ import javax.inject.Inject; * {@link MediaDreamSentinel} is responsible for tracking media state and registering/unregistering * the media complication as appropriate */ -public class MediaDreamSentinel extends CoreStartable { +public class MediaDreamSentinel implements CoreStartable { private static final String TAG = "MediaDreamSentinel"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -113,11 +112,10 @@ public class MediaDreamSentinel extends CoreStartable { private final FeatureFlags mFeatureFlags; @Inject - public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager, + public MediaDreamSentinel(MediaDataManager mediaDataManager, DreamOverlayStateController dreamOverlayStateController, DreamMediaEntryComplication mediaEntryComplication, FeatureFlags featureFlags) { - super(context); mMediaDataManager = mediaDataManager; mDreamOverlayStateController = dreamOverlayStateController; mMediaEntryComplication = mediaEntryComplication; diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt index 78f4e012da03..5ace3ea8a05b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt @@ -1,9 +1,9 @@ package com.android.systemui.media.muteawait import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.MediaMuteAwaitLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject /** Log messages for [MediaMuteAwaitConnectionManager]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt index 46b2cc141b3c..78408fce5a36 100644 --- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt @@ -1,9 +1,9 @@ package com.android.systemui.media.nearby import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NearbyMediaDevicesLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject /** Log messages for [NearbyMediaDevicesManager]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java index d60172a17988..0ba5f28c351f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java +++ b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java @@ -40,7 +40,7 @@ import javax.inject.Inject; * documented at {@link #handleTaskStackChanged} apply. */ @SysUISingleton -public class HomeSoundEffectController extends CoreStartable { +public class HomeSoundEffectController implements CoreStartable { private static final String TAG = "HomeSoundEffectController"; private final AudioManager mAudioManager; @@ -65,7 +65,6 @@ public class HomeSoundEffectController extends CoreStartable { TaskStackChangeListeners taskStackChangeListeners, ActivityManagerWrapper activityManagerWrapper, PackageManager packageManager) { - super(context); mAudioManager = audioManager; mTaskStackChangeListeners = taskStackChangeListeners; mActivityManagerWrapper = activityManagerWrapper; diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index f5caefbf4ced..a4a968067462 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -43,7 +43,7 @@ class MediaTttCommandLineHelper @Inject constructor( private val commandRegistry: CommandRegistry, private val context: Context, @Main private val mainExecutor: Executor -) : CoreStartable(context) { +) : CoreStartable { /** All commands for the sender device. */ inner class SenderCommand : Command { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md index 6379960b85e9..b5a0483e0c69 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md @@ -41,3 +41,5 @@ in the `receiver` package, and code that's shared between them is in the `common ## Testing If you want to test out the tap-to-transfer chip without using the `@SystemApi`s, you can use adb commands instead. Refer to `MediaTttCommandLineHelper` for information about adb commands. + +TODO(b/245610654): Update this page once the chipbar migration is complete. 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 index b565f3c22f24..38c971ed3f7d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt @@ -16,8 +16,8 @@ package com.android.systemui.media.taptotransfer.common -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.temporarydisplay.TemporaryViewLogger /** 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 c3de94f28aea..0a6043793ef6 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 @@ -21,6 +21,8 @@ import android.content.pm.PackageManager import android.graphics.drawable.Drawable import com.android.settingslib.Utils import com.android.systemui.R +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon /** Utility methods for media tap-to-transfer. */ class MediaTttUtils { @@ -31,6 +33,23 @@ class MediaTttUtils { const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED" /** + * Returns the information needed to display the icon in [Icon] form. + * + * See [getIconInfoFromPackageName]. + */ + fun getIconFromPackageName( + context: Context, + appPackageName: String?, + logger: MediaTttLogger, + ): Icon { + val iconInfo = getIconInfoFromPackageName(context, appPackageName, logger) + return Icon.Loaded( + iconInfo.drawable, + ContentDescription.Loaded(iconInfo.contentDescription) + ) + } + + /** * Returns the information needed to display the icon. * * The information will either contain app name and icon of the app playing media, or a 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 1461293c7a07..089625ca8d9c 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 @@ -36,6 +36,7 @@ import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main +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 @@ -52,6 +53,8 @@ import javax.inject.Inject * A controller to display and hide the Media Tap-To-Transfer chip on the **receiving** device. * * This chip is shown when a user is transferring media to/from a sending device and this device. + * + * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator. */ @SysUISingleton class MediaTttChipControllerReceiver @Inject constructor( @@ -64,6 +67,7 @@ class MediaTttChipControllerReceiver @Inject constructor( configurationController: ConfigurationController, powerManager: PowerManager, @Main private val mainHandler: Handler, + private val mediaTttFlags: MediaTttFlags, private val uiEventLogger: MediaTttReceiverUiEventLogger, private val viewUtil: ViewUtil, ) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>( @@ -118,7 +122,7 @@ class MediaTttChipControllerReceiver @Inject constructor( uiEventLogger.logReceiverStateChange(chipState) if (chipState == ChipStateReceiver.FAR_FROM_SENDER) { - removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER::class.simpleName!!) + removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER.name) return } if (appIcon == null) { @@ -138,7 +142,9 @@ class MediaTttChipControllerReceiver @Inject constructor( } override fun start() { - commandQueue.addCallback(commandQueueCallbacks) + if (mediaTttFlags.isMediaTttEnabled()) { + commandQueue.addCallback(commandQueueCallbacks) + } } override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index aae973dcc1c7..6e596ee1f473 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -18,14 +18,11 @@ package com.android.systemui.media.taptotransfer.sender import android.app.StatusBarManager import android.content.Context -import android.media.MediaRoute2Info import android.util.Log -import android.view.View import androidx.annotation.StringRes import com.android.internal.logging.UiEventLogger -import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R -import com.android.systemui.plugins.FalsingManager +import com.android.systemui.common.shared.model.Text import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS /** @@ -36,6 +33,7 @@ import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS * @property stringResId the res ID of the string that should be displayed in the chip. Null if the * state should not have the chip be displayed. * @property transferStatus the transfer status that the chip state represents. + * @property endItem the item that should be displayed in the end section of the chip. * @property timeout the amount of time this chip should display on the screen before it times out * and disappears. */ @@ -44,6 +42,7 @@ enum class ChipStateSender( val uiEvent: UiEventLogger.UiEventEnum, @StringRes val stringResId: Int?, val transferStatus: TransferStatus, + val endItem: SenderEndItem?, val timeout: Long = DEFAULT_TIMEOUT_MILLIS ) { /** @@ -56,6 +55,7 @@ enum class ChipStateSender( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST, R.string.media_move_closer_to_start_cast, transferStatus = TransferStatus.NOT_STARTED, + endItem = null, ), /** @@ -69,6 +69,7 @@ enum class ChipStateSender( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST, R.string.media_move_closer_to_end_cast, transferStatus = TransferStatus.NOT_STARTED, + endItem = null, ), /** @@ -80,6 +81,7 @@ enum class ChipStateSender( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED, R.string.media_transfer_playing_different_device, transferStatus = TransferStatus.IN_PROGRESS, + endItem = SenderEndItem.Loading, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS ), @@ -92,6 +94,7 @@ enum class ChipStateSender( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED, R.string.media_transfer_playing_this_device, transferStatus = TransferStatus.IN_PROGRESS, + endItem = SenderEndItem.Loading, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS ), @@ -103,36 +106,13 @@ enum class ChipStateSender( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED, R.string.media_transfer_playing_different_device, transferStatus = TransferStatus.SUCCEEDED, - ) { - override fun undoClickListener( - controllerSender: MediaTttChipControllerSender, - routeInfo: MediaRoute2Info, - undoCallback: IUndoMediaTransferCallback?, - uiEventLogger: MediaTttSenderUiEventLogger, - falsingManager: FalsingManager, - ): View.OnClickListener? { - if (undoCallback == null) { - return null - } - return View.OnClickListener { - if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener - - uiEventLogger.logUndoClicked( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED - ) - undoCallback.onUndoTriggered() - // The external service should eventually send us a TransferToThisDeviceTriggered - // state, but that may take too long to go through the binder and the user may be - // confused ast o why the UI hasn't changed yet. So, we immediately change the UI - // here. - controllerSender.displayView( - ChipSenderInfo( - TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, undoCallback - ) - ) - } - } - }, + endItem = SenderEndItem.UndoButton( + uiEventOnClick = + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED, + newState = + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED + ), + ), /** * A state representing that a transfer back to this device has been successfully completed. @@ -142,36 +122,13 @@ enum class ChipStateSender( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, R.string.media_transfer_playing_this_device, transferStatus = TransferStatus.SUCCEEDED, - ) { - override fun undoClickListener( - controllerSender: MediaTttChipControllerSender, - routeInfo: MediaRoute2Info, - undoCallback: IUndoMediaTransferCallback?, - uiEventLogger: MediaTttSenderUiEventLogger, - falsingManager: FalsingManager, - ): View.OnClickListener? { - if (undoCallback == null) { - return null - } - return View.OnClickListener { - if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener - - uiEventLogger.logUndoClicked( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED - ) - undoCallback.onUndoTriggered() - // The external service should eventually send us a TransferToReceiverTriggered - // state, but that may take too long to go through the binder and the user may be - // confused as to why the UI hasn't changed yet. So, we immediately change the UI - // here. - controllerSender.displayView( - ChipSenderInfo( - TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo, undoCallback - ) - ) - } - } - }, + endItem = SenderEndItem.UndoButton( + uiEventOnClick = + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED, + newState = + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED + ), + ), /** A state representing that a transfer to the receiver device has failed. */ TRANSFER_TO_RECEIVER_FAILED( @@ -179,6 +136,7 @@ enum class ChipStateSender( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED, R.string.media_transfer_failed, transferStatus = TransferStatus.FAILED, + endItem = SenderEndItem.Error, ), /** A state representing that a transfer back to this device has failed. */ @@ -187,6 +145,7 @@ enum class ChipStateSender( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED, R.string.media_transfer_failed, transferStatus = TransferStatus.FAILED, + endItem = SenderEndItem.Error, ), /** A state representing that this device is far away from any receiver device. */ @@ -195,37 +154,27 @@ enum class ChipStateSender( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER, stringResId = null, transferStatus = TransferStatus.TOO_FAR, - ); + // We shouldn't be displaying the chipbar anyway + endItem = null, + ) { + override fun getChipTextString(context: Context, otherDeviceName: String): Text { + // TODO(b/245610654): Better way to handle this. + throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " + + "so its string should never be fetched") + } + }; /** * Returns a fully-formed string with the text that the chip should display. * + * Throws an NPE if [stringResId] is null. + * * @param otherDeviceName the name of the other device involved in the transfer. */ - fun getChipTextString(context: Context, otherDeviceName: String): String? { - if (stringResId == null) { - return null - } - return context.getString(stringResId, otherDeviceName) + open fun getChipTextString(context: Context, otherDeviceName: String): Text { + return Text.Loaded(context.getString(stringResId!!, otherDeviceName)) } - /** - * Returns a click listener for the undo button on the chip. Returns null if this chip state - * doesn't have an undo button. - * - * @param controllerSender passed as a parameter in case we want to display a new chip state - * when undo is clicked. - * @param undoCallback if present, the callback that should be called when the user clicks the - * undo button. The undo button will only be shown if this is non-null. - */ - open fun undoClickListener( - controllerSender: MediaTttChipControllerSender, - routeInfo: MediaRoute2Info, - undoCallback: IUndoMediaTransferCallback?, - uiEventLogger: MediaTttSenderUiEventLogger, - falsingManager: FalsingManager, - ): View.OnClickListener? = null - companion object { /** * Returns the sender state enum associated with the given [displayState] from @@ -251,6 +200,26 @@ enum class ChipStateSender( } } +/** Represents the item that should be displayed in the end section of the chip. */ +sealed class SenderEndItem { + /** A loading icon should be displayed. */ + object Loading : SenderEndItem() + + /** An error icon should be displayed. */ + object Error : SenderEndItem() + + /** + * An undo button should be displayed. + * + * @property uiEventOnClick the UI event to log when this button is clicked. + * @property newState the state that should immediately be transitioned to. + */ + data class UndoButton( + val uiEventOnClick: UiEventLogger.UiEventEnum, + @StatusBarManager.MediaTransferSenderState val newState: Int, + ) : SenderEndItem() +} + // Give the Transfer*Triggered states a longer timeout since those states represent an active // process and we should keep the user informed about it as long as possible (but don't allow it to // continue indefinitely). diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt deleted file mode 100644 index 5d631450cb41..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ /dev/null @@ -1,247 +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.media.taptotransfer.sender - -import android.app.StatusBarManager -import android.content.Context -import android.graphics.Rect -import android.media.MediaRoute2Info -import android.os.PowerManager -import android.util.Log -import android.view.Gravity -import android.view.MotionEvent -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import android.view.accessibility.AccessibilityManager -import android.widget.TextView -import com.android.internal.statusbar.IUndoMediaTransferCallback -import com.android.internal.widget.CachingIconView -import com.android.systemui.Gefingerpoken -import com.android.systemui.R -import com.android.systemui.animation.Interpolators -import com.android.systemui.animation.ViewHierarchyAnimator -import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.media.taptotransfer.common.MediaTttLogger -import com.android.systemui.media.taptotransfer.common.MediaTttUtils -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason -import com.android.systemui.temporarydisplay.TemporaryViewDisplayController -import com.android.systemui.temporarydisplay.TemporaryViewInfo -import com.android.systemui.util.concurrency.DelayableExecutor -import com.android.systemui.util.view.ViewUtil -import javax.inject.Inject - -/** - * A controller to display and hide the Media Tap-To-Transfer chip on the **sending** device. This - * chip is shown when a user is transferring media to/from this device and a receiver device. - */ -@SysUISingleton -open class MediaTttChipControllerSender @Inject constructor( - private val commandQueue: CommandQueue, - context: Context, - @MediaTttSenderLogger logger: MediaTttLogger, - windowManager: WindowManager, - @Main mainExecutor: DelayableExecutor, - accessibilityManager: AccessibilityManager, - configurationController: ConfigurationController, - powerManager: PowerManager, - private val uiEventLogger: MediaTttSenderUiEventLogger, - private val falsingManager: FalsingManager, - private val falsingCollector: FalsingCollector, - private val viewUtil: ViewUtil, -) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>( - context, - logger, - windowManager, - mainExecutor, - accessibilityManager, - configurationController, - powerManager, - R.layout.media_ttt_chip, - MediaTttUtils.WINDOW_TITLE, - MediaTttUtils.WAKE_REASON, -) { - - private lateinit var parent: MediaTttChipRootView - - override val windowLayoutParams = commonWindowLayoutParams.apply { - gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) - } - - private val commandQueueCallbacks = object : CommandQueue.Callbacks { - override fun updateMediaTapToTransferSenderDisplay( - @StatusBarManager.MediaTransferSenderState displayState: Int, - routeInfo: MediaRoute2Info, - undoCallback: IUndoMediaTransferCallback? - ) { - this@MediaTttChipControllerSender.updateMediaTapToTransferSenderDisplay( - displayState, routeInfo, undoCallback - ) - } - } - - private fun updateMediaTapToTransferSenderDisplay( - @StatusBarManager.MediaTransferSenderState displayState: Int, - routeInfo: MediaRoute2Info, - undoCallback: IUndoMediaTransferCallback? - ) { - val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState) - val stateName = chipState?.name ?: "Invalid" - logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName) - - if (chipState == null) { - Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState") - return - } - uiEventLogger.logSenderStateChange(chipState) - - if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { - removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER.name) - } else { - displayView(ChipSenderInfo(chipState, routeInfo, undoCallback)) - } - } - - override fun start() { - commandQueue.addCallback(commandQueueCallbacks) - } - - override fun updateView( - newInfo: ChipSenderInfo, - currentView: ViewGroup - ) { - val chipState = newInfo.state - - // Detect falsing touches on the chip. - parent = currentView.requireViewById(R.id.media_ttt_sender_chip) - parent.touchHandler = object : Gefingerpoken { - override fun onTouchEvent(ev: MotionEvent?): Boolean { - falsingCollector.onTouchEvent(ev) - return false - } - } - - // App icon - val iconInfo = MediaTttUtils.getIconInfoFromPackageName( - context, newInfo.routeInfo.clientPackageName, logger - ) - val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon) - iconView.setImageDrawable(iconInfo.drawable) - iconView.contentDescription = iconInfo.contentDescription - - // Text - val otherDeviceName = newInfo.routeInfo.name.toString() - val chipText = chipState.getChipTextString(context, otherDeviceName) - currentView.requireViewById<TextView>(R.id.text).text = chipText - - // Loading - currentView.requireViewById<View>(R.id.loading).visibility = - (chipState.transferStatus == TransferStatus.IN_PROGRESS).visibleIfTrue() - - // Undo - val undoView = currentView.requireViewById<View>(R.id.undo) - val undoClickListener = chipState.undoClickListener( - this, - newInfo.routeInfo, - newInfo.undoCallback, - uiEventLogger, - falsingManager, - ) - undoView.setOnClickListener(undoClickListener) - undoView.visibility = (undoClickListener != null).visibleIfTrue() - - // Failure - currentView.requireViewById<View>(R.id.failure_icon).visibility = - (chipState.transferStatus == TransferStatus.FAILED).visibleIfTrue() - - // For accessibility - currentView.requireViewById<ViewGroup>( - R.id.media_ttt_sender_chip_inner - ).contentDescription = "${iconInfo.contentDescription} $chipText" - } - - override fun animateViewIn(view: ViewGroup) { - val chipInnerView = view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner) - ViewHierarchyAnimator.animateAddition( - chipInnerView, - ViewHierarchyAnimator.Hotspot.TOP, - Interpolators.EMPHASIZED_DECELERATE, - duration = ANIMATION_DURATION, - includeMargins = true, - includeFadeIn = true, - // We can only request focus once the animation finishes. - onAnimationEnd = { chipInnerView.requestAccessibilityFocus() }, - ) - } - - override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { - ViewHierarchyAnimator.animateRemoval( - view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner), - ViewHierarchyAnimator.Hotspot.TOP, - Interpolators.EMPHASIZED_ACCELERATE, - ANIMATION_DURATION, - onAnimationEnd, - ) - // TODO(b/203800644): Add includeMargins as an option to ViewHierarchyAnimator so that the - // animateChipOut matches the animateChipIn. - } - - override fun shouldIgnoreViewRemoval(info: ChipSenderInfo, removalReason: String): Boolean { - // 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. (But do remove it if it's finally timed out.) - val transferStatus = info.state.transferStatus - if ( - (transferStatus == TransferStatus.IN_PROGRESS || - transferStatus == TransferStatus.SUCCEEDED) && - removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT - ) { - logger.logRemovalBypass( - removalReason, bypassReason = "transferStatus=${transferStatus.name}" - ) - return true - } - return false - } - - override fun getTouchableRegion(view: View, outRect: Rect) { - viewUtil.setRectToViewWindowLocation(view, outRect) - } - - private fun Boolean.visibleIfTrue(): Int { - return if (this) { - View.VISIBLE - } else { - View.GONE - } - } -} - -data class ChipSenderInfo( - val state: ChipStateSender, - val routeInfo: MediaRoute2Info, - val undoCallback: IUndoMediaTransferCallback? = null -) : TemporaryViewInfo { - override fun getTimeoutMs() = state.timeout -} - -const val SENDER_TAG = "MediaTapToTransferSender" -private const val ANIMATION_DURATION = 500L 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 new file mode 100644 index 000000000000..fe2eed9c6079 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -0,0 +1,201 @@ +/* + * 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 android.app.StatusBarManager +import android.content.Context +import android.media.MediaRoute2Info +import android.util.Log +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.R +import com.android.systemui.common.shared.model.Text +import com.android.systemui.dagger.SysUISingleton +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.chipbar.ChipbarCoordinator +import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem +import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo +import com.android.systemui.temporarydisplay.chipbar.SENDER_TAG +import javax.inject.Inject + +/** + * A coordinator for showing/hiding the Media Tap-To-Transfer UI on the **sending** device. This UI + * is shown when a user is transferring media to/from this device and a receiver device. + */ +@SysUISingleton +class MediaTttSenderCoordinator +@Inject +constructor( + private val chipbarCoordinator: ChipbarCoordinator, + private val commandQueue: CommandQueue, + private val context: Context, + @MediaTttSenderLogger private val logger: MediaTttLogger, + private val mediaTttFlags: MediaTttFlags, + private val uiEventLogger: MediaTttSenderUiEventLogger, +) : CoreStartable { + + private var displayedState: ChipStateSender? = null + + private val commandQueueCallbacks = + object : CommandQueue.Callbacks { + override fun updateMediaTapToTransferSenderDisplay( + @StatusBarManager.MediaTransferSenderState displayState: Int, + routeInfo: MediaRoute2Info, + undoCallback: IUndoMediaTransferCallback? + ) { + this@MediaTttSenderCoordinator.updateMediaTapToTransferSenderDisplay( + displayState, + routeInfo, + undoCallback + ) + } + } + + override fun start() { + if (mediaTttFlags.isMediaTttEnabled()) { + commandQueue.addCallback(commandQueueCallbacks) + } + } + + private fun updateMediaTapToTransferSenderDisplay( + @StatusBarManager.MediaTransferSenderState displayState: Int, + routeInfo: MediaRoute2Info, + undoCallback: IUndoMediaTransferCallback? + ) { + val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState) + val stateName = chipState?.name ?: "Invalid" + logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName) + + if (chipState == null) { + Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState") + return + } + uiEventLogger.logSenderStateChange(chipState) + + if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { + // Return early if we're not displaying a chip anyway + val currentDisplayedState = displayedState ?: return + + val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name + if ( + currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS || + currentDisplayedState.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}" + ) + return + } + + displayedState = null + chipbarCoordinator.removeView(removalReason) + } else { + displayedState = chipState + chipbarCoordinator.displayView( + createChipbarInfo( + chipState, + routeInfo, + undoCallback, + context, + logger, + ) + ) + } + } + + /** + * Creates an instance of [ChipbarInfo] that can be sent to [ChipbarCoordinator] for display. + */ + private fun createChipbarInfo( + chipStateSender: ChipStateSender, + routeInfo: MediaRoute2Info, + undoCallback: IUndoMediaTransferCallback?, + context: Context, + logger: MediaTttLogger, + ): ChipbarInfo { + val packageName = routeInfo.clientPackageName + val otherDeviceName = routeInfo.name.toString() + + return ChipbarInfo( + // Display the app's icon as the start icon + startIcon = MediaTttUtils.getIconFromPackageName(context, packageName, logger), + text = chipStateSender.getChipTextString(context, otherDeviceName), + endItem = + when (chipStateSender.endItem) { + null -> null + is SenderEndItem.Loading -> ChipbarEndItem.Loading + is SenderEndItem.Error -> ChipbarEndItem.Error + is SenderEndItem.UndoButton -> { + if (undoCallback != null) { + getUndoButton( + undoCallback, + chipStateSender.endItem.uiEventOnClick, + chipStateSender.endItem.newState, + routeInfo, + ) + } else { + null + } + } + } + ) + } + + /** + * Returns an undo button for the chip. + * + * When the button is clicked: [undoCallback] will be triggered, [uiEvent] will be logged, and + * this coordinator will transition to [newState]. + */ + private fun getUndoButton( + undoCallback: IUndoMediaTransferCallback, + uiEvent: UiEventLogger.UiEventEnum, + @StatusBarManager.MediaTransferSenderState newState: Int, + routeInfo: MediaRoute2Info, + ): ChipbarEndItem.Button { + val onClickListener = + View.OnClickListener { + uiEventLogger.logUndoClicked(uiEvent) + undoCallback.onUndoTriggered() + + // The external service should eventually send us a new TransferTriggered state, but + // but that may take too long to go through the binder and the user may be confused + // as to why the UI hasn't changed yet. So, we immediately change the UI here. + updateMediaTapToTransferSenderDisplay( + newState, + routeInfo, + // Since we're force-updating the UI, we don't have any [undoCallback] from the + // external service (and TransferTriggered states don't have undo callbacks + // anyway). + undoCallback = null, + ) + } + + return ChipbarEndItem.Button( + Text.Resource(R.string.media_transfer_undo), + onClickListener, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 50a10bc0b15a..c089511a7ce9 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -114,6 +114,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener; @@ -211,6 +212,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final NotificationShadeDepthController mNotificationShadeDepthController; private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener; private final UserContextProvider mUserContextProvider; + private final WakefulnessLifecycle mWakefulnessLifecycle; private final RegionSamplingHelper mRegionSamplingHelper; private final int mNavColorSampleMargin; private NavigationBarFrame mFrame; @@ -451,6 +453,28 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } }; + private final WakefulnessLifecycle.Observer mWakefulnessObserver = + new WakefulnessLifecycle.Observer() { + private void notifyScreenStateChanged(boolean isScreenOn) { + notifyNavigationBarScreenOn(); + mView.onScreenStateChanged(isScreenOn); + } + + @Override + public void onStartedWakingUp() { + notifyScreenStateChanged(true); + if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) { + mRegionSamplingHelper.start(mSamplingBounds); + } + } + + @Override + public void onFinishedGoingToSleep() { + notifyScreenStateChanged(false); + mRegionSamplingHelper.stop(); + } + }; + @Inject NavigationBar( NavigationBarView navigationBarView, @@ -491,7 +515,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements NavigationBarTransitions navigationBarTransitions, EdgeBackGestureHandler edgeBackGestureHandler, Optional<BackAnimation> backAnimation, - UserContextProvider userContextProvider) { + UserContextProvider userContextProvider, + WakefulnessLifecycle wakefulnessLifecycle) { super(navigationBarView); mFrame = navigationBarFrame; mContext = context; @@ -529,6 +554,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mTelecomManagerOptional = telecomManagerOptional; mInputMethodManager = inputMethodManager; mUserContextProvider = userContextProvider; + mWakefulnessLifecycle = wakefulnessLifecycle; mNavColorSampleMargin = getResources() .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin); @@ -653,7 +679,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements public void onViewAttached() { final Display display = mView.getDisplay(); mView.setComponents(mRecentsOptional); - mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController()); + if (mCentralSurfacesOptionalLazy.get().isPresent()) { + mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController()); + } mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer); mView.setOnVerticalChangedListener(this::onVerticalChanged); mView.setOnTouchListener(this::onNavigationTouch); @@ -682,11 +710,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements prepareNavigationBarView(); checkNavBarModes(); - IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_USER_SWITCHED); + IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, Handler.getMain(), UserHandle.ALL); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); notifyNavigationBarScreenOn(); mOverviewProxyService.addCallback(mOverviewProxyListener); @@ -737,6 +764,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements getBarTransitions().destroy(); mOverviewProxyService.removeCallback(mOverviewProxyListener); mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); + mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); if (mOrientationHandle != null) { resetSecondaryHandle(); getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener); @@ -1619,19 +1647,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements return; } String action = intent.getAction(); - if (Intent.ACTION_SCREEN_OFF.equals(action) - || Intent.ACTION_SCREEN_ON.equals(action)) { - notifyNavigationBarScreenOn(); - boolean isScreenOn = Intent.ACTION_SCREEN_ON.equals(action); - mView.onScreenStateChanged(isScreenOn); - if (isScreenOn) { - if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) { - mRegionSamplingHelper.start(mSamplingBounds); - } - } else { - mRegionSamplingHelper.stop(); - } - } if (Intent.ACTION_USER_SWITCHED.equals(action)) { // The accessibility settings may be different for the new user updateAccessibilityStateFlags(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 029cf68b243d..3fd1aa73c033 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -21,6 +21,7 @@ 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; import android.content.ContentResolver; @@ -141,7 +142,13 @@ public class NavigationBarController implements public void onConfigChanged(Configuration newConfig) { boolean isOldConfigTablet = mIsTablet; mIsTablet = isTablet(mContext); + boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources()); boolean largeScreenChanged = mIsTablet != isOldConfigTablet; + // TODO(b/243765256): Disable this logging once b/243765256 is fixed. + Log.d(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig + + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized() + + " willApplyConfigToNavbars=" + willApplyConfig + + " navBarCount=" + mNavigationBars.size()); if (mTaskbarDelegate.isInitialized()) { mTaskbarDelegate.onConfigurationChanged(newConfig); } @@ -150,7 +157,7 @@ public class NavigationBarController implements return; } - if (mConfigChanges.applyNewConfig(mContext.getResources())) { + if (willApplyConfig) { for (int i = 0; i < mNavigationBars.size(); i++) { recreateNavigationBar(mNavigationBars.keyAt(i)); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 97024881ca62..403d276f8cbc 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -148,6 +148,7 @@ public class NavigationBarView extends FrameLayout { private NavigationBarInflaterView mNavigationInflaterView; private Optional<Recents> mRecentsOptional = Optional.empty(); + @Nullable private NotificationPanelViewController mPanelView; private RotationContextButton mRotationContextButton; private FloatingRotationButton mFloatingRotationButton; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index 6424256f36ab..6e927b071727 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -417,6 +417,7 @@ class BackPanelController private constructor( stretchEntryBackIndicator(preThresholdStretchProgress(xTranslation)) GestureState.INACTIVE -> mView.resetStretch() + else -> {} } // set y translation diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index a8799c744656..709467ffd3b5 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -113,7 +113,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private static final int MAX_NUM_LOGGED_GESTURES = 10; static final boolean DEBUG_MISSING_GESTURE = false; - static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; + public static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; private ISystemGestureExclusionListener mGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 67dae9e7a0ea..1da866efc08d 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -46,6 +46,7 @@ import com.android.systemui.CoreStartable; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -59,7 +60,7 @@ import javax.inject.Inject; import dagger.Lazy; @SysUISingleton -public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { +public class PowerUI implements CoreStartable, CommandQueue.Callbacks { static final String TAG = "PowerUI"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -78,6 +79,7 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { private final PowerManager mPowerManager; private final WarningsUI mWarnings; + private final WakefulnessLifecycle mWakefulnessLifecycle; private InattentiveSleepWarningView mOverlayView; private final Configuration mLastConfiguration = new Configuration(); private int mPlugType = 0; @@ -103,22 +105,37 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { private IThermalEventListener mSkinThermalEventListener; private IThermalEventListener mUsbThermalEventListener; + private final Context mContext; private final BroadcastDispatcher mBroadcastDispatcher; private final CommandQueue mCommandQueue; private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; + private final WakefulnessLifecycle.Observer mWakefulnessObserver = + new WakefulnessLifecycle.Observer() { + @Override + public void onStartedWakingUp() { + mScreenOffTime = -1; + } + + @Override + public void onFinishedGoingToSleep() { + mScreenOffTime = SystemClock.elapsedRealtime(); + } + }; @Inject public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher, CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, WarningsUI warningsUI, EnhancedEstimates enhancedEstimates, + WakefulnessLifecycle wakefulnessLifecycle, PowerManager powerManager) { - super(context); + mContext = context; mBroadcastDispatcher = broadcastDispatcher; mCommandQueue = commandQueue; mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; mWarnings = warningsUI; mEnhancedEstimates = enhancedEstimates; mPowerManager = powerManager; + mWakefulnessLifecycle = wakefulnessLifecycle; } public void start() { @@ -137,6 +154,7 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { false, obs, UserHandle.USER_ALL); updateBatteryWarningLevels(); mReceiver.init(); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); // Check to see if we need to let the user know that the phone previously shut down due // to the temperature being too high. @@ -169,7 +187,7 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { } @Override - protected void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(Configuration newConfig) { final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC; // Safe to modify mLastConfiguration here as it's only updated by the main thread (here). @@ -232,8 +250,6 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_USER_SWITCHED); mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler); // Force get initial values. Relying on Sticky behavior until API for getting info. @@ -316,10 +332,6 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { plugged, bucket); }); - } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mScreenOffTime = SystemClock.elapsedRealtime(); - } else if (Intent.ACTION_SCREEN_ON.equals(action)) { - mScreenOffTime = -1; } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { mWarnings.userSwitched(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt index 1ea93474f954..03503fd1ff61 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt @@ -17,10 +17,10 @@ package com.android.systemui.privacy.logging import android.permission.PermissionGroupUsage -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogMessage import com.android.systemui.log.dagger.PrivacyLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogMessage import com.android.systemui.privacy.PrivacyDialog import com.android.systemui.privacy.PrivacyItem import java.text.SimpleDateFormat diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java index 5510eb172cd7..cd32a10a432b 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java +++ b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java @@ -67,7 +67,7 @@ import javax.inject.Inject; * recording audio, accessing the camera or accessing the location. */ @SysUISingleton -public class TvOngoingPrivacyChip extends CoreStartable implements PrivacyItemController.Callback, +public class TvOngoingPrivacyChip implements CoreStartable, PrivacyItemController.Callback, PrivacyChipDrawable.PrivacyChipDrawableListener { private static final String TAG = "TvOngoingPrivacyChip"; private static final boolean DEBUG = false; @@ -134,7 +134,6 @@ public class TvOngoingPrivacyChip extends CoreStartable implements PrivacyItemCo @Inject public TvOngoingPrivacyChip(Context context, PrivacyItemController privacyItemController, IWindowManager iWindowManager) { - super(context); if (DEBUG) Log.d(TAG, "Privacy chip running"); mContext = context; mPrivacyItemController = privacyItemController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 1ef6426d52a0..0fe3d1699de0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -691,6 +691,15 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca if (mQSAnimator != null) { mQSAnimator.setPosition(expansion); } + if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD + || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) { + // At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen + // and media player expect no change by squishiness in lock screen shade + mQsMediaHost.setSquishFraction(1.0F); + } else { + mQsMediaHost.setSquishFraction(mSquishinessFraction); + } + } private void setAlphaAnimationProgress(float progress) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt index 8544f61d7031..c663aa605ca2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt @@ -1,8 +1,8 @@ package com.android.systemui.qs -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.QSFragmentDisableLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.DisableFlagsLogger import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 84d7e65d289b..27d9da6c2e1e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -25,6 +25,7 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.util.Pair; import android.view.DisplayCutout; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; @@ -231,6 +232,16 @@ public class QuickStatusBarHeader extends FrameLayout { } } + @Override + public boolean onTouchEvent(MotionEvent event) { + // If using combined headers, only react to touches inside QuickQSPanel + if (!mUseCombinedQSHeader || event.getY() > mHeaderQsPanel.getTop()) { + return super.onTouchEvent(event); + } else { + return false; + } + } + void updateResources() { Resources resources = mContext.getResources(); boolean largeScreenHeaderActive = diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 4cacbbacec2f..5d03da3cc113 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -35,6 +35,7 @@ import androidx.annotation.Nullable; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.QSTileHost; import com.android.systemui.settings.UserTracker; @@ -53,6 +54,7 @@ import javax.inject.Provider; /** * Runs the day-to-day operations of which tiles should be bound and when. */ +@SysUISingleton public class TileServices extends IQSService.Stub { static final int DEFAULT_MAX_BOUND = 3; static final int REDUCED_MAX_BOUND = 1; diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index 11d955580983..d3c06f60bc90 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -23,7 +23,6 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import com.android.settingslib.Utils -import com.android.settingslib.drawable.UserIconDrawable import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription @@ -250,22 +249,19 @@ class FooterActionsViewModel( status: UserSwitcherStatusModel.Enabled ): FooterActionsButtonViewModel { val icon = status.currentUserImage!! - val iconTint = - if (status.isGuestUser && icon !is UserIconDrawable) { - Utils.getColorAttrDefaultColor(context, android.R.attr.colorForeground) - } else { - null - } return FooterActionsButtonViewModel( id = R.id.multi_user_switch, - Icon.Loaded( - icon, - ContentDescription.Loaded(userSwitcherContentDescription(status.currentUserName)), - ), - iconTint, - R.drawable.qs_footer_action_circle, - this::onUserSwitcherClicked, + icon = + Icon.Loaded( + icon, + ContentDescription.Loaded( + userSwitcherContentDescription(status.currentUserName) + ), + ), + iconTint = null, + background = R.drawable.qs_footer_action_circle, + onClick = this::onUserSwitcherClicked, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index 60380064e098..931dc8df151a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -17,12 +17,12 @@ package com.android.systemui.qs.logging import android.service.quicksettings.Tile -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.VERBOSE -import com.android.systemui.log.LogMessage import com.android.systemui.log.dagger.QSLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.VERBOSE +import com.android.systemui.plugins.log.LogMessage import com.android.systemui.plugins.qs.QSTile import com.android.systemui.statusbar.StatusBarState import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index d2d5063c7ae0..b6b657ec82f6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -26,6 +26,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; @@ -43,6 +44,9 @@ import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.user.data.source.UserRecord; +import java.util.List; +import java.util.stream.Collectors; + import javax.inject.Inject; /** @@ -83,6 +87,13 @@ public class UserDetailView extends PseudoGridView { private final FalsingManager mFalsingManager; private @Nullable UserSwitchDialogController.DialogShower mDialogShower; + @NonNull + @Override + protected List<UserRecord> getUsers() { + return super.getUsers().stream().filter( + userRecord -> !userRecord.isManageUsers).collect(Collectors.toList()); + } + @Inject public Adapter(Context context, UserSwitcherController controller, UiEventLogger uiEventLogger, FalsingManager falsingManager) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 9b3b843c9848..b041f957d771 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -29,13 +29,14 @@ import java.io.PrintWriter; /** * A proxy to a Recents implementation. */ -public class Recents extends CoreStartable implements CommandQueue.Callbacks { +public class Recents implements CoreStartable, CommandQueue.Callbacks { + private final Context mContext; private final RecentsImplementation mImpl; private final CommandQueue mCommandQueue; public Recents(Context context, RecentsImplementation impl, CommandQueue commandQueue) { - super(context); + mContext = context; mImpl = impl; mCommandQueue = commandQueue; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt new file mode 100644 index 000000000000..017e57fcaf62 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt @@ -0,0 +1,76 @@ +/* + * 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.content.ClipData +import android.content.ClipDescription +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.net.Uri +import com.android.systemui.R + +object ActionIntentCreator { + /** @return a chooser intent to share the given URI with the optional provided subject. */ + fun createShareIntent(uri: Uri, subject: String?): Intent { + // Create a share intent, this will always go through the chooser activity first + // which should not trigger auto-enter PiP + val sharingIntent = + Intent(Intent.ACTION_SEND).apply { + setDataAndType(uri, "image/png") + putExtra(Intent.EXTRA_STREAM, uri) + + // Include URI in ClipData also, so that grantPermission picks it up. + // We don't use setData here because some apps interpret this as "to:". + clipData = + ClipData( + ClipDescription("content", arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)), + ClipData.Item(uri) + ) + + putExtra(Intent.EXTRA_SUBJECT, subject) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } + + return Intent.createChooser(sharingIntent, null) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + /** + * @return an ACTION_EDIT intent for the given URI, directed to config_screenshotEditor if + * available. + */ + fun createEditIntent(uri: Uri, context: Context): Intent { + val editIntent = Intent(Intent.ACTION_EDIT) + + context.getString(R.string.config_screenshotEditor)?.let { + if (it.isNotEmpty()) { + editIntent.component = ComponentName.unflattenFromString(it) + } + } + + return editIntent + .setDataAndType(uri, "image/png") + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt new file mode 100644 index 000000000000..5961635a0dba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -0,0 +1,159 @@ +/* + * 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.content.Context +import android.content.Intent +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 +import android.view.RemoteAnimationTarget +import android.view.WindowManager +import android.view.WindowManagerGlobal +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.Background +import javax.inject.Inject +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +@SysUISingleton +class ActionIntentExecutor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val bgDispatcher: CoroutineDispatcher, + private val context: Context, +) { + /** + * Execute the given intent with startActivity while performing operations for screenshot action + * launching. + * - Dismiss the keyguard first + * - If the userId is not the current user, proxy to a service running as that user to execute + * - After startActivity, optionally override the pending app transition. + */ + fun launchIntentAsync( + intent: Intent, + bundle: Bundle, + userId: Int, + overrideTransition: Boolean, + ) { + applicationScope.launch { launchIntent(intent, bundle, userId, overrideTransition) } + } + + suspend fun launchIntent( + intent: Intent, + bundle: Bundle, + userId: Int, + overrideTransition: Boolean, + ) { + withContext(bgDispatcher) { + dismissKeyguard() + + if (userId == UserHandle.myUserId()) { + context.startActivity(intent, bundle) + } else { + launchCrossProfileIntent(userId, intent, bundle) + } + + if (overrideTransition) { + val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0) + try { + WindowManagerGlobal.getWindowManagerService() + .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY) + } catch (e: Exception) { + Log.e(TAG, "Error overriding screenshot app transition", e) + } + } + } + } + + private val proxyConnector: ServiceConnector<IScreenshotProxy> = + ServiceConnector.Impl( + context, + Intent(context, ScreenshotProxyService::class.java), + Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, + context.userId, + IScreenshotProxy.Stub::asInterface, + ) + + private suspend fun dismissKeyguard() { + val completion = CompletableDeferred<Unit>() + val onDoneBinder = + object : IOnDoneCallback.Stub() { + override fun onDone(success: Boolean) { + completion.complete(Unit) + } + } + proxyConnector.post { it.dismissKeyguard(onDoneBinder) } + completion.await() + } + + private fun getCrossProfileConnector(userId: Int): ServiceConnector<ICrossProfileService> = + ServiceConnector.Impl<ICrossProfileService>( + context, + Intent(context, ScreenshotCrossProfileService::class.java), + Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, + userId, + ICrossProfileService.Stub::asInterface, + ) + + private suspend fun launchCrossProfileIntent(userId: Int, intent: Intent, bundle: Bundle) { + val connector = getCrossProfileConnector(userId) + val completion = CompletableDeferred<Unit>() + connector.post { + it.launchIntent(intent, bundle) + completion.complete(Unit) + } + completion.await() + } +} + +private const val TAG: String = "ActionIntentExecutor" +private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)" + +/** + * This is effectively a no-op, but we need something non-null to pass in, in order to successfully + * override the pending activity entrance animation. + */ +private val SCREENSHOT_REMOTE_RUNNER: IRemoteAnimationRunner.Stub = + object : IRemoteAnimationRunner.Stub() { + override fun onAnimationStart( + @WindowManager.TransitionOldType transit: Int, + apps: Array<RemoteAnimationTarget>, + wallpapers: Array<RemoteAnimationTarget>, + nonApps: Array<RemoteAnimationTarget>, + finishedCallback: IRemoteAnimationFinishedCallback, + ) { + try { + finishedCallback.onAnimationFinished() + } catch (e: RemoteException) { + Log.e(TAG, "Error finishing screenshot remote animation", e) + } + } + + override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {} + } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java index 950806d89422..ead3b7b1de53 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java @@ -49,7 +49,6 @@ public class DraggableConstraintLayout extends ConstraintLayout private final SwipeDismissHandler mSwipeDismissHandler; private final GestureDetector mSwipeDetector; private View mActionsContainer; - private View mActionsContainerBackground; private SwipeDismissCallbacks mCallbacks; private final DisplayMetrics mDisplayMetrics; @@ -111,6 +110,9 @@ public class DraggableConstraintLayout extends ConstraintLayout } }); mSwipeDetector.setIsLongpressEnabled(false); + + mCallbacks = new SwipeDismissCallbacks() { + }; // default to unimplemented callbacks } public void setCallbacks(SwipeDismissCallbacks callbacks) { @@ -119,16 +121,13 @@ public class DraggableConstraintLayout extends ConstraintLayout @Override public boolean onInterceptHoverEvent(MotionEvent event) { - if (mCallbacks != null) { - mCallbacks.onInteraction(); - } + mCallbacks.onInteraction(); return super.onInterceptHoverEvent(event); } @Override // View protected void onFinishInflate() { mActionsContainer = findViewById(R.id.actions_container); - mActionsContainerBackground = findViewById(R.id.actions_container_background); } @Override @@ -186,6 +185,13 @@ public class DraggableConstraintLayout extends ConstraintLayout inoutInfo.touchableRegion.set(r); } + private int getBackgroundRight() { + // background expected to be null in testing. + // animation may have unexpected behavior if view is not present + View background = findViewById(R.id.actions_container_background); + return background == null ? 0 : background.getRight(); + } + /** * Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not * met @@ -213,8 +219,6 @@ public class DraggableConstraintLayout extends ConstraintLayout mGestureDetector = new GestureDetector(context, gestureListener); mDisplayMetrics = new DisplayMetrics(); context.getDisplay().getRealMetrics(mDisplayMetrics); - mCallbacks = new SwipeDismissCallbacks() { - }; // default to unimplemented callbacks } @Override @@ -230,7 +234,9 @@ public class DraggableConstraintLayout extends ConstraintLayout return true; } if (isPastDismissThreshold()) { - dismiss(); + ValueAnimator anim = createSwipeDismissAnimation(); + mCallbacks.onSwipeDismissInitiated(anim); + dismiss(anim); } else { // if we've moved, but not past the threshold, start the return animation if (DEBUG_DISMISS) { @@ -295,10 +301,7 @@ public class DraggableConstraintLayout extends ConstraintLayout } void dismiss() { - float velocityPxPerMs = FloatingWindowUtil.dpToPx(mDisplayMetrics, VELOCITY_DP_PER_MS); - ValueAnimator anim = createSwipeDismissAnimation(velocityPxPerMs); - mCallbacks.onSwipeDismissInitiated(anim); - dismiss(anim); + dismiss(createSwipeDismissAnimation()); } private void dismiss(ValueAnimator animator) { @@ -323,6 +326,11 @@ public class DraggableConstraintLayout extends ConstraintLayout mDismissAnimation.start(); } + private ValueAnimator createSwipeDismissAnimation() { + float velocityPxPerMs = FloatingWindowUtil.dpToPx(mDisplayMetrics, VELOCITY_DP_PER_MS); + return createSwipeDismissAnimation(velocityPxPerMs); + } + private ValueAnimator createSwipeDismissAnimation(float velocity) { // velocity is measured in pixels per millisecond velocity = Math.min(3, Math.max(1, velocity)); @@ -337,7 +345,7 @@ public class DraggableConstraintLayout extends ConstraintLayout if (startX > 0 || (startX == 0 && layoutDir == LAYOUT_DIRECTION_RTL)) { finalX = mDisplayMetrics.widthPixels; } else { - finalX = -1 * mActionsContainerBackground.getRight(); + finalX = -1 * getBackgroundRight(); } float distance = Math.abs(finalX - startX); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl new file mode 100644 index 000000000000..da834729d319 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; + +/** Interface implemented by ScreenshotCrossProfileService */ +interface ICrossProfileService { + + void launchIntent(in Intent intent, in Bundle bundle); +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl new file mode 100644 index 000000000000..e15030f78234 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl @@ -0,0 +1,21 @@ +/** + * 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; + +interface IOnDoneCallback { + void onDone(boolean success); +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl index f7c4dadc6605..d2e3fbd65762 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl +++ b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl @@ -16,9 +16,14 @@ package com.android.systemui.screenshot; +import com.android.systemui.screenshot.IOnDoneCallback; + /** Interface implemented by ScreenshotProxyService */ interface IScreenshotProxy { /** Is the notification shade currently exanded? */ boolean isNotificationShadeExpanded(); -}
\ No newline at end of file + + /** Attempts to dismiss the keyguard. */ + void dismissKeyguard(IOnDoneCallback callback); +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 077ad35fd63f..7143ba263570 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -173,6 +173,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); mImageData.quickShareAction = createQuickShareAction(mContext, mQuickShareData.quickShareAction, uri); + mImageData.subject = getSubjectString(); mParams.mActionsReadyListener.onActionsReady(mImageData); if (DEBUG_CALLBACK) { @@ -237,8 +238,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Create a share intent, this will always go through the chooser activity first // which should not trigger auto-enter PiP - String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime)); - String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate); Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setDataAndType(uri, "image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); @@ -248,7 +247,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}), new ClipData.Item(uri)); sharingIntent.setClipData(clipdata); - sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); + sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString()); sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); @@ -318,7 +317,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // by setting the (otherwise unused) request code to the current user id. int requestCode = mContext.getUserId(); - // Create a edit action + // Create an edit action PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, ActionProxyReceiver.class) .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent) @@ -479,4 +478,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData); } } + + private String getSubjectString() { + String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime)); + return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate); + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 704e11512b37..231e415f17c6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -28,6 +28,7 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_UI; import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW; import static com.android.systemui.screenshot.LogConfig.logTag; import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER; +import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT; import static java.util.Objects.requireNonNull; @@ -173,7 +174,7 @@ public class ScreenshotController { public List<Notification.Action> smartActions; public Notification.Action quickShareAction; public UserHandle owner; - + public String subject; // Title for sharing /** * POD for shared element transition. @@ -194,6 +195,7 @@ public class ScreenshotController { deleteAction = null; smartActions = null; quickShareAction = null; + subject = null; } } @@ -272,6 +274,7 @@ public class ScreenshotController { private final ScreenshotNotificationSmartActionsProvider mScreenshotNotificationSmartActionsProvider; private final TimeoutHandler mScreenshotHandler; + private final ActionIntentExecutor mActionExecutor; private ScreenshotView mScreenshotView; private Bitmap mScreenBitmap; @@ -309,7 +312,8 @@ public class ScreenshotController { ActivityManager activityManager, TimeoutHandler timeoutHandler, BroadcastSender broadcastSender, - ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider + ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, + ActionIntentExecutor actionExecutor ) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; @@ -331,9 +335,7 @@ public class ScreenshotController { if (DEBUG_UI) { Log.d(TAG, "Corner timeout hit"); } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0, - mPackageName); - ScreenshotController.this.dismissScreenshot(false); + dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT); }); mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); @@ -341,6 +343,7 @@ public class ScreenshotController { mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); mWindowManager = mContext.getSystemService(WindowManager.class); mFlags = flags; + mActionExecutor = actionExecutor; mAccessibilityManager = AccessibilityManager.getInstance(mContext); @@ -361,8 +364,7 @@ public class ScreenshotController { @Override public void onReceive(Context context, Intent intent) { if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) { - mUiEventLogger.log(SCREENSHOT_DISMISSED_OTHER); - dismissScreenshot(false); + dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); } } }; @@ -410,24 +412,20 @@ public class ScreenshotController { /** * Clears current screenshot */ - void dismissScreenshot(boolean immediate) { + void dismissScreenshot(ScreenshotEvent event) { if (DEBUG_DISMISS) { - Log.d(TAG, "dismissScreenshot(immediate=" + immediate + ")"); + Log.d(TAG, "dismissScreenshot"); } // If we're already animating out, don't restart the animation - // (but do obey an immediate dismissal) - if (!immediate && mScreenshotView.isDismissing()) { + if (mScreenshotView.isDismissing()) { if (DEBUG_DISMISS) { Log.v(TAG, "Already dismissing, ignoring duplicate command"); } return; } + mUiEventLogger.log(event, 0, mPackageName); mScreenshotHandler.cancelTimeout(); - if (immediate) { - finishDismiss(); - } else { - mScreenshotView.animateDismissal(); - } + mScreenshotView.animateDismissal(); } boolean isPendingSharedTransition() { @@ -492,7 +490,7 @@ public class ScreenshotController { // TODO(159460485): Remove this when focus is handled properly in the system setWindowFocusable(false); } - }); + }, mActionExecutor, mFlags); mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); mScreenshotView.setOnKeyListener((v, keyCode, event) -> { @@ -500,7 +498,7 @@ public class ScreenshotController { if (DEBUG_INPUT) { Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK"); } - dismissScreenshot(false); + dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); return true; } return false; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt new file mode 100644 index 000000000000..2e6c7567259f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.app.Service +import android.content.Intent +import android.os.Bundle +import android.os.IBinder +import android.util.Log + +/** + * If a screenshot is saved to the work profile, any intents that grant access to the screenshot + * must come from a service running as the work profile user. This service is meant to be started as + * the desired user and just startActivity for the given intent. + */ +class ScreenshotCrossProfileService : Service() { + + private val mBinder: IBinder = + object : ICrossProfileService.Stub() { + override fun launchIntent(intent: Intent, bundle: Bundle) { + startActivity(intent, bundle) + } + } + + override fun onBind(intent: Intent): IBinder? { + Log.d(TAG, "onBind: $intent") + return mBinder + } + + companion object { + const val TAG = "ScreenshotProxyService" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt index 793085a60133..c41e2bc14afc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt @@ -20,13 +20,16 @@ import android.content.Intent import android.os.IBinder import android.util.Log import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.statusbar.phone.CentralSurfaces +import java.util.Optional import javax.inject.Inject /** * Provides state from the main SystemUI process on behalf of the Screenshot process. */ internal class ScreenshotProxyService @Inject constructor( - private val mExpansionMgr: ShadeExpansionStateManager + private val mExpansionMgr: ShadeExpansionStateManager, + private val mCentralSurfacesOptional: Optional<CentralSurfaces>, ) : Service() { private val mBinder: IBinder = object : IScreenshotProxy.Stub() { @@ -38,6 +41,20 @@ internal class ScreenshotProxyService @Inject constructor( Log.d(TAG, "isNotificationShadeExpanded(): $expanded") return expanded } + + override fun dismissKeyguard(callback: IOnDoneCallback) { + if (mCentralSurfacesOptional.isPresent) { + mCentralSurfacesOptional.get().executeRunnableDismissingKeyguard( + Runnable { + callback.onDone(true) + }, null, + true /* dismissShade */, true /* afterKeyguardGone */, + true /* deferred */ + ) + } else { + callback.onDone(false) + } + } } override fun onBind(intent: Intent): IBinder? { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index be41a6b0d376..26cbcbf5214f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -87,6 +87,8 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; @@ -168,6 +170,8 @@ public class ScreenshotView extends FrameLayout implements private final InteractionJankMonitor mInteractionJankMonitor; private long mDefaultTimeoutOfTimeoutHandler; + private ActionIntentExecutor mActionExecutor; + private FeatureFlags mFlags; private enum PendingInteraction { PREVIEW, @@ -422,9 +426,12 @@ public class ScreenshotView extends FrameLayout implements * Note: must be called before any other (non-constructor) method or null pointer exceptions * may occur. */ - void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks) { + void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, + ActionIntentExecutor actionExecutor, FeatureFlags flags) { mUiEventLogger = uiEventLogger; mCallbacks = callbacks; + mActionExecutor = actionExecutor; + mFlags = flags; } void setScreenshot(Bitmap bitmap, Insets screenInsets) { @@ -759,18 +766,37 @@ public class ScreenshotView extends FrameLayout implements void setChipIntents(ScreenshotController.SavedImageData imageData) { mShareChip.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName); - startSharedTransition( - imageData.shareTransition.get()); + if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { + mActionExecutor.launchIntentAsync(ActionIntentCreator.INSTANCE.createShareIntent( + imageData.uri, imageData.subject), + imageData.shareTransition.get().bundle, + imageData.owner.getIdentifier(), false); + } else { + startSharedTransition(imageData.shareTransition.get()); + } }); mEditChip.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName); - startSharedTransition( - imageData.editTransition.get()); + if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { + mActionExecutor.launchIntentAsync( + ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext), + imageData.editTransition.get().bundle, + imageData.owner.getIdentifier(), true); + } else { + startSharedTransition(imageData.editTransition.get()); + } }); mScreenshotPreview.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName); - startSharedTransition( - imageData.editTransition.get()); + if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { + mActionExecutor.launchIntentAsync( + ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext), + imageData.editTransition.get().bundle, + imageData.owner.getIdentifier(), true); + } else { + startSharedTransition( + imageData.editTransition.get()); + } }); if (mQuickShareChip != null) { mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index a4a59ce52c7a..2176825d8b38 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -89,8 +89,7 @@ public class TakeScreenshotService extends Service { Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS"); } if (!mScreenshot.isPendingSharedTransition()) { - mUiEventLogger.log(SCREENSHOT_DISMISSED_OTHER); - mScreenshot.dismissScreenshot(false); + mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); } } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt index ad073c073ed8..d450afa59c7d 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt @@ -42,11 +42,11 @@ import javax.inject.Inject @SysUISingleton class UserFileManagerImpl @Inject constructor( // Context of system process and system user. - val context: Context, + private val context: Context, val userManager: UserManager, val broadcastDispatcher: BroadcastDispatcher, @Background val backgroundExecutor: DelayableExecutor -) : UserFileManager, CoreStartable(context) { +) : UserFileManager, CoreStartable { companion object { private const val FILES = "files" @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs" diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index d3ed47407b9d..6b540aa9f392 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -280,6 +280,9 @@ class LargeScreenShadeHeaderController @Inject constructor( context.getString(com.android.internal.R.string.status_bar_alarm_clock) ) } + if (combinedHeaders) { + privacyIconsController.onParentVisible() + } } override fun onViewAttached() { @@ -289,6 +292,7 @@ class LargeScreenShadeHeaderController @Inject constructor( clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f v.pivotX = newPivot + v.pivotY = v.height.toFloat() / 2 } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt index 07e8b9fe3123..754036d3baa9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt @@ -16,7 +16,7 @@ package com.android.systemui.shade import android.view.MotionEvent import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.util.collection.RingBuffer +import com.android.systemui.plugins.util.RingBuffer import java.text.SimpleDateFormat import java.util.Locale diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index cd1c722780f3..c1afd59a1e22 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -17,6 +17,8 @@ package com.android.systemui.shade; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; import static androidx.constraintlayout.widget.ConstraintSet.END; import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; @@ -26,8 +28,12 @@ import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE; import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE; +import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; +import static com.android.systemui.classifier.Classifier.GENERIC; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; +import static com.android.systemui.classifier.Classifier.UNLOCK; +import static com.android.systemui.shade.NotificationPanelView.DEBUG; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING; @@ -41,6 +47,8 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD; import static com.android.systemui.util.DumpUtilsKt.asIndenting; +import static java.lang.Float.isNaN; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -48,6 +56,8 @@ import android.annotation.NonNull; import android.app.Fragment; import android.app.StatusBarManager; import android.content.ContentResolver; +import android.content.res.Configuration; +import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.Color; @@ -73,11 +83,13 @@ import android.transition.TransitionManager; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; +import android.view.InputDevice; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.View.AccessibilityDelegate; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewPropertyAnimator; import android.view.ViewStub; @@ -86,6 +98,7 @@ import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.animation.Interpolator; import android.widget.FrameLayout; import androidx.annotation.Nullable; @@ -178,6 +191,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import com.android.systemui.statusbar.phone.BounceInterpolator; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; @@ -230,8 +244,13 @@ import javax.inject.Inject; import javax.inject.Provider; @CentralSurfacesComponent.CentralSurfacesScope -public final class NotificationPanelViewController extends PanelViewController { +public final class NotificationPanelViewController { + public static final String TAG = NotificationPanelView.class.getSimpleName(); + public static final float FLING_MAX_LENGTH_SECONDS = 0.6f; + public static final float FLING_SPEED_UP_FACTOR = 0.6f; + public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f; + public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f; private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); private static final boolean DEBUG_DRAWABLE = false; @@ -262,6 +281,22 @@ public final class NotificationPanelViewController extends PanelViewController { ActivityLaunchAnimator.TIMINGS.getTotalDuration() - CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY - 48; + private static final int NO_FIXED_DURATION = -1; + private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L; + private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L; + /** + * The factor of the usual high velocity that is needed in order to reach the maximum overshoot + * when flinging. A low value will make it that most flings will reach the maximum overshoot. + */ + private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f; + private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; + private final Resources mResources; + private final KeyguardStateController mKeyguardStateController; + private final SysuiStatusBarStateController mStatusBarStateController; + private final AmbientState mAmbientState; + private final LockscreenGestureLogger mLockscreenGestureLogger; + private final SystemClock mSystemClock; + private final ShadeLogger mShadeLog; private final DozeParameters mDozeParameters; private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener(); @@ -333,6 +368,28 @@ public final class NotificationPanelViewController extends PanelViewController { private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; private final RecordingController mRecordingController; private final PanelEventsEmitter mPanelEventsEmitter; + private final boolean mVibrateOnOpening; + private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); + private final FlingAnimationUtils mFlingAnimationUtilsClosing; + private final FlingAnimationUtils mFlingAnimationUtilsDismissing; + private final LatencyTracker mLatencyTracker; + private final DozeLog mDozeLog; + /** Whether or not the NotificationPanelView can be expanded or collapsed with a drag. */ + private final boolean mNotificationsDragEnabled; + private final Interpolator mBounceInterpolator; + private final NotificationShadeWindowController mNotificationShadeWindowController; + private final ShadeExpansionStateManager mShadeExpansionStateManager; + private long mDownTime; + private boolean mTouchSlopExceededBeforeDown; + private boolean mIsLaunchAnimationRunning; + private float mOverExpansion; + private CentralSurfaces mCentralSurfaces; + private HeadsUpManagerPhone mHeadsUpManager; + private float mExpandedHeight = 0; + private boolean mTracking; + private boolean mHintAnimationRunning; + private KeyguardBottomAreaView mKeyguardBottomArea; + private boolean mExpanding; private boolean mSplitShadeEnabled; /** The bottom padding reserved for elements of the keyguard measuring notifications. */ private float mKeyguardNotificationBottomPadding; @@ -632,6 +689,7 @@ public final class NotificationPanelViewController extends PanelViewController { private int mScreenCornerRadius; private boolean mQSAnimatingHiddenFromCollapsed; private boolean mUseLargeScreenShadeHeader; + private boolean mEnableQsClipping; private int mQsClipTop; private int mQsClipBottom; @@ -706,6 +764,54 @@ public final class NotificationPanelViewController extends PanelViewController { private final CameraGestureHelper mCameraGestureHelper; private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; + private float mMinExpandHeight; + private boolean mPanelUpdateWhenAnimatorEnds; + private boolean mHasVibratedOnOpen = false; + private int mFixedDuration = NO_FIXED_DURATION; + /** The overshoot amount when the panel flings open. */ + private float mPanelFlingOvershootAmount; + /** The amount of pixels that we have overexpanded the last time with a gesture. */ + private float mLastGesturedOverExpansion = -1; + /** Whether the current animator is the spring back animation. */ + private boolean mIsSpringBackAnimation; + private boolean mInSplitShade; + private float mHintDistance; + private float mInitialOffsetOnTouch; + private boolean mCollapsedAndHeadsUpOnDown; + private float mExpandedFraction = 0; + private float mExpansionDragDownAmountPx = 0; + private boolean mPanelClosedOnDown; + private boolean mHasLayoutedSinceDown; + private float mUpdateFlingVelocity; + private boolean mUpdateFlingOnLayout; + private boolean mClosing; + private boolean mTouchSlopExceeded; + private int mTrackingPointer; + private int mTouchSlop; + private float mSlopMultiplier; + private boolean mTouchAboveFalsingThreshold; + private boolean mTouchStartedInEmptyArea; + private boolean mMotionAborted; + private boolean mUpwardsWhenThresholdReached; + private boolean mAnimatingOnDown; + private boolean mHandlingPointerUp; + private ValueAnimator mHeightAnimator; + /** Whether an instant expand request is currently pending and we are waiting for layout. */ + private boolean mInstantExpanding; + private boolean mAnimateAfterExpanding; + private boolean mIsFlinging; + private String mViewName; + private float mInitialExpandY; + private float mInitialExpandX; + private boolean mTouchDisabled; + private boolean mInitialTouchFromKeyguard; + /** Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time. */ + private float mNextCollapseSpeedUpFactor = 1.0f; + private boolean mGestureWaitForTouchSlop; + private boolean mIgnoreXTouchSlop; + private boolean mExpandLatencyTracking; + private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, + mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */); @Inject public NotificationPanelViewController(NotificationPanelView view, @@ -775,32 +881,73 @@ public final class NotificationPanelViewController extends PanelViewController { CameraGestureHelper cameraGestureHelper, KeyguardBottomAreaViewModel keyguardBottomAreaViewModel, KeyguardBottomAreaInteractor keyguardBottomAreaInteractor) { - super(view, - falsingManager, - dozeLog, - keyguardStateController, - (SysuiStatusBarStateController) statusBarStateController, - notificationShadeWindowController, - vibratorHelper, - statusBarKeyguardViewManager, - latencyTracker, - flingAnimationUtilsBuilder.get(), - statusBarTouchableRegionManager, - lockscreenGestureLogger, - shadeExpansionStateManager, - ambientState, - interactionJankMonitor, - shadeLogger, - systemClock); + keyguardStateController.addCallback(new KeyguardStateController.Callback() { + @Override + public void onKeyguardFadingAwayChanged() { + updateExpandedHeightToMaxHeight(); + } + }); + mAmbientState = ambientState; mView = view; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mLockscreenGestureLogger = lockscreenGestureLogger; + mShadeExpansionStateManager = shadeExpansionStateManager; + mShadeLog = shadeLogger; + TouchHandler touchHandler = createTouchHandler(); + mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mViewName = mResources.getResourceName(mView.getId()); + } + + @Override + public void onViewDetachedFromWindow(View v) { + } + }); + + mView.addOnLayoutChangeListener(createLayoutChangeListener()); + mView.setOnTouchListener(touchHandler); + mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener()); + + mResources = mView.getResources(); + mKeyguardStateController = keyguardStateController; + mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController; + mNotificationShadeWindowController = notificationShadeWindowController; + FlingAnimationUtils.Builder fauBuilder = flingAnimationUtilsBuilder.get(); + mFlingAnimationUtils = fauBuilder + .reset() + .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS) + .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) + .build(); + mFlingAnimationUtilsClosing = fauBuilder + .reset() + .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS) + .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR) + .build(); + mFlingAnimationUtilsDismissing = fauBuilder + .reset() + .setMaxLengthSeconds(0.5f) + .setSpeedUpFactor(0.6f) + .setX2(0.6f) + .setY2(0.84f) + .build(); + mLatencyTracker = latencyTracker; + mBounceInterpolator = new BounceInterpolator(); + mFalsingManager = falsingManager; + mDozeLog = dozeLog; + mNotificationsDragEnabled = mResources.getBoolean( + R.bool.config_enableNotificationShadeDrag); mVibratorHelper = vibratorHelper; + mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation); + mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; + mInteractionJankMonitor = interactionJankMonitor; + mSystemClock = systemClock; mKeyguardMediaController = keyguardMediaController; mPrivacyDotViewController = privacyDotViewController; mMetricsLogger = metricsLogger; mConfigurationController = configurationController; mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder; mMediaHierarchyManager = mediaHierarchyManager; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mNotificationsQSContainerController = notificationsQSContainerController; mNotificationListContainer = notificationListContainer; mNotificationStackSizeCalculator = notificationStackSizeCalculator; @@ -822,7 +969,6 @@ public final class NotificationPanelViewController extends PanelViewController { mLargeScreenShadeHeaderController = largeScreenShadeHeaderController; mLayoutInflater = layoutInflater; mFeatureFlags = featureFlags; - mFalsingManager = falsingManager; mFalsingCollector = falsingCollector; mPowerManager = powerManager; mWakeUpCoordinator = coordinator; @@ -838,7 +984,6 @@ public final class NotificationPanelViewController extends PanelViewController { mUserManager = userManager; mMediaDataManager = mediaDataManager; mTapAgainViewController = tapAgainViewController; - mInteractionJankMonitor = interactionJankMonitor; mSysUiState = sysUiState; mPanelEventsEmitter = panelEventsEmitter; pulseExpansionHandler.setPulseExpandAbortListener(() -> { @@ -1040,9 +1185,14 @@ public final class NotificationPanelViewController extends PanelViewController { controller.setup(mNotificationContainerParent)); } - @Override - protected void loadDimens() { - super.loadDimens(); + @VisibleForTesting + void loadDimens() { + final ViewConfiguration configuration = ViewConfiguration.get(this.mView.getContext()); + mTouchSlop = configuration.getScaledTouchSlop(); + mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); + mHintDistance = mResources.getDimension(R.dimen.hint_move_distance); + mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount); + mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade); mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get() .setMaxLengthSeconds(0.4f).build(); mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext()); @@ -1149,6 +1299,8 @@ public final class NotificationPanelViewController extends PanelViewController { mSplitShadeFullTransitionDistance = mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance); + + mEnableQsClipping = mResources.getBoolean(R.bool.qs_enable_clipping); } private void onSplitShadeEnabledChanged() { @@ -1715,11 +1867,10 @@ public final class NotificationPanelViewController extends PanelViewController { // it's possible that nothing animated, so we replicate the termination // conditions of panelExpansionChanged here // TODO(b/200063118): This can likely go away in a future refactor CL. - getPanelExpansionStateManager().updateState(STATE_CLOSED); + getShadeExpansionStateManager().updateState(STATE_CLOSED); } } - @Override public void collapse(boolean delayed, float speedUpFactor) { if (!canPanelBeCollapsed()) { return; @@ -1729,7 +1880,20 @@ public final class NotificationPanelViewController extends PanelViewController { setQsExpandImmediate(true); setShowShelfOnly(true); } - super.collapse(delayed, speedUpFactor); + if (DEBUG) this.logf("collapse: " + this); + if (canPanelBeCollapsed()) { + cancelHeightAnimator(); + notifyExpandingStarted(); + + // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state. + setIsClosing(true); + if (delayed) { + mNextCollapseSpeedUpFactor = speedUpFactor; + this.mView.postDelayed(mFlingCollapseRunnable, 120); + } else { + fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */); + } + } } private void setQsExpandImmediate(boolean expandImmediate) { @@ -1749,10 +1913,15 @@ public final class NotificationPanelViewController extends PanelViewController { setQsExpansion(mQsMinExpansionHeight); } - @Override @VisibleForTesting - protected void cancelHeightAnimator() { - super.cancelHeightAnimator(); + void cancelHeightAnimator() { + if (mHeightAnimator != null) { + if (mHeightAnimator.isRunning()) { + mPanelUpdateWhenAnimatorEnds = false; + } + mHeightAnimator.cancel(); + } + endClosing(); } public void cancelAnimation() { @@ -1820,28 +1989,124 @@ public final class NotificationPanelViewController extends PanelViewController { } } - @Override public void fling(float vel, boolean expand) { GestureRecorder gr = mCentralSurfaces.getGestureRecorder(); if (gr != null) { gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); } - super.fling(vel, expand); + fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false); } - @Override - protected void flingToHeight(float vel, boolean expand, float target, + @VisibleForTesting + void flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { mHeadsUpTouchHelper.notifyFling(!expand); mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */); setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f); mNotificationStackScrollLayoutController.setPanelFlinging(true); - super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); + if (target == mExpandedHeight && mOverExpansion == 0.0f) { + // We're at the target and didn't fling and there's no overshoot + onFlingEnd(false /* cancelled */); + return; + } + mIsFlinging = true; + // we want to perform an overshoot animation when flinging open + final boolean addOverscroll = + expand + && !mInSplitShade // Split shade has its own overscroll logic + && mStatusBarStateController.getState() != KEYGUARD + && mOverExpansion == 0.0f + && vel >= 0; + final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand); + float overshootAmount = 0.0f; + if (addOverscroll) { + // Let's overshoot depending on the amount of velocity + overshootAmount = MathUtils.lerp( + 0.2f, + 1.0f, + MathUtils.saturate(vel + / (this.mFlingAnimationUtils.getHighVelocityPxPerSecond() + * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT))); + overshootAmount += mOverExpansion / mPanelFlingOvershootAmount; + } + ValueAnimator animator = createHeightAnimator(target, overshootAmount); + if (expand) { + if (expandBecauseOfFalsing && vel < 0) { + vel = 0; + } + this.mFlingAnimationUtils.apply(animator, mExpandedHeight, + target + overshootAmount * mPanelFlingOvershootAmount, vel, + this.mView.getHeight()); + if (vel == 0) { + animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION); + } + } else { + if (shouldUseDismissingAnimation()) { + if (vel == 0) { + animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); + long duration = (long) (200 + mExpandedHeight / this.mView.getHeight() * 100); + animator.setDuration(duration); + } else { + mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel, + this.mView.getHeight()); + } + } else { + mFlingAnimationUtilsClosing.apply( + animator, mExpandedHeight, target, vel, this.mView.getHeight()); + } + + // Make it shorter if we run a canned animation + if (vel == 0) { + animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor)); + } + if (mFixedDuration != NO_FIXED_DURATION) { + animator.setDuration(mFixedDuration); + } + } + animator.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + + @Override + public void onAnimationStart(Animator animation) { + if (!mStatusBarStateController.isDozing()) { + beginJankMonitoring(); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (shouldSpringBack && !mCancelled) { + // After the shade is flinged open to an overscrolled state, spring back + // the shade by reducing section padding to 0. + springBack(); + } else { + onFlingEnd(mCancelled); + } + } + }); + setAnimator(animator); + animator.start(); } - @Override - protected void onFlingEnd(boolean cancelled) { - super.onFlingEnd(cancelled); + @VisibleForTesting + void onFlingEnd(boolean cancelled) { + mIsFlinging = false; + // No overshoot when the animation ends + setOverExpansionInternal(0, false /* isFromGesture */); + setAnimator(null); + mKeyguardStateController.notifyPanelFlingEnd(); + if (!cancelled) { + endJankMonitoring(); + notifyExpandingFinished(); + } else { + cancelJankMonitoring(); + } + updatePanelExpansionAndVisibility(); mNotificationStackScrollLayoutController.setPanelFlinging(false); } @@ -1903,7 +2168,8 @@ public final class NotificationPanelViewController extends PanelViewController { mShadeLog.logMotionEvent(event, "onQsIntercept: move ignored because qs tracking disabled"); } - if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded)) + float touchSlop = getTouchSlop(event); + if ((h > touchSlop || (h < -touchSlop && mQsExpanded)) && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion"); @@ -1918,6 +2184,9 @@ public final class NotificationPanelViewController extends PanelViewController { mInitialTouchX = x; mNotificationStackScrollLayoutController.cancelLongPress(); return true; + } else { + mShadeLog.logQsTrackingNotStarted(mInitialTouchY, y, h, touchSlop, mQsExpanded, + mCollapsedOnDown, mKeyguardShowing, isQsExpansionEnabled()); } break; @@ -1936,8 +2205,7 @@ public final class NotificationPanelViewController extends PanelViewController { return mQsTracking; } - @Override - protected boolean isInContentBounds(float x, float y) { + private boolean isInContentBounds(float x, float y) { float stackScrollerX = mNotificationStackScrollLayoutController.getX(); return !mNotificationStackScrollLayoutController .isBelowLastNotification(x - stackScrollerX, y) @@ -2070,9 +2338,8 @@ public final class NotificationPanelViewController extends PanelViewController { - mQsMinExpansionHeight)); } - @Override - protected boolean shouldExpandWhenNotFlinging() { - if (super.shouldExpandWhenNotFlinging()) { + private boolean shouldExpandWhenNotFlinging() { + if (getExpandedFraction() > 0.5f) { return true; } if (mAllowExpandForSmallExpansion) { @@ -2084,8 +2351,7 @@ public final class NotificationPanelViewController extends PanelViewController { return false; } - @Override - protected float getOpeningHeight() { + private float getOpeningHeight() { return mNotificationStackScrollLayoutController.getOpeningHeight(); } @@ -2236,9 +2502,20 @@ public final class NotificationPanelViewController extends PanelViewController { } } - @Override - protected boolean flingExpands(float vel, float vectorVel, float x, float y) { - boolean expands = super.flingExpands(vel, vectorVel, x, y); + private boolean flingExpands(float vel, float vectorVel, float x, float y) { + boolean expands = true; + if (!this.mFalsingManager.isUnlockingDisabled()) { + @Classifier.InteractionType int interactionType = y - mInitialExpandY > 0 + ? QUICK_SETTINGS : ( + mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK); + if (!isFalseTouch(x, y, interactionType)) { + if (Math.abs(vectorVel) < this.mFlingAnimationUtils.getMinVelocityPxPerSecond()) { + expands = shouldExpandWhenNotFlinging(); + } else { + expands = vel > 0; + } + } + } // If we are already running a QS expansion, make sure that we keep the panel open. if (mQsExpansionAnimator != null) { @@ -2247,8 +2524,7 @@ public final class NotificationPanelViewController extends PanelViewController { return expands; } - @Override - protected boolean shouldGestureWaitForTouchSlop() { + private boolean shouldGestureWaitForTouchSlop() { if (mExpectingSynthesizedDown) { mExpectingSynthesizedDown = false; return false; @@ -2326,7 +2602,7 @@ public final class NotificationPanelViewController extends PanelViewController { } } - protected int getFalsingThreshold() { + private int getFalsingThreshold() { float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f; return (int) (mQsFalsingThreshold * factor); } @@ -2680,8 +2956,10 @@ public final class NotificationPanelViewController extends PanelViewController { mQsTranslationForFullShadeTransition = qsTranslation; updateQsFrameTranslation(); float currentTranslation = mQsFrame.getTranslationY(); - mQsClipTop = (int) (top - currentTranslation - mQsFrame.getTop()); - mQsClipBottom = (int) (bottom - currentTranslation - mQsFrame.getTop()); + mQsClipTop = mEnableQsClipping + ? (int) (top - currentTranslation - mQsFrame.getTop()) : 0; + mQsClipBottom = mEnableQsClipping + ? (int) (bottom - currentTranslation - mQsFrame.getTop()) : 0; mQsVisible = qsVisible; mQs.setQsVisible(mQsVisible); mQs.setFancyClipping( @@ -3066,8 +3344,8 @@ public final class NotificationPanelViewController extends PanelViewController { } } - @Override - protected boolean canCollapsePanelOnTouch() { + @VisibleForTesting + boolean canCollapsePanelOnTouch() { if (!isInSettings() && mBarState == KEYGUARD) { return true; } @@ -3079,7 +3357,6 @@ public final class NotificationPanelViewController extends PanelViewController { return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS); } - @Override public int getMaxPanelHeight() { int min = mStatusBarMinHeight; if (!(mBarState == KEYGUARD) @@ -3113,8 +3390,7 @@ public final class NotificationPanelViewController extends PanelViewController { return mIsExpanding; } - @Override - protected void onHeightUpdated(float expandedHeight) { + private void onHeightUpdated(float expandedHeight) { if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) { // Updating the clock position will set the top padding which might // trigger a new panel height and re-position the clock. @@ -3293,9 +3569,7 @@ public final class NotificationPanelViewController extends PanelViewController { mLockIconViewController.setAlpha(alpha); } - @Override - protected void onExpandingStarted() { - super.onExpandingStarted(); + private void onExpandingStarted() { mNotificationStackScrollLayoutController.onExpansionStarted(); mIsExpanding = true; mQsExpandedWhenExpandingStarted = mQsFullyExpanded; @@ -3311,8 +3585,7 @@ public final class NotificationPanelViewController extends PanelViewController { mQs.setHeaderListening(true); } - @Override - protected void onExpandingFinished() { + private void onExpandingFinished() { mScrimController.onExpandingFinished(); mNotificationStackScrollLayoutController.onExpansionStopped(); mHeadsUpManager.onExpandingFinished(); @@ -3364,18 +3637,58 @@ public final class NotificationPanelViewController extends PanelViewController { mQs.setListening(listening); } - @Override public void expand(boolean animate) { - super.expand(animate); + if (isFullyCollapsed() || isCollapsing()) { + mInstantExpanding = true; + mAnimateAfterExpanding = animate; + mUpdateFlingOnLayout = false; + abortAnimations(); + if (mTracking) { + // The panel is expanded after this call. + onTrackingStopped(true /* expands */); + } + if (mExpanding) { + notifyExpandingFinished(); + } + updatePanelExpansionAndVisibility(); + // Wait for window manager to pickup the change, so we know the maximum height of the + // panel then. + this.mView.getViewTreeObserver().addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (!mInstantExpanding) { + mView.getViewTreeObserver().removeOnGlobalLayoutListener( + this); + return; + } + if (mCentralSurfaces.getNotificationShadeWindowView() + .isVisibleToUser()) { + mView.getViewTreeObserver().removeOnGlobalLayoutListener( + this); + if (mAnimateAfterExpanding) { + notifyExpandingStarted(); + beginJankMonitoring(); + fling(0, true /* expand */); + } else { + setExpandedFraction(1f); + } + mInstantExpanding = false; + } + } + }); + // Make sure a layout really happens. + this.mView.requestLayout(); + } + setListening(true); } - @Override public void setOverExpansion(float overExpansion) { if (overExpansion == mOverExpansion) { return; } - super.setOverExpansion(overExpansion); + mOverExpansion = overExpansion; // Translating the quick settings by half the overexpansion to center it in the background // frame updateQsFrameTranslation(); @@ -3388,10 +3701,13 @@ public final class NotificationPanelViewController extends PanelViewController { } - @Override - protected void onTrackingStarted() { + private void onTrackingStarted() { mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen()); - super.onTrackingStarted(); + endClosing(); + mTracking = true; + mCentralSurfaces.onTrackingStarted(); + notifyExpandingStarted(); + updatePanelExpansionAndVisibility(); mScrimController.onTrackingStarted(); if (mQsFullyExpanded) { setQsExpandImmediate(true); @@ -3401,10 +3717,11 @@ public final class NotificationPanelViewController extends PanelViewController { cancelPendingPanelCollapse(); } - @Override - protected void onTrackingStopped(boolean expand) { + private void onTrackingStopped(boolean expand) { mFalsingCollector.onTrackingStopped(); - super.onTrackingStopped(expand); + mTracking = false; + mCentralSurfaces.onTrackingStopped(expand); + updatePanelExpansionAndVisibility(); if (expand) { mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */, true /* animate */); @@ -3421,37 +3738,48 @@ public final class NotificationPanelViewController extends PanelViewController { getHeight(), mNavigationBarBottomHeight); } - @Override - protected void startUnlockHintAnimation() { + @VisibleForTesting + void startUnlockHintAnimation() { if (mPowerManager.isPowerSaveMode() || mAmbientState.getDozeAmount() > 0f) { onUnlockHintStarted(); onUnlockHintFinished(); return; } - super.startUnlockHintAnimation(); + + // We don't need to hint the user if an animation is already running or the user is changing + // the expansion. + if (mHeightAnimator != null || mTracking) { + return; + } + notifyExpandingStarted(); + startUnlockHintAnimationPhase1(() -> { + notifyExpandingFinished(); + onUnlockHintFinished(); + mHintAnimationRunning = false; + }); + onUnlockHintStarted(); + mHintAnimationRunning = true; } - @Override - protected void onUnlockHintFinished() { - super.onUnlockHintFinished(); + @VisibleForTesting + void onUnlockHintFinished() { + mCentralSurfaces.onHintFinished(); mScrimController.setExpansionAffectsAlpha(true); mNotificationStackScrollLayoutController.setUnlockHintRunning(false); } - @Override - protected void onUnlockHintStarted() { - super.onUnlockHintStarted(); + @VisibleForTesting + void onUnlockHintStarted() { + mCentralSurfaces.onUnlockHintStarted(); mScrimController.setExpansionAffectsAlpha(false); mNotificationStackScrollLayoutController.setUnlockHintRunning(true); } - @Override - protected boolean shouldUseDismissingAnimation() { + private boolean shouldUseDismissingAnimation() { return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen() || !isTracking()); } - @Override public int getMaxPanelTransitionDistance() { // Traditionally the value is based on the number of notifications. On split-shade, we want // the required distance to be a specific and constant value, to make sure the expansion @@ -3476,8 +3804,8 @@ public final class NotificationPanelViewController extends PanelViewController { } } - @Override - protected boolean isTrackingBlocked() { + @VisibleForTesting + boolean isTrackingBlocked() { return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch; } @@ -3499,22 +3827,22 @@ public final class NotificationPanelViewController extends PanelViewController { return mIsLaunchTransitionFinished; } - @Override public void setIsLaunchAnimationRunning(boolean running) { boolean wasRunning = mIsLaunchAnimationRunning; - super.setIsLaunchAnimationRunning(running); + mIsLaunchAnimationRunning = running; if (wasRunning != mIsLaunchAnimationRunning) { mPanelEventsEmitter.notifyLaunchingActivityChanged(running); } } - @Override - protected void setIsClosing(boolean isClosing) { + @VisibleForTesting + void setIsClosing(boolean isClosing) { boolean wasClosing = isClosing(); - super.setIsClosing(isClosing); + mClosing = isClosing; if (wasClosing != isClosing) { mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing); } + mAmbientState.setIsClosing(isClosing); } private void updateDozingVisibilities(boolean animate) { @@ -3524,7 +3852,6 @@ public final class NotificationPanelViewController extends PanelViewController { } } - @Override public boolean isDozing() { return mDozing; } @@ -3541,22 +3868,21 @@ public final class NotificationPanelViewController extends PanelViewController { mKeyguardStatusViewController.dozeTimeTick(); } - @Override - protected boolean onMiddleClicked() { + private boolean onMiddleClicked() { switch (mBarState) { case KEYGUARD: if (!mDozingOnDown) { - if (mUpdateMonitor.isFaceEnrolled() - && !mUpdateMonitor.isFaceDetectionRunning() - && !mUpdateMonitor.getUserCanSkipBouncer( - KeyguardUpdateMonitor.getCurrentUser())) { - mUpdateMonitor.requestFaceAuth(true, - FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); - } else { - mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT, - 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); - mLockscreenGestureLogger - .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT); + mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false"); + // Try triggering face auth, this "might" run. Check + // KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run. + mUpdateMonitor.requestFaceAuth(true, + FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); + + mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT, + 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); + mLockscreenGestureLogger + .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT); + if (!mUpdateMonitor.isFaceDetectionRunning()) { startUnlockHintAnimation(); } if (mUpdateMonitor.isFaceEnrolled()) { @@ -3601,15 +3927,13 @@ public final class NotificationPanelViewController extends PanelViewController { updateVisibility(); } - @Override - protected boolean shouldPanelBeVisible() { + private boolean shouldPanelBeVisible() { boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode; return headsUpVisible || isExpanded() || mBouncerShowing; } - @Override public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { - super.setHeadsUpManager(headsUpManager); + mHeadsUpManager = headsUpManager; mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScrollLayoutController.getHeadsUpCallback(), NotificationPanelViewController.this); @@ -3623,8 +3947,7 @@ public final class NotificationPanelViewController extends PanelViewController { // otherwise we update the state when the expansion is finished } - @Override - protected void onClosingFinished() { + private void onClosingFinished() { mCentralSurfaces.onClosingFinished(); setClosingWithAlphaFadeout(false); mMediaHierarchyManager.closeGuts(); @@ -3713,8 +4036,7 @@ public final class NotificationPanelViewController extends PanelViewController { mCentralSurfaces.clearNotificationEffects(); } - @Override - protected boolean isPanelVisibleBecauseOfHeadsUp() { + private boolean isPanelVisibleBecauseOfHeadsUp() { return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway) && mBarState == StatusBarState.SHADE; } @@ -3829,9 +4151,15 @@ public final class NotificationPanelViewController extends PanelViewController { mNotificationBoundsAnimationDelay = delay; } - @Override public void setTouchAndAnimationDisabled(boolean disabled) { - super.setTouchAndAnimationDisabled(disabled); + mTouchDisabled = disabled; + if (mTouchDisabled) { + cancelHeightAnimator(); + if (mTracking) { + onTrackingStopped(true /* expanded */); + } + notifyExpandingFinished(); + } mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled); } @@ -4031,9 +4359,14 @@ public final class NotificationPanelViewController extends PanelViewController { mBlockingExpansionForCurrentTouch = mTracking; } - @Override public void dump(PrintWriter pw, String[] args) { - super.dump(pw, args); + pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" + + " tracking=%s timeAnim=%s%s " + + "touchDisabled=%s" + "]", + this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(), + mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator, + ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""), + mTouchDisabled ? "T" : "f")); IndentingPrintWriter ipw = asIndenting(pw); ipw.increaseIndent(); ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect()); @@ -4176,126 +4509,13 @@ public final class NotificationPanelViewController extends PanelViewController { mConfigurationListener.onThemeChanged(); } - @Override - protected OnLayoutChangeListener createLayoutChangeListener() { - return new OnLayoutChangeListenerImpl(); + private OnLayoutChangeListener createLayoutChangeListener() { + return new OnLayoutChangeListener(); } - @Override - protected TouchHandler createTouchHandler() { - return new TouchHandler() { - - private long mLastTouchDownTime = -1L; - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - if (SPEW_LOGCAT) { - Log.v(TAG, - "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX() - + "," + event.getY() + ")"); - } - if (mQs.disallowPanelTouches()) { - return false; - } - initDownStates(event); - // Do not let touches go to shade or QS if the bouncer is visible, - // but still let user swipe down to expand the panel, dismissing the bouncer. - if (mCentralSurfaces.isBouncerShowing()) { - return true; - } - if (mCommandQueue.panelsEnabled() - && !mNotificationStackScrollLayoutController.isLongPressInProgress() - && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { - mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); - mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); - return true; - } - if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0) - && mPulseExpansionHandler.onInterceptTouchEvent(event)) { - return true; - } - - if (!isFullyCollapsed() && onQsIntercept(event)) { - if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true"); - return true; - } - return super.onInterceptTouchEvent(event); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (event.getDownTime() == mLastTouchDownTime) { - // An issue can occur when swiping down after unlock, where multiple down - // events are received in this handler with identical downTimes. Until the - // source of the issue can be located, detect this case and ignore. - // see b/193350347 - Log.w(TAG, "Duplicate down event detected... ignoring"); - return true; - } - mLastTouchDownTime = event.getDownTime(); - } - - - if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) { - return false; - } - - // Do not allow panel expansion if bouncer is scrimmed or showing over a dream, - // otherwise user would be able to pull down QS or expand the shade. - if (mCentralSurfaces.isBouncerShowingScrimmed() - || mCentralSurfaces.isBouncerShowingOverDream()) { - return false; - } - - // Make sure the next touch won't the blocked after the current ends. - if (event.getAction() == MotionEvent.ACTION_UP - || event.getAction() == MotionEvent.ACTION_CANCEL) { - mBlockingExpansionForCurrentTouch = false; - } - // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately - // without any ACTION_MOVE event. - // In such case, simply expand the panel instead of being stuck at the bottom bar. - if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) { - expand(true /* animate */); - } - initDownStates(event); - - // If pulse is expanding already, let's give it the touch. There are situations - // where the panel starts expanding even though we're also pulsing - boolean pulseShouldGetTouch = (!mIsExpanding - && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)) - || mPulseExpansionHandler.isExpanding(); - if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) { - // We're expanding all the other ones shouldn't get this anymore - mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event"); - return true; - } - if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp() - && !mNotificationStackScrollLayoutController.isLongPressInProgress() - && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { - mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); - } - boolean handled = mHeadsUpTouchHelper.onTouchEvent(event); - - if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) { - mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event"); - return true; - } - if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { - mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); - handled = true; - } - - if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded() - && mKeyguardStateController.isShowing()) { - mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX()); - } - - handled |= super.onTouch(v, event); - return !mDozing || mPulsing || handled; - } - }; + @VisibleForTesting + TouchHandler createTouchHandler() { + return new TouchHandler(); } private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler = @@ -4347,8 +4567,7 @@ public final class NotificationPanelViewController extends PanelViewController { } }; - @Override - protected OnConfigurationChangedListener createOnConfigurationChangedListener() { + private OnConfigurationChangedListener createOnConfigurationChangedListener() { return new OnConfigurationChangedListener(); } @@ -4410,6 +4629,595 @@ public final class NotificationPanelViewController extends PanelViewController { .commitUpdate(mDisplayId); } + private void logf(String fmt, Object... args) { + Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); + } + + @VisibleForTesting + void notifyExpandingStarted() { + if (!mExpanding) { + mExpanding = true; + onExpandingStarted(); + } + } + + @VisibleForTesting + void notifyExpandingFinished() { + endClosing(); + if (mExpanding) { + mExpanding = false; + onExpandingFinished(); + } + } + + private float getTouchSlop(MotionEvent event) { + // Adjust the touch slop if another gesture may be being performed. + return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE + ? mTouchSlop * mSlopMultiplier + : mTouchSlop; + } + + private void addMovement(MotionEvent event) { + // Add movement to velocity tracker using raw screen X and Y coordinates instead + // of window coordinates because the window frame may be moving at the same time. + float deltaX = event.getRawX() - event.getX(); + float deltaY = event.getRawY() - event.getY(); + event.offsetLocation(deltaX, deltaY); + mVelocityTracker.addMovement(event); + event.offsetLocation(-deltaX, -deltaY); + } + + /** If the latency tracker is enabled, begins tracking expand latency. */ + public void startExpandLatencyTracking() { + if (mLatencyTracker.isEnabled()) { + mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL); + mExpandLatencyTracking = true; + } + } + + private void startOpening(MotionEvent event) { + updatePanelExpansionAndVisibility(); + // Reset at start so haptic can be triggered as soon as panel starts to open. + mHasVibratedOnOpen = false; + //TODO: keyguard opens QS a different way; log that too? + + // Log the position of the swipe that opened the panel + float width = mCentralSurfaces.getDisplayWidth(); + float height = mCentralSurfaces.getDisplayHeight(); + int rot = mCentralSurfaces.getRotation(); + + mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND, + (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot); + mLockscreenGestureLogger + .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND); + } + + /** + * Maybe vibrate as panel is opened. + * + * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead + * being opened programmatically (such as by the open panel gesture), we always play haptic. + */ + private void maybeVibrateOnOpening(boolean openingWithTouch) { + if (mVibrateOnOpening) { + if (!openingWithTouch || !mHasVibratedOnOpen) { + mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); + mHasVibratedOnOpen = true; + } + } + } + + /** + * @return whether the swiping direction is upwards and above a 45 degree angle compared to the + * horizontal direction + */ + private boolean isDirectionUpwards(float x, float y) { + float xDiff = x - mInitialExpandX; + float yDiff = y - mInitialExpandY; + if (yDiff >= 0) { + return false; + } + return Math.abs(yDiff) >= Math.abs(xDiff); + } + + /** Called when a MotionEvent is about to trigger Shade expansion. */ + public void startExpandMotion(float newX, float newY, boolean startTracking, + float expandedHeight) { + if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) { + beginJankMonitoring(); + } + mInitialOffsetOnTouch = expandedHeight; + mInitialExpandY = newY; + mInitialExpandX = newX; + mInitialTouchFromKeyguard = mKeyguardStateController.isShowing(); + if (startTracking) { + mTouchSlopExceeded = true; + setExpandedHeight(mInitialOffsetOnTouch); + onTrackingStarted(); + } + } + + private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { + mTrackingPointer = -1; + mAmbientState.setSwipingUp(false); + if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop + || Math.abs(y - mInitialExpandY) > mTouchSlop + || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { + mVelocityTracker.computeCurrentVelocity(1000); + float vel = mVelocityTracker.getYVelocity(); + float vectorVel = (float) Math.hypot( + mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + + final boolean onKeyguard = mKeyguardStateController.isShowing(); + final boolean expand; + if (mKeyguardStateController.isKeyguardFadingAway() + || (mInitialTouchFromKeyguard && !onKeyguard)) { + // Don't expand for any touches that started from the keyguard and ended after the + // keyguard is gone. + expand = false; + } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { + if (onKeyguard) { + expand = true; + } else if (mCentralSurfaces.isBouncerShowingOverDream()) { + expand = false; + } else { + // If we get a cancel, put the shade back to the state it was in when the + // gesture started + expand = !mPanelClosedOnDown; + } + } else { + expand = flingExpands(vel, vectorVel, x, y); + } + + mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold, + mCentralSurfaces.isFalsingThresholdNeeded(), + mCentralSurfaces.isWakeUpComingFromTouch()); + // Log collapse gesture if on lock screen. + if (!expand && onKeyguard) { + float displayDensity = mCentralSurfaces.getDisplayDensity(); + int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity); + int velocityDp = (int) Math.abs(vel / displayDensity); + mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp); + mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK); + } + @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC + : y - mInitialExpandY > 0 ? QUICK_SETTINGS + : (mKeyguardStateController.canDismissLockScreen() + ? UNLOCK : BOUNCER_UNLOCK); + + fling(vel, expand, isFalseTouch(x, y, interactionType)); + onTrackingStopped(expand); + mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; + if (mUpdateFlingOnLayout) { + mUpdateFlingVelocity = vel; + } + } else if (!mCentralSurfaces.isBouncerShowing() + && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating() + && !mKeyguardStateController.isKeyguardGoingAway()) { + boolean expands = onEmptySpaceClick(); + onTrackingStopped(expands); + } + mVelocityTracker.clear(); + } + + private float getCurrentExpandVelocity() { + mVelocityTracker.computeCurrentVelocity(1000); + return mVelocityTracker.getYVelocity(); + } + + private void endClosing() { + if (mClosing) { + setIsClosing(false); + onClosingFinished(); + } + } + + /** + * @param x the final x-coordinate when the finger was lifted + * @param y the final y-coordinate when the finger was lifted + * @return whether this motion should be regarded as a false touch + */ + private boolean isFalseTouch(float x, float y, + @Classifier.InteractionType int interactionType) { + if (!mCentralSurfaces.isFalsingThresholdNeeded()) { + return false; + } + if (mFalsingManager.isClassifierEnabled()) { + return mFalsingManager.isFalseTouch(interactionType); + } + if (!mTouchAboveFalsingThreshold) { + return true; + } + if (mUpwardsWhenThresholdReached) { + return false; + } + return !isDirectionUpwards(x, y); + } + + private void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) { + fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing); + } + + private void fling(float vel, boolean expand, float collapseSpeedUpFactor, + boolean expandBecauseOfFalsing) { + float target = expand ? getMaxPanelHeight() : 0; + if (!expand) { + setIsClosing(true); + } + flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); + } + + private void springBack() { + if (mOverExpansion == 0) { + onFlingEnd(false /* cancelled */); + return; + } + mIsSpringBackAnimation = true; + ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0); + animator.addUpdateListener( + animation -> setOverExpansionInternal((float) animation.getAnimatedValue(), + false /* isFromGesture */)); + animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + animator.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + @Override + public void onAnimationEnd(Animator animation) { + mIsSpringBackAnimation = false; + onFlingEnd(mCancelled); + } + }); + setAnimator(animator); + animator.start(); + } + + public String getName() { + return mViewName; + } + + @VisibleForTesting + void setExpandedHeight(float height) { + if (DEBUG) logf("setExpandedHeight(%.1f)", height); + setExpandedHeightInternal(height); + } + + private void updateExpandedHeightToMaxHeight() { + float currentMaxPanelHeight = getMaxPanelHeight(); + + if (isFullyCollapsed()) { + return; + } + + if (currentMaxPanelHeight == mExpandedHeight) { + return; + } + + if (mTracking && !isTrackingBlocked()) { + return; + } + + if (mHeightAnimator != null && !mIsSpringBackAnimation) { + mPanelUpdateWhenAnimatorEnds = true; + return; + } + + setExpandedHeight(currentMaxPanelHeight); + } + + private void setExpandedHeightInternal(float h) { + if (isNaN(h)) { + Log.wtf(TAG, "ExpandedHeight set to NaN"); + } + mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> { + if (mExpandLatencyTracking && h != 0f) { + DejankUtils.postAfterTraversal( + () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); + mExpandLatencyTracking = false; + } + float maxPanelHeight = getMaxPanelTransitionDistance(); + if (mHeightAnimator == null) { + // Split shade has its own overscroll logic + if (mTracking && !mInSplitShade) { + float overExpansionPixels = Math.max(0, h - maxPanelHeight); + setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); + } + } + mExpandedHeight = Math.min(h, maxPanelHeight); + // If we are closing the panel and we are almost there due to a slow decelerating + // interpolator, abort the animation. + if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) { + mExpandedHeight = 0f; + if (mHeightAnimator != null) { + mHeightAnimator.end(); + } + } + mExpansionDragDownAmountPx = h; + mExpandedFraction = Math.min(1f, + maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); + mAmbientState.setExpansionFraction(mExpandedFraction); + onHeightUpdated(mExpandedHeight); + updatePanelExpansionAndVisibility(); + }); + } + + /** + * Set the current overexpansion + * + * @param overExpansion the amount of overexpansion to apply + * @param isFromGesture is this amount from a gesture and needs to be rubberBanded? + */ + private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) { + if (!isFromGesture) { + mLastGesturedOverExpansion = -1; + setOverExpansion(overExpansion); + } else if (mLastGesturedOverExpansion != overExpansion) { + mLastGesturedOverExpansion = overExpansion; + final float heightForFullOvershoot = mView.getHeight() / 3.0f; + float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot); + newExpansion = Interpolators.getOvershootInterpolation(newExpansion); + setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f); + } + } + + /** Sets the expanded height relative to a number from 0 to 1. */ + public void setExpandedFraction(float frac) { + setExpandedHeight(getMaxPanelTransitionDistance() * frac); + } + + @VisibleForTesting + float getExpandedHeight() { + return mExpandedHeight; + } + + public float getExpandedFraction() { + return mExpandedFraction; + } + + public boolean isFullyExpanded() { + return mExpandedHeight >= getMaxPanelHeight(); + } + + public boolean isFullyCollapsed() { + return mExpandedFraction <= 0.0f; + } + + public boolean isCollapsing() { + return mClosing || mIsLaunchAnimationRunning; + } + + public boolean isFlinging() { + return mIsFlinging; + } + + public boolean isTracking() { + return mTracking; + } + + /** Returns whether the shade can be collapsed. */ + public boolean canPanelBeCollapsed() { + return !isFullyCollapsed() && !mTracking && !mClosing; + } + + /** Collapses the shade instantly without animation. */ + public void instantCollapse() { + abortAnimations(); + setExpandedFraction(0f); + if (mExpanding) { + notifyExpandingFinished(); + } + if (mInstantExpanding) { + mInstantExpanding = false; + updatePanelExpansionAndVisibility(); + } + } + + private void abortAnimations() { + cancelHeightAnimator(); + mView.removeCallbacks(mFlingCollapseRunnable); + } + + public boolean isUnlockHintRunning() { + return mHintAnimationRunning; + } + + /** + * Phase 1: Move everything upwards. + */ + private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) { + float target = Math.max(0, getMaxPanelHeight() - mHintDistance); + ValueAnimator animator = createHeightAnimator(target); + animator.setDuration(250); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + animator.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCancelled) { + setAnimator(null); + onAnimationFinished.run(); + } else { + startUnlockHintAnimationPhase2(onAnimationFinished); + } + } + }); + animator.start(); + setAnimator(animator); + + final List<ViewPropertyAnimator> indicationAnimators = + mKeyguardBottomArea.getIndicationAreaAnimators(); + for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) { + indicationAreaAnimator + .translationY(-mHintDistance) + .setDuration(250) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .withEndAction(() -> indicationAreaAnimator + .translationY(0) + .setDuration(450) + .setInterpolator(mBounceInterpolator) + .start()) + .start(); + } + } + + private void setAnimator(ValueAnimator animator) { + mHeightAnimator = animator; + if (animator == null && mPanelUpdateWhenAnimatorEnds) { + mPanelUpdateWhenAnimatorEnds = false; + updateExpandedHeightToMaxHeight(); + } + } + + /** + * Phase 2: Bounce down. + */ + private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) { + ValueAnimator animator = createHeightAnimator(getMaxPanelHeight()); + animator.setDuration(450); + animator.setInterpolator(mBounceInterpolator); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setAnimator(null); + onAnimationFinished.run(); + updatePanelExpansionAndVisibility(); + } + }); + animator.start(); + setAnimator(animator); + } + + private ValueAnimator createHeightAnimator(float targetHeight) { + return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */); + } + + /** + * Create an animator that can also overshoot + * + * @param targetHeight the target height + * @param overshootAmount the amount of overshoot desired + */ + private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) { + float startExpansion = mOverExpansion; + ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); + animator.addUpdateListener( + animation -> { + if (overshootAmount > 0.0f + // Also remove the overExpansion when collapsing + || (targetHeight == 0.0f && startExpansion != 0)) { + final float expansion = MathUtils.lerp( + startExpansion, + mPanelFlingOvershootAmount * overshootAmount, + Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + animator.getAnimatedFraction())); + setOverExpansionInternal(expansion, false /* isFromGesture */); + } + setExpandedHeightInternal((float) animation.getAnimatedValue()); + }); + return animator; + } + + /** Update the visibility of {@link NotificationPanelView} if necessary. */ + private void updateVisibility() { + mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE); + } + + /** + * Updates the panel expansion and {@link NotificationPanelView} visibility if necessary. + * + * TODO(b/200063118): Could public calls to this method be replaced with calls to + * {@link #updateVisibility()}? That would allow us to make this method private. + */ + public void updatePanelExpansionAndVisibility() { + mShadeExpansionStateManager.onPanelExpansionChanged( + mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); + updateVisibility(); + } + + public boolean isExpanded() { + return mExpandedFraction > 0f + || mInstantExpanding + || isPanelVisibleBecauseOfHeadsUp() + || mTracking + || mHeightAnimator != null + && !mIsSpringBackAnimation; + } + + /** + * Gets called when the user performs a click anywhere in the empty area of the panel. + * + * @return whether the panel will be expanded after the action performed by this method + */ + private boolean onEmptySpaceClick() { + if (mHintAnimationRunning) { + return true; + } + return onMiddleClicked(); + } + + @VisibleForTesting + boolean isClosing() { + return mClosing; + } + + /** Collapses the shade with an animation duration in milliseconds. */ + public void collapseWithDuration(int animationDuration) { + mFixedDuration = animationDuration; + collapse(false /* delayed */, 1.0f /* speedUpFactor */); + mFixedDuration = NO_FIXED_DURATION; + } + + /** Returns the NotificationPanelView. */ + public ViewGroup getView() { + // TODO: remove this method, or at least reduce references to it. + return mView; + } + + private void beginJankMonitoring() { + if (mInteractionJankMonitor == null) { + return; + } + InteractionJankMonitor.Configuration.Builder builder = + InteractionJankMonitor.Configuration.Builder.withView( + InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, + mView) + .setTag(isFullyCollapsed() ? "Expand" : "Collapse"); + mInteractionJankMonitor.begin(builder); + } + + private void endJankMonitoring() { + if (mInteractionJankMonitor == null) { + return; + } + InteractionJankMonitor.getInstance().end( + InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + } + + private void cancelJankMonitoring() { + if (mInteractionJankMonitor == null) { + return; + } + InteractionJankMonitor.getInstance().cancel( + InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + } + + private float getExpansionFraction() { + return mExpandedFraction; + } + + private ShadeExpansionStateManager getShadeExpansionStateManager() { + return mShadeExpansionStateManager; + } + private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener { @Override public void onHeightChanged(ExpandableView view, boolean needsAnimation) { @@ -4818,13 +5626,18 @@ public final class NotificationPanelViewController extends PanelViewController { } } - private class OnLayoutChangeListenerImpl extends OnLayoutChangeListener { - + private final class OnLayoutChangeListener implements View.OnLayoutChangeListener { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { DejankUtils.startDetectingBlockingIpcs("NVP#onLayout"); - super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom); + updateExpandedHeightToMaxHeight(); + mHasLayoutedSinceDown = true; + if (mUpdateFlingOnLayout) { + abortAnimations(); + fling(mUpdateFlingVelocity, true /* expands */); + mUpdateFlingOnLayout = false; + } updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount()); setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth()); @@ -5082,4 +5895,365 @@ public final class NotificationPanelViewController extends PanelViewController { } } } + + /** Handles MotionEvents for the Shade. */ + public final class TouchHandler implements View.OnTouchListener { + private long mLastTouchDownTime = -1L; + + /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */ + public boolean onInterceptTouchEvent(MotionEvent event) { + if (SPEW_LOGCAT) { + Log.v(TAG, + "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX() + + "," + event.getY() + ")"); + } + if (mQs.disallowPanelTouches()) { + return false; + } + initDownStates(event); + // Do not let touches go to shade or QS if the bouncer is visible, + // but still let user swipe down to expand the panel, dismissing the bouncer. + if (mCentralSurfaces.isBouncerShowing()) { + return true; + } + if (mCommandQueue.panelsEnabled() + && !mNotificationStackScrollLayoutController.isLongPressInProgress() + && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { + mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); + mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); + return true; + } + if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0) + && mPulseExpansionHandler.onInterceptTouchEvent(event)) { + return true; + } + + if (!isFullyCollapsed() && onQsIntercept(event)) { + if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true"); + return true; + } + if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted + && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { + return false; + } + + /* If the user drags anywhere inside the panel we intercept it if the movement is + upwards. This allows closing the shade from anywhere inside the panel. + We only do this if the current content is scrolled to the bottom, i.e. + canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling + gesture possible. */ + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float x = event.getX(pointerIndex); + final float y = event.getY(pointerIndex); + boolean canCollapsePanel = canCollapsePanelOnTouch(); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mCentralSurfaces.userActivity(); + mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation; + mMinExpandHeight = 0.0f; + mDownTime = mSystemClock.uptimeMillis(); + if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) { + cancelHeightAnimator(); + mTouchSlopExceeded = true; + return true; + } + mInitialExpandY = y; + mInitialExpandX = x; + mTouchStartedInEmptyArea = !isInContentBounds(x, y); + mTouchSlopExceeded = mTouchSlopExceededBeforeDown; + mMotionAborted = false; + mPanelClosedOnDown = isFullyCollapsed(); + mCollapsedAndHeadsUpOnDown = false; + mHasLayoutedSinceDown = false; + mUpdateFlingOnLayout = false; + mTouchAboveFalsingThreshold = false; + addMovement(event); + break; + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + mTrackingPointer = event.getPointerId(newIndex); + mInitialExpandX = event.getX(newIndex); + mInitialExpandY = event.getY(newIndex); + } + break; + case MotionEvent.ACTION_POINTER_DOWN: + if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { + mMotionAborted = true; + mVelocityTracker.clear(); + } + break; + case MotionEvent.ACTION_MOVE: + final float h = y - mInitialExpandY; + addMovement(event); + final boolean openShadeWithoutHun = + mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown; + if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown + || openShadeWithoutHun) { + float hAbs = Math.abs(h); + float touchSlop = getTouchSlop(event); + if ((h < -touchSlop + || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop)) + && hAbs > Math.abs(x - mInitialExpandX)) { + cancelHeightAnimator(); + startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); + return true; + } + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mVelocityTracker.clear(); + break; + } + return false; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (event.getDownTime() == mLastTouchDownTime) { + // An issue can occur when swiping down after unlock, where multiple down + // events are received in this handler with identical downTimes. Until the + // source of the issue can be located, detect this case and ignore. + // see b/193350347 + Log.w(TAG, "Duplicate down event detected... ignoring"); + return true; + } + mLastTouchDownTime = event.getDownTime(); + } + + + if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) { + return false; + } + + // Do not allow panel expansion if bouncer is scrimmed or showing over a dream, + // otherwise user would be able to pull down QS or expand the shade. + if (mCentralSurfaces.isBouncerShowingScrimmed() + || mCentralSurfaces.isBouncerShowingOverDream()) { + return false; + } + + // Make sure the next touch won't the blocked after the current ends. + if (event.getAction() == MotionEvent.ACTION_UP + || event.getAction() == MotionEvent.ACTION_CANCEL) { + mBlockingExpansionForCurrentTouch = false; + } + // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately + // without any ACTION_MOVE event. + // In such case, simply expand the panel instead of being stuck at the bottom bar. + if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) { + expand(true /* animate */); + } + initDownStates(event); + + // If pulse is expanding already, let's give it the touch. There are situations + // where the panel starts expanding even though we're also pulsing + boolean pulseShouldGetTouch = (!mIsExpanding + && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)) + || mPulseExpansionHandler.isExpanding(); + if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) { + // We're expanding all the other ones shouldn't get this anymore + mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event"); + return true; + } + if (mPulsing) { + mShadeLog.logMotionEvent(event, "onTouch: eat touch, device pulsing"); + return true; + } + if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp() + && !mNotificationStackScrollLayoutController.isLongPressInProgress() + && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { + mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); + } + boolean handled = mHeadsUpTouchHelper.onTouchEvent(event); + + if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) { + mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event"); + return true; + } + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { + mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); + handled = true; + } + + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded() + && mKeyguardStateController.isShowing()) { + mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX()); + } + + handled |= handleTouch(event); + return !mDozing || handled; + } + + private boolean handleTouch(MotionEvent event) { + if (mInstantExpanding) { + mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding"); + return false; + } + if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) { + mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled"); + return false; + } + if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) { + mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted"); + return false; + } + + // If dragging should not expand the notifications shade, then return false. + if (!mNotificationsDragEnabled) { + if (mTracking) { + // Turn off tracking if it's on or the shade can get stuck in the down position. + onTrackingStopped(true /* expand */); + } + mShadeLog.logMotionEvent(event, "onTouch: drag not enabled"); + return false; + } + + // On expanding, single mouse click expands the panel instead of dragging. + if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) { + if (event.getAction() == MotionEvent.ACTION_UP) { + expand(true); + } + return true; + } + + /* + * We capture touch events here and update the expand height here in case according to + * the users fingers. This also handles multi-touch. + * + * Flinging is also enabled in order to open or close the shade. + */ + + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float x = event.getX(pointerIndex); + final float y = event.getY(pointerIndex); + + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop(); + mIgnoreXTouchSlop = true; + } + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); + mMinExpandHeight = 0.0f; + mPanelClosedOnDown = isFullyCollapsed(); + mHasLayoutedSinceDown = false; + mUpdateFlingOnLayout = false; + mMotionAborted = false; + mDownTime = mSystemClock.uptimeMillis(); + mTouchAboveFalsingThreshold = false; + mCollapsedAndHeadsUpOnDown = + isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); + addMovement(event); + boolean regularHeightAnimationRunning = mHeightAnimator != null + && !mHintAnimationRunning && !mIsSpringBackAnimation; + if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) { + mTouchSlopExceeded = regularHeightAnimationRunning + || mTouchSlopExceededBeforeDown; + cancelHeightAnimator(); + onTrackingStarted(); + } + if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp() + && !mCentralSurfaces.isBouncerShowing()) { + startOpening(event); + } + break; + + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + final float newY = event.getY(newIndex); + final float newX = event.getX(newIndex); + mTrackingPointer = event.getPointerId(newIndex); + mHandlingPointerUp = true; + startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight); + mHandlingPointerUp = false; + } + break; + case MotionEvent.ACTION_POINTER_DOWN: + if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { + mMotionAborted = true; + endMotionEvent(event, x, y, true /* forceCancel */); + return false; + } + break; + case MotionEvent.ACTION_MOVE: + addMovement(event); + if (!isFullyCollapsed()) { + maybeVibrateOnOpening(true /* openingWithTouch */); + } + float h = y - mInitialExpandY; + + // If the panel was collapsed when touching, we only need to check for the + // y-component of the gesture, as we have no conflicting horizontal gesture. + if (Math.abs(h) > getTouchSlop(event) + && (Math.abs(h) > Math.abs(x - mInitialExpandX) + || mIgnoreXTouchSlop)) { + mTouchSlopExceeded = true; + if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) { + if (mInitialOffsetOnTouch != 0f) { + startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); + h = 0; + } + cancelHeightAnimator(); + onTrackingStarted(); + } + } + float newHeight = Math.max(0, h + mInitialOffsetOnTouch); + newHeight = Math.max(newHeight, mMinExpandHeight); + if (-h >= getFalsingThreshold()) { + mTouchAboveFalsingThreshold = true; + mUpwardsWhenThresholdReached = isDirectionUpwards(x, y); + } + if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) { + // Count h==0 as part of swipe-up, + // otherwise {@link NotificationStackScrollLayout} + // wrongly enables stack height updates at the start of lockscreen swipe-up + mAmbientState.setSwipingUp(h <= 0); + setExpandedHeightInternal(newHeight); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + addMovement(event); + endMotionEvent(event, x, y, false /* forceCancel */); + // mHeightAnimator is null, there is no remaining frame, ends instrumenting. + if (mHeightAnimator == null) { + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + endJankMonitoring(); + } else { + cancelJankMonitoring(); + } + } + break; + } + return !mGestureWaitForTouchSlop || mTracking; + } + } + + /** Listens for config changes. */ + public class OnConfigurationChangedListener implements + NotificationPanelView.OnConfigurationChangedListener { + @Override + public void onConfigurationChanged(Configuration newConfig) { + loadDimens(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS index 7dc9dc7efeb7..49709a8c7674 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS @@ -1,3 +1,6 @@ +justinweir@google.com +syeonlee@google.com + per-file *Notification* = set noparent per-file *Notification* = file:../statusbar/notification/OWNERS diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java deleted file mode 100644 index fa51d854c9aa..000000000000 --- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java +++ /dev/null @@ -1,1493 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shade; - -import static android.view.View.INVISIBLE; -import static android.view.View.VISIBLE; - -import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; -import static com.android.systemui.classifier.Classifier.GENERIC; -import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; -import static com.android.systemui.classifier.Classifier.UNLOCK; -import static com.android.systemui.shade.NotificationPanelView.DEBUG; - -import static java.lang.Float.isNaN; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.VibrationEffect; -import android.util.Log; -import android.util.MathUtils; -import android.view.InputDevice; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; -import android.view.ViewTreeObserver; -import android.view.animation.Interpolator; - -import com.android.internal.jank.InteractionJankMonitor; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.util.LatencyTracker; -import com.android.systemui.DejankUtils; -import com.android.systemui.R; -import com.android.systemui.animation.Interpolators; -import com.android.systemui.classifier.Classifier; -import com.android.systemui.doze.DozeLog; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.notification.stack.AmbientState; -import com.android.systemui.statusbar.phone.BounceInterpolator; -import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; -import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; -import com.android.systemui.statusbar.phone.LockscreenGestureLogger; -import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.time.SystemClock; -import com.android.wm.shell.animation.FlingAnimationUtils; - -import java.io.PrintWriter; -import java.util.List; - -public abstract class PanelViewController { - public static final String TAG = NotificationPanelView.class.getSimpleName(); - public static final float FLING_MAX_LENGTH_SECONDS = 0.6f; - public static final float FLING_SPEED_UP_FACTOR = 0.6f; - public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f; - public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f; - private static final int NO_FIXED_DURATION = -1; - private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L; - private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L; - - /** - * The factor of the usual high velocity that is needed in order to reach the maximum overshoot - * when flinging. A low value will make it that most flings will reach the maximum overshoot. - */ - private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f; - - protected long mDownTime; - protected boolean mTouchSlopExceededBeforeDown; - private float mMinExpandHeight; - private boolean mPanelUpdateWhenAnimatorEnds; - private final boolean mVibrateOnOpening; - private boolean mHasVibratedOnOpen = false; - protected boolean mIsLaunchAnimationRunning; - private int mFixedDuration = NO_FIXED_DURATION; - protected float mOverExpansion; - - /** - * The overshoot amount when the panel flings open - */ - private float mPanelFlingOvershootAmount; - - /** - * The amount of pixels that we have overexpanded the last time with a gesture - */ - private float mLastGesturedOverExpansion = -1; - - /** - * Is the current animator the spring back animation? - */ - private boolean mIsSpringBackAnimation; - - private boolean mInSplitShade; - - private void logf(String fmt, Object... args) { - Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); - } - - protected CentralSurfaces mCentralSurfaces; - protected HeadsUpManagerPhone mHeadsUpManager; - protected final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; - - private float mHintDistance; - private float mInitialOffsetOnTouch; - private boolean mCollapsedAndHeadsUpOnDown; - private float mExpandedFraction = 0; - private float mExpansionDragDownAmountPx = 0; - protected float mExpandedHeight = 0; - private boolean mPanelClosedOnDown; - private boolean mHasLayoutedSinceDown; - private float mUpdateFlingVelocity; - private boolean mUpdateFlingOnLayout; - private boolean mClosing; - protected boolean mTracking; - private boolean mTouchSlopExceeded; - private int mTrackingPointer; - private int mTouchSlop; - private float mSlopMultiplier; - protected boolean mHintAnimationRunning; - private boolean mTouchAboveFalsingThreshold; - private boolean mTouchStartedInEmptyArea; - private boolean mMotionAborted; - private boolean mUpwardsWhenThresholdReached; - private boolean mAnimatingOnDown; - private boolean mHandlingPointerUp; - - private ValueAnimator mHeightAnimator; - private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); - private final FlingAnimationUtils mFlingAnimationUtils; - private final FlingAnimationUtils mFlingAnimationUtilsClosing; - private final FlingAnimationUtils mFlingAnimationUtilsDismissing; - private final LatencyTracker mLatencyTracker; - private final FalsingManager mFalsingManager; - private final DozeLog mDozeLog; - private final VibratorHelper mVibratorHelper; - - /** - * Whether an instant expand request is currently pending and we are just waiting for layout. - */ - private boolean mInstantExpanding; - private boolean mAnimateAfterExpanding; - private boolean mIsFlinging; - - private String mViewName; - private float mInitialExpandY; - private float mInitialExpandX; - private boolean mTouchDisabled; - private boolean mInitialTouchFromKeyguard; - - /** - * Whether or not the NotificationPanelView can be expanded or collapsed with a drag. - */ - private final boolean mNotificationsDragEnabled; - - private final Interpolator mBounceInterpolator; - protected KeyguardBottomAreaView mKeyguardBottomArea; - - /** - * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time. - */ - private float mNextCollapseSpeedUpFactor = 1.0f; - - protected boolean mExpanding; - private boolean mGestureWaitForTouchSlop; - private boolean mIgnoreXTouchSlop; - private boolean mExpandLatencyTracking; - private final NotificationPanelView mView; - private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private final NotificationShadeWindowController mNotificationShadeWindowController; - protected final Resources mResources; - protected final KeyguardStateController mKeyguardStateController; - protected final SysuiStatusBarStateController mStatusBarStateController; - protected final AmbientState mAmbientState; - protected final LockscreenGestureLogger mLockscreenGestureLogger; - private final ShadeExpansionStateManager mShadeExpansionStateManager; - private final InteractionJankMonitor mInteractionJankMonitor; - protected final SystemClock mSystemClock; - - protected final ShadeLogger mShadeLog; - - protected abstract void onExpandingFinished(); - - protected void onExpandingStarted() { - } - - protected void notifyExpandingStarted() { - if (!mExpanding) { - mExpanding = true; - onExpandingStarted(); - } - } - - protected final void notifyExpandingFinished() { - endClosing(); - if (mExpanding) { - mExpanding = false; - onExpandingFinished(); - } - } - - protected AmbientState getAmbientState() { - return mAmbientState; - } - - public PanelViewController( - NotificationPanelView view, - FalsingManager falsingManager, - DozeLog dozeLog, - KeyguardStateController keyguardStateController, - SysuiStatusBarStateController statusBarStateController, - NotificationShadeWindowController notificationShadeWindowController, - VibratorHelper vibratorHelper, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, - LatencyTracker latencyTracker, - FlingAnimationUtils.Builder flingAnimationUtilsBuilder, - StatusBarTouchableRegionManager statusBarTouchableRegionManager, - LockscreenGestureLogger lockscreenGestureLogger, - ShadeExpansionStateManager shadeExpansionStateManager, - AmbientState ambientState, - InteractionJankMonitor interactionJankMonitor, - ShadeLogger shadeLogger, - SystemClock systemClock) { - keyguardStateController.addCallback(new KeyguardStateController.Callback() { - @Override - public void onKeyguardFadingAwayChanged() { - updateExpandedHeightToMaxHeight(); - } - }); - mAmbientState = ambientState; - mView = view; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; - mLockscreenGestureLogger = lockscreenGestureLogger; - mShadeExpansionStateManager = shadeExpansionStateManager; - mShadeLog = shadeLogger; - TouchHandler touchHandler = createTouchHandler(); - mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - mViewName = mResources.getResourceName(mView.getId()); - } - - @Override - public void onViewDetachedFromWindow(View v) { - } - }); - - mView.addOnLayoutChangeListener(createLayoutChangeListener()); - mView.setOnTouchListener(touchHandler); - mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener()); - - mResources = mView.getResources(); - mKeyguardStateController = keyguardStateController; - mStatusBarStateController = statusBarStateController; - mNotificationShadeWindowController = notificationShadeWindowController; - mFlingAnimationUtils = flingAnimationUtilsBuilder - .reset() - .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS) - .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) - .build(); - mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder - .reset() - .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS) - .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR) - .build(); - mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder - .reset() - .setMaxLengthSeconds(0.5f) - .setSpeedUpFactor(0.6f) - .setX2(0.6f) - .setY2(0.84f) - .build(); - mLatencyTracker = latencyTracker; - mBounceInterpolator = new BounceInterpolator(); - mFalsingManager = falsingManager; - mDozeLog = dozeLog; - mNotificationsDragEnabled = mResources.getBoolean( - R.bool.config_enableNotificationShadeDrag); - mVibratorHelper = vibratorHelper; - mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation); - mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; - mInteractionJankMonitor = interactionJankMonitor; - mSystemClock = systemClock; - } - - protected void loadDimens() { - final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext()); - mTouchSlop = configuration.getScaledTouchSlop(); - mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); - mHintDistance = mResources.getDimension(R.dimen.hint_move_distance); - mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount); - mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade); - } - - protected float getTouchSlop(MotionEvent event) { - // Adjust the touch slop if another gesture may be being performed. - return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE - ? mTouchSlop * mSlopMultiplier - : mTouchSlop; - } - - private void addMovement(MotionEvent event) { - // Add movement to velocity tracker using raw screen X and Y coordinates instead - // of window coordinates because the window frame may be moving at the same time. - float deltaX = event.getRawX() - event.getX(); - float deltaY = event.getRawY() - event.getY(); - event.offsetLocation(deltaX, deltaY); - mVelocityTracker.addMovement(event); - event.offsetLocation(-deltaX, -deltaY); - } - - public void setTouchAndAnimationDisabled(boolean disabled) { - mTouchDisabled = disabled; - if (mTouchDisabled) { - cancelHeightAnimator(); - if (mTracking) { - onTrackingStopped(true /* expanded */); - } - notifyExpandingFinished(); - } - } - - public void startExpandLatencyTracking() { - if (mLatencyTracker.isEnabled()) { - mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL); - mExpandLatencyTracking = true; - } - } - - private void startOpening(MotionEvent event) { - updatePanelExpansionAndVisibility(); - // Reset at start so haptic can be triggered as soon as panel starts to open. - mHasVibratedOnOpen = false; - //TODO: keyguard opens QS a different way; log that too? - - // Log the position of the swipe that opened the panel - float width = mCentralSurfaces.getDisplayWidth(); - float height = mCentralSurfaces.getDisplayHeight(); - int rot = mCentralSurfaces.getRotation(); - - mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND, - (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot); - mLockscreenGestureLogger - .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND); - } - - /** - * Maybe vibrate as panel is opened. - * - * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead - * being opened programmatically (such as by the open panel gesture), we always play haptic. - */ - protected void maybeVibrateOnOpening(boolean openingWithTouch) { - if (mVibrateOnOpening) { - if (!openingWithTouch || !mHasVibratedOnOpen) { - mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); - mHasVibratedOnOpen = true; - } - } - } - - protected abstract float getOpeningHeight(); - - /** - * @return whether the swiping direction is upwards and above a 45 degree angle compared to the - * horizontal direction - */ - private boolean isDirectionUpwards(float x, float y) { - float xDiff = x - mInitialExpandX; - float yDiff = y - mInitialExpandY; - if (yDiff >= 0) { - return false; - } - return Math.abs(yDiff) >= Math.abs(xDiff); - } - - public void startExpandMotion(float newX, float newY, boolean startTracking, - float expandedHeight) { - if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) { - beginJankMonitoring(); - } - mInitialOffsetOnTouch = expandedHeight; - mInitialExpandY = newY; - mInitialExpandX = newX; - mInitialTouchFromKeyguard = mKeyguardStateController.isShowing(); - if (startTracking) { - mTouchSlopExceeded = true; - setExpandedHeight(mInitialOffsetOnTouch); - onTrackingStarted(); - } - } - - private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { - mTrackingPointer = -1; - mAmbientState.setSwipingUp(false); - if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop - || Math.abs(y - mInitialExpandY) > mTouchSlop - || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { - mVelocityTracker.computeCurrentVelocity(1000); - float vel = mVelocityTracker.getYVelocity(); - float vectorVel = (float) Math.hypot( - mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); - - final boolean onKeyguard = mKeyguardStateController.isShowing(); - final boolean expand; - if (mKeyguardStateController.isKeyguardFadingAway() - || (mInitialTouchFromKeyguard && !onKeyguard)) { - // Don't expand for any touches that started from the keyguard and ended after the - // keyguard is gone. - expand = false; - } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { - if (onKeyguard) { - expand = true; - } else if (mCentralSurfaces.isBouncerShowingOverDream()) { - expand = false; - } else { - // If we get a cancel, put the shade back to the state it was in when the - // gesture started - expand = !mPanelClosedOnDown; - } - } else { - expand = flingExpands(vel, vectorVel, x, y); - } - - mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold, - mCentralSurfaces.isFalsingThresholdNeeded(), - mCentralSurfaces.isWakeUpComingFromTouch()); - // Log collapse gesture if on lock screen. - if (!expand && onKeyguard) { - float displayDensity = mCentralSurfaces.getDisplayDensity(); - int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity); - int velocityDp = (int) Math.abs(vel / displayDensity); - mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp); - mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK); - } - @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC - : y - mInitialExpandY > 0 ? QUICK_SETTINGS - : (mKeyguardStateController.canDismissLockScreen() - ? UNLOCK : BOUNCER_UNLOCK); - - fling(vel, expand, isFalseTouch(x, y, interactionType)); - onTrackingStopped(expand); - mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; - if (mUpdateFlingOnLayout) { - mUpdateFlingVelocity = vel; - } - } else if (!mCentralSurfaces.isBouncerShowing() - && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating() - && !mKeyguardStateController.isKeyguardGoingAway()) { - boolean expands = onEmptySpaceClick(); - onTrackingStopped(expands); - } - mVelocityTracker.clear(); - } - - protected float getCurrentExpandVelocity() { - mVelocityTracker.computeCurrentVelocity(1000); - return mVelocityTracker.getYVelocity(); - } - - protected abstract int getFalsingThreshold(); - - protected abstract boolean shouldGestureWaitForTouchSlop(); - - protected void onTrackingStopped(boolean expand) { - mTracking = false; - mCentralSurfaces.onTrackingStopped(expand); - updatePanelExpansionAndVisibility(); - } - - protected void onTrackingStarted() { - endClosing(); - mTracking = true; - mCentralSurfaces.onTrackingStarted(); - notifyExpandingStarted(); - updatePanelExpansionAndVisibility(); - } - - /** - * @return Whether a pair of coordinates are inside the visible view content bounds. - */ - protected abstract boolean isInContentBounds(float x, float y); - - protected void cancelHeightAnimator() { - if (mHeightAnimator != null) { - if (mHeightAnimator.isRunning()) { - mPanelUpdateWhenAnimatorEnds = false; - } - mHeightAnimator.cancel(); - } - endClosing(); - } - - private void endClosing() { - if (mClosing) { - setIsClosing(false); - onClosingFinished(); - } - } - - protected abstract boolean canCollapsePanelOnTouch(); - - protected float getContentHeight() { - return mExpandedHeight; - } - - /** - * @param vel the current vertical velocity of the motion - * @param vectorVel the length of the vectorial velocity - * @return whether a fling should expands the panel; contracts otherwise - */ - protected boolean flingExpands(float vel, float vectorVel, float x, float y) { - if (mFalsingManager.isUnlockingDisabled()) { - return true; - } - - @Classifier.InteractionType int interactionType = y - mInitialExpandY > 0 - ? QUICK_SETTINGS : ( - mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK); - - if (isFalseTouch(x, y, interactionType)) { - return true; - } - if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { - return shouldExpandWhenNotFlinging(); - } else { - return vel > 0; - } - } - - protected boolean shouldExpandWhenNotFlinging() { - return getExpandedFraction() > 0.5f; - } - - /** - * @param x the final x-coordinate when the finger was lifted - * @param y the final y-coordinate when the finger was lifted - * @return whether this motion should be regarded as a false touch - */ - private boolean isFalseTouch(float x, float y, - @Classifier.InteractionType int interactionType) { - if (!mCentralSurfaces.isFalsingThresholdNeeded()) { - return false; - } - if (mFalsingManager.isClassifierEnabled()) { - return mFalsingManager.isFalseTouch(interactionType); - } - if (!mTouchAboveFalsingThreshold) { - return true; - } - if (mUpwardsWhenThresholdReached) { - return false; - } - return !isDirectionUpwards(x, y); - } - - protected void fling(float vel, boolean expand) { - fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false); - } - - protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) { - fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing); - } - - protected void fling(float vel, boolean expand, float collapseSpeedUpFactor, - boolean expandBecauseOfFalsing) { - float target = expand ? getMaxPanelHeight() : 0; - if (!expand) { - setIsClosing(true); - } - flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); - } - - protected void flingToHeight(float vel, boolean expand, float target, - float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { - if (target == mExpandedHeight && mOverExpansion == 0.0f) { - // We're at the target and didn't fling and there's no overshoot - onFlingEnd(false /* cancelled */); - return; - } - mIsFlinging = true; - // we want to perform an overshoot animation when flinging open - final boolean addOverscroll = - expand - && !mInSplitShade // Split shade has its own overscroll logic - && mStatusBarStateController.getState() != StatusBarState.KEYGUARD - && mOverExpansion == 0.0f - && vel >= 0; - final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand); - float overshootAmount = 0.0f; - if (addOverscroll) { - // Let's overshoot depending on the amount of velocity - overshootAmount = MathUtils.lerp( - 0.2f, - 1.0f, - MathUtils.saturate(vel - / (mFlingAnimationUtils.getHighVelocityPxPerSecond() - * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT))); - overshootAmount += mOverExpansion / mPanelFlingOvershootAmount; - } - ValueAnimator animator = createHeightAnimator(target, overshootAmount); - if (expand) { - if (expandBecauseOfFalsing && vel < 0) { - vel = 0; - } - mFlingAnimationUtils.apply(animator, mExpandedHeight, - target + overshootAmount * mPanelFlingOvershootAmount, vel, mView.getHeight()); - if (vel == 0) { - animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION); - } - } else { - if (shouldUseDismissingAnimation()) { - if (vel == 0) { - animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); - long duration = (long) (200 + mExpandedHeight / mView.getHeight() * 100); - animator.setDuration(duration); - } else { - mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel, - mView.getHeight()); - } - } else { - mFlingAnimationUtilsClosing.apply( - animator, mExpandedHeight, target, vel, mView.getHeight()); - } - - // Make it shorter if we run a canned animation - if (vel == 0) { - animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor)); - } - if (mFixedDuration != NO_FIXED_DURATION) { - animator.setDuration(mFixedDuration); - } - } - animator.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationStart(Animator animation) { - if (!mStatusBarStateController.isDozing()) { - beginJankMonitoring(); - } - } - - @Override - public void onAnimationCancel(Animator animation) { - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (shouldSpringBack && !mCancelled) { - // After the shade is flinged open to an overscrolled state, spring back - // the shade by reducing section padding to 0. - springBack(); - } else { - onFlingEnd(mCancelled); - } - } - }); - setAnimator(animator); - animator.start(); - } - - private void springBack() { - if (mOverExpansion == 0) { - onFlingEnd(false /* cancelled */); - return; - } - mIsSpringBackAnimation = true; - ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0); - animator.addUpdateListener( - animation -> setOverExpansionInternal((float) animation.getAnimatedValue(), - false /* isFromGesture */)); - animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - animator.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - @Override - public void onAnimationCancel(Animator animation) { - mCancelled = true; - } - @Override - public void onAnimationEnd(Animator animation) { - mIsSpringBackAnimation = false; - onFlingEnd(mCancelled); - } - }); - setAnimator(animator); - animator.start(); - } - - protected void onFlingEnd(boolean cancelled) { - mIsFlinging = false; - // No overshoot when the animation ends - setOverExpansionInternal(0, false /* isFromGesture */); - setAnimator(null); - mKeyguardStateController.notifyPanelFlingEnd(); - if (!cancelled) { - endJankMonitoring(); - notifyExpandingFinished(); - } else { - cancelJankMonitoring(); - } - updatePanelExpansionAndVisibility(); - } - - protected abstract boolean shouldUseDismissingAnimation(); - - public String getName() { - return mViewName; - } - - public void setExpandedHeight(float height) { - if (DEBUG) logf("setExpandedHeight(%.1f)", height); - setExpandedHeightInternal(height); - } - - void updateExpandedHeightToMaxHeight() { - float currentMaxPanelHeight = getMaxPanelHeight(); - - if (isFullyCollapsed()) { - return; - } - - if (currentMaxPanelHeight == mExpandedHeight) { - return; - } - - if (mTracking && !isTrackingBlocked()) { - return; - } - - if (mHeightAnimator != null && !mIsSpringBackAnimation) { - mPanelUpdateWhenAnimatorEnds = true; - return; - } - - setExpandedHeight(currentMaxPanelHeight); - } - - /** - * Returns drag down distance after which panel should be fully expanded. Usually it's the - * same as max panel height but for large screen devices (especially split shade) we might - * want to return different value to shorten drag distance - */ - public abstract int getMaxPanelTransitionDistance(); - - public void setExpandedHeightInternal(float h) { - if (isNaN(h)) { - Log.wtf(TAG, "ExpandedHeight set to NaN"); - } - mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> { - if (mExpandLatencyTracking && h != 0f) { - DejankUtils.postAfterTraversal( - () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); - mExpandLatencyTracking = false; - } - float maxPanelHeight = getMaxPanelTransitionDistance(); - if (mHeightAnimator == null) { - // Split shade has its own overscroll logic - if (mTracking && !mInSplitShade) { - float overExpansionPixels = Math.max(0, h - maxPanelHeight); - setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); - } - } - mExpandedHeight = Math.min(h, maxPanelHeight); - // If we are closing the panel and we are almost there due to a slow decelerating - // interpolator, abort the animation. - if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) { - mExpandedHeight = 0f; - if (mHeightAnimator != null) { - mHeightAnimator.end(); - } - } - mExpansionDragDownAmountPx = h; - mExpandedFraction = Math.min(1f, - maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); - mAmbientState.setExpansionFraction(mExpandedFraction); - onHeightUpdated(mExpandedHeight); - updatePanelExpansionAndVisibility(); - }); - } - - /** - * @return true if the panel tracking should be temporarily blocked; this is used when a - * conflicting gesture (opening QS) is happening - */ - protected abstract boolean isTrackingBlocked(); - - protected void setOverExpansion(float overExpansion) { - mOverExpansion = overExpansion; - } - - /** - * Set the current overexpansion - * - * @param overExpansion the amount of overexpansion to apply - * @param isFromGesture is this amount from a gesture and needs to be rubberBanded? - */ - private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) { - if (!isFromGesture) { - mLastGesturedOverExpansion = -1; - setOverExpansion(overExpansion); - } else if (mLastGesturedOverExpansion != overExpansion) { - mLastGesturedOverExpansion = overExpansion; - final float heightForFullOvershoot = mView.getHeight() / 3.0f; - float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot); - newExpansion = Interpolators.getOvershootInterpolation(newExpansion); - setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f); - } - } - - protected abstract void onHeightUpdated(float expandedHeight); - - /** - * This returns the maximum height of the panel. Children should override this if their - * desired height is not the full height. - * - * @return the default implementation simply returns the maximum height. - */ - protected abstract int getMaxPanelHeight(); - - public void setExpandedFraction(float frac) { - setExpandedHeight(getMaxPanelTransitionDistance() * frac); - } - - public float getExpandedHeight() { - return mExpandedHeight; - } - - public float getExpandedFraction() { - return mExpandedFraction; - } - - public boolean isFullyExpanded() { - return mExpandedHeight >= getMaxPanelHeight(); - } - - public boolean isFullyCollapsed() { - return mExpandedFraction <= 0.0f; - } - - public boolean isCollapsing() { - return mClosing || mIsLaunchAnimationRunning; - } - - public boolean isFlinging() { - return mIsFlinging; - } - - public boolean isTracking() { - return mTracking; - } - - public void collapse(boolean delayed, float speedUpFactor) { - if (DEBUG) logf("collapse: " + this); - if (canPanelBeCollapsed()) { - cancelHeightAnimator(); - notifyExpandingStarted(); - - // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state. - setIsClosing(true); - if (delayed) { - mNextCollapseSpeedUpFactor = speedUpFactor; - mView.postDelayed(mFlingCollapseRunnable, 120); - } else { - fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */); - } - } - } - - public boolean canPanelBeCollapsed() { - return !isFullyCollapsed() && !mTracking && !mClosing; - } - - private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, - mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */); - - public void expand(final boolean animate) { - if (!isFullyCollapsed() && !isCollapsing()) { - return; - } - - mInstantExpanding = true; - mAnimateAfterExpanding = animate; - mUpdateFlingOnLayout = false; - abortAnimations(); - if (mTracking) { - onTrackingStopped(true /* expands */); // The panel is expanded after this call. - } - if (mExpanding) { - notifyExpandingFinished(); - } - updatePanelExpansionAndVisibility(); - - // Wait for window manager to pickup the change, so we know the maximum height of the panel - // then. - mView.getViewTreeObserver().addOnGlobalLayoutListener( - new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - if (!mInstantExpanding) { - mView.getViewTreeObserver().removeOnGlobalLayoutListener(this); - return; - } - if (mCentralSurfaces.getNotificationShadeWindowView().isVisibleToUser()) { - mView.getViewTreeObserver().removeOnGlobalLayoutListener(this); - if (mAnimateAfterExpanding) { - notifyExpandingStarted(); - beginJankMonitoring(); - fling(0, true /* expand */); - } else { - setExpandedFraction(1f); - } - mInstantExpanding = false; - } - } - }); - - // Make sure a layout really happens. - mView.requestLayout(); - } - - public void instantCollapse() { - abortAnimations(); - setExpandedFraction(0f); - if (mExpanding) { - notifyExpandingFinished(); - } - if (mInstantExpanding) { - mInstantExpanding = false; - updatePanelExpansionAndVisibility(); - } - } - - private void abortAnimations() { - cancelHeightAnimator(); - mView.removeCallbacks(mFlingCollapseRunnable); - } - - protected abstract void onClosingFinished(); - - protected void startUnlockHintAnimation() { - - // We don't need to hint the user if an animation is already running or the user is changing - // the expansion. - if (mHeightAnimator != null || mTracking) { - return; - } - notifyExpandingStarted(); - startUnlockHintAnimationPhase1(() -> { - notifyExpandingFinished(); - onUnlockHintFinished(); - mHintAnimationRunning = false; - }); - onUnlockHintStarted(); - mHintAnimationRunning = true; - } - - protected void onUnlockHintFinished() { - mCentralSurfaces.onHintFinished(); - } - - protected void onUnlockHintStarted() { - mCentralSurfaces.onUnlockHintStarted(); - } - - public boolean isUnlockHintRunning() { - return mHintAnimationRunning; - } - - /** - * Phase 1: Move everything upwards. - */ - private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) { - float target = Math.max(0, getMaxPanelHeight() - mHintDistance); - ValueAnimator animator = createHeightAnimator(target); - animator.setDuration(250); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - animator.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationCancel(Animator animation) { - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mCancelled) { - setAnimator(null); - onAnimationFinished.run(); - } else { - startUnlockHintAnimationPhase2(onAnimationFinished); - } - } - }); - animator.start(); - setAnimator(animator); - - final List<ViewPropertyAnimator> indicationAnimators = - mKeyguardBottomArea.getIndicationAreaAnimators(); - for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) { - indicationAreaAnimator - .translationY(-mHintDistance) - .setDuration(250) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .withEndAction(() -> indicationAreaAnimator - .translationY(0) - .setDuration(450) - .setInterpolator(mBounceInterpolator) - .start()) - .start(); - } - } - - private void setAnimator(ValueAnimator animator) { - mHeightAnimator = animator; - if (animator == null && mPanelUpdateWhenAnimatorEnds) { - mPanelUpdateWhenAnimatorEnds = false; - updateExpandedHeightToMaxHeight(); - } - } - - /** - * Phase 2: Bounce down. - */ - private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) { - ValueAnimator animator = createHeightAnimator(getMaxPanelHeight()); - animator.setDuration(450); - animator.setInterpolator(mBounceInterpolator); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setAnimator(null); - onAnimationFinished.run(); - updatePanelExpansionAndVisibility(); - } - }); - animator.start(); - setAnimator(animator); - } - - private ValueAnimator createHeightAnimator(float targetHeight) { - return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */); - } - - /** - * Create an animator that can also overshoot - * - * @param targetHeight the target height - * @param overshootAmount the amount of overshoot desired - */ - private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) { - float startExpansion = mOverExpansion; - ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); - animator.addUpdateListener( - animation -> { - if (overshootAmount > 0.0f - // Also remove the overExpansion when collapsing - || (targetHeight == 0.0f && startExpansion != 0)) { - final float expansion = MathUtils.lerp( - startExpansion, - mPanelFlingOvershootAmount * overshootAmount, - Interpolators.FAST_OUT_SLOW_IN.getInterpolation( - animator.getAnimatedFraction())); - setOverExpansionInternal(expansion, false /* isFromGesture */); - } - setExpandedHeightInternal((float) animation.getAnimatedValue()); - }); - return animator; - } - - /** Update the visibility of {@link NotificationPanelView} if necessary. */ - public void updateVisibility() { - mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE); - } - - /** Returns true if {@link NotificationPanelView} should be visible. */ - abstract protected boolean shouldPanelBeVisible(); - - /** - * Updates the panel expansion and {@link NotificationPanelView} visibility if necessary. - * - * TODO(b/200063118): Could public calls to this method be replaced with calls to - * {@link #updateVisibility()}? That would allow us to make this method private. - */ - public void updatePanelExpansionAndVisibility() { - mShadeExpansionStateManager.onPanelExpansionChanged( - mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); - updateVisibility(); - } - - public boolean isExpanded() { - return mExpandedFraction > 0f - || mInstantExpanding - || isPanelVisibleBecauseOfHeadsUp() - || mTracking - || mHeightAnimator != null - && !mIsSpringBackAnimation; - } - - protected abstract boolean isPanelVisibleBecauseOfHeadsUp(); - - /** - * Gets called when the user performs a click anywhere in the empty area of the panel. - * - * @return whether the panel will be expanded after the action performed by this method - */ - protected boolean onEmptySpaceClick() { - if (mHintAnimationRunning) { - return true; - } - return onMiddleClicked(); - } - - protected abstract boolean onMiddleClicked(); - - protected abstract boolean isDozing(); - - public void dump(PrintWriter pw, String[] args) { - pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" - + " tracking=%s timeAnim=%s%s " - + "touchDisabled=%s" + "]", - this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(), - mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator, - ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""), - mTouchDisabled ? "T" : "f")); - } - - public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { - mHeadsUpManager = headsUpManager; - } - - public void setIsLaunchAnimationRunning(boolean running) { - mIsLaunchAnimationRunning = running; - } - - protected void setIsClosing(boolean isClosing) { - mClosing = isClosing; - } - - protected boolean isClosing() { - return mClosing; - } - - public void collapseWithDuration(int animationDuration) { - mFixedDuration = animationDuration; - collapse(false /* delayed */, 1.0f /* speedUpFactor */); - mFixedDuration = NO_FIXED_DURATION; - } - - public ViewGroup getView() { - // TODO: remove this method, or at least reduce references to it. - return mView; - } - - protected abstract OnLayoutChangeListener createLayoutChangeListener(); - - protected abstract TouchHandler createTouchHandler(); - - protected OnConfigurationChangedListener createOnConfigurationChangedListener() { - return new OnConfigurationChangedListener(); - } - - public class TouchHandler implements View.OnTouchListener { - - public boolean onInterceptTouchEvent(MotionEvent event) { - if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted - && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { - return false; - } - - /* - * If the user drags anywhere inside the panel we intercept it if the movement is - * upwards. This allows closing the shade from anywhere inside the panel. - * - * We only do this if the current content is scrolled to the bottom, - * i.e canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling - * gesture - * possible. - */ - int pointerIndex = event.findPointerIndex(mTrackingPointer); - if (pointerIndex < 0) { - pointerIndex = 0; - mTrackingPointer = event.getPointerId(pointerIndex); - } - final float x = event.getX(pointerIndex); - final float y = event.getY(pointerIndex); - boolean canCollapsePanel = canCollapsePanelOnTouch(); - - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mCentralSurfaces.userActivity(); - mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation; - mMinExpandHeight = 0.0f; - mDownTime = mSystemClock.uptimeMillis(); - if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) { - cancelHeightAnimator(); - mTouchSlopExceeded = true; - return true; - } - mInitialExpandY = y; - mInitialExpandX = x; - mTouchStartedInEmptyArea = !isInContentBounds(x, y); - mTouchSlopExceeded = mTouchSlopExceededBeforeDown; - mMotionAborted = false; - mPanelClosedOnDown = isFullyCollapsed(); - mCollapsedAndHeadsUpOnDown = false; - mHasLayoutedSinceDown = false; - mUpdateFlingOnLayout = false; - mTouchAboveFalsingThreshold = false; - addMovement(event); - break; - case MotionEvent.ACTION_POINTER_UP: - final int upPointer = event.getPointerId(event.getActionIndex()); - if (mTrackingPointer == upPointer) { - // gesture is ongoing, find a new pointer to track - final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; - mTrackingPointer = event.getPointerId(newIndex); - mInitialExpandX = event.getX(newIndex); - mInitialExpandY = event.getY(newIndex); - } - break; - case MotionEvent.ACTION_POINTER_DOWN: - if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { - mMotionAborted = true; - mVelocityTracker.clear(); - } - break; - case MotionEvent.ACTION_MOVE: - final float h = y - mInitialExpandY; - addMovement(event); - final boolean openShadeWithoutHun = - mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown; - if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown - || openShadeWithoutHun) { - float hAbs = Math.abs(h); - float touchSlop = getTouchSlop(event); - if ((h < -touchSlop - || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop)) - && hAbs > Math.abs(x - mInitialExpandX)) { - cancelHeightAnimator(); - startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); - return true; - } - } - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mVelocityTracker.clear(); - break; - } - return false; - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (mInstantExpanding) { - mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding"); - return false; - } - if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) { - mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled"); - return false; - } - if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) { - mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted"); - return false; - } - - // If dragging should not expand the notifications shade, then return false. - if (!mNotificationsDragEnabled) { - if (mTracking) { - // Turn off tracking if it's on or the shade can get stuck in the down position. - onTrackingStopped(true /* expand */); - } - mShadeLog.logMotionEvent(event, "onTouch: drag not enabled"); - return false; - } - - // On expanding, single mouse click expands the panel instead of dragging. - if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) { - if (event.getAction() == MotionEvent.ACTION_UP) { - expand(true); - } - return true; - } - - /* - * We capture touch events here and update the expand height here in case according to - * the users fingers. This also handles multi-touch. - * - * Flinging is also enabled in order to open or close the shade. - */ - - int pointerIndex = event.findPointerIndex(mTrackingPointer); - if (pointerIndex < 0) { - pointerIndex = 0; - mTrackingPointer = event.getPointerId(pointerIndex); - } - final float x = event.getX(pointerIndex); - final float y = event.getY(pointerIndex); - - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop(); - mIgnoreXTouchSlop = true; - } - - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); - mMinExpandHeight = 0.0f; - mPanelClosedOnDown = isFullyCollapsed(); - mHasLayoutedSinceDown = false; - mUpdateFlingOnLayout = false; - mMotionAborted = false; - mDownTime = mSystemClock.uptimeMillis(); - mTouchAboveFalsingThreshold = false; - mCollapsedAndHeadsUpOnDown = - isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); - addMovement(event); - boolean regularHeightAnimationRunning = mHeightAnimator != null - && !mHintAnimationRunning && !mIsSpringBackAnimation; - if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) { - mTouchSlopExceeded = regularHeightAnimationRunning - || mTouchSlopExceededBeforeDown; - cancelHeightAnimator(); - onTrackingStarted(); - } - if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp() - && !mCentralSurfaces.isBouncerShowing()) { - startOpening(event); - } - break; - - case MotionEvent.ACTION_POINTER_UP: - final int upPointer = event.getPointerId(event.getActionIndex()); - if (mTrackingPointer == upPointer) { - // gesture is ongoing, find a new pointer to track - final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; - final float newY = event.getY(newIndex); - final float newX = event.getX(newIndex); - mTrackingPointer = event.getPointerId(newIndex); - mHandlingPointerUp = true; - startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight); - mHandlingPointerUp = false; - } - break; - case MotionEvent.ACTION_POINTER_DOWN: - if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { - mMotionAborted = true; - endMotionEvent(event, x, y, true /* forceCancel */); - return false; - } - break; - case MotionEvent.ACTION_MOVE: - addMovement(event); - if (!isFullyCollapsed()) { - maybeVibrateOnOpening(true /* openingWithTouch */); - } - float h = y - mInitialExpandY; - - // If the panel was collapsed when touching, we only need to check for the - // y-component of the gesture, as we have no conflicting horizontal gesture. - if (Math.abs(h) > getTouchSlop(event) - && (Math.abs(h) > Math.abs(x - mInitialExpandX) - || mIgnoreXTouchSlop)) { - mTouchSlopExceeded = true; - if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) { - if (mInitialOffsetOnTouch != 0f) { - startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); - h = 0; - } - cancelHeightAnimator(); - onTrackingStarted(); - } - } - float newHeight = Math.max(0, h + mInitialOffsetOnTouch); - newHeight = Math.max(newHeight, mMinExpandHeight); - if (-h >= getFalsingThreshold()) { - mTouchAboveFalsingThreshold = true; - mUpwardsWhenThresholdReached = isDirectionUpwards(x, y); - } - if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) { - // Count h==0 as part of swipe-up, - // otherwise {@link NotificationStackScrollLayout} - // wrongly enables stack height updates at the start of lockscreen swipe-up - mAmbientState.setSwipingUp(h <= 0); - setExpandedHeightInternal(newHeight); - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - addMovement(event); - endMotionEvent(event, x, y, false /* forceCancel */); - // mHeightAnimator is null, there is no remaining frame, ends instrumenting. - if (mHeightAnimator == null) { - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - endJankMonitoring(); - } else { - cancelJankMonitoring(); - } - } - break; - } - return !mGestureWaitForTouchSlop || mTracking; - } - } - - protected abstract class OnLayoutChangeListener implements View.OnLayoutChangeListener { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, - int oldTop, int oldRight, int oldBottom) { - updateExpandedHeightToMaxHeight(); - mHasLayoutedSinceDown = true; - if (mUpdateFlingOnLayout) { - abortAnimations(); - fling(mUpdateFlingVelocity, true /* expands */); - mUpdateFlingOnLayout = false; - } - } - } - - public class OnConfigurationChangedListener implements - NotificationPanelView.OnConfigurationChangedListener { - @Override - public void onConfigurationChanged(Configuration newConfig) { - loadDimens(); - } - } - - private void beginJankMonitoring() { - if (mInteractionJankMonitor == null) { - return; - } - InteractionJankMonitor.Configuration.Builder builder = - InteractionJankMonitor.Configuration.Builder.withView( - InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, - mView) - .setTag(isFullyCollapsed() ? "Expand" : "Collapse"); - mInteractionJankMonitor.begin(builder); - } - - private void endJankMonitoring() { - if (mInteractionJankMonitor == null) { - return; - } - InteractionJankMonitor.getInstance().end( - InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); - } - - private void cancelJankMonitoring() { - if (mInteractionJankMonitor == null) { - return; - } - InteractionJankMonitor.getInstance().cancel( - InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); - } - - protected float getExpansionFraction() { - return mExpandedFraction; - } - - protected ShadeExpansionStateManager getPanelExpansionStateManager() { - return mShadeExpansionStateManager; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index f1e44ce5736e..2b788d85a14c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -1,20 +1,17 @@ package com.android.systemui.shade import android.view.MotionEvent -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogMessage import com.android.systemui.log.dagger.ShadeLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogMessage import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject private const val TAG = "systemui.shade" /** Lightweight logging utility for the Shade. */ -class ShadeLogger @Inject constructor( - @ShadeLog - private val buffer: LogBuffer -) { +class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { fun v(@CompileTimeConstant msg: String) { buffer.log(TAG, LogLevel.VERBOSE, msg) } @@ -28,21 +25,56 @@ class ShadeLogger @Inject constructor( } fun onQsInterceptMoveQsTrackingEnabled(h: Float) { - log(LogLevel.VERBOSE, + log( + LogLevel.VERBOSE, { double1 = h.toDouble() }, - { "onQsIn[tercept: move action, QS tracking enabled. h = $double1" }) + { "onQsIntercept: move action, QS tracking enabled. h = $double1" } + ) + } + + fun logQsTrackingNotStarted( + initialTouchY: Float, + y: Float, + h: Float, + touchSlop: Float, + qsExpanded: Boolean, + collapsedOnDown: Boolean, + keyguardShowing: Boolean, + qsExpansionEnabled: Boolean + ) { + log( + LogLevel.VERBOSE, + { + int1 = initialTouchY.toInt() + int2 = y.toInt() + long1 = h.toLong() + double1 = touchSlop.toDouble() + bool1 = qsExpanded + bool2 = collapsedOnDown + bool3 = keyguardShowing + bool4 = qsExpansionEnabled + }, + { + "QsTrackingNotStarted: initTouchY=$int1,y=$int2,h=$long1,slop=$double1,qsExpanded" + + "=$bool1,collapsedDown=$bool2,keyguardShowing=$bool3,qsExpansion=$bool4" + } + ) } fun logMotionEvent(event: MotionEvent, message: String) { - log(LogLevel.VERBOSE, { - str1 = message - long1 = event.eventTime - long2 = event.downTime - int1 = event.action - int2 = event.classification - double1 = event.y.toDouble() - }, { - "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,classification=$int2" - }) + log( + LogLevel.VERBOSE, + { + str1 = message + long1 = event.eventTime + long2 = event.downTime + int1 = event.action + int2 = event.classification + double1 = event.y.toDouble() + }, + { + "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" + } + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt new file mode 100644 index 000000000000..09019a69df47 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -0,0 +1,62 @@ +/* + * 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.shade.data.repository + +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.shade.ShadeExpansionChangeEvent +import com.android.systemui.shade.ShadeExpansionListener +import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.shade.domain.model.ShadeModel +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged + +/** Business logic for shade interactions */ +@SysUISingleton +class ShadeRepository @Inject constructor(shadeExpansionStateManager: ShadeExpansionStateManager) { + + val shadeModel: Flow<ShadeModel> = + conflatedCallbackFlow { + val callback = + object : ShadeExpansionListener { + override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) { + // Don't propagate ShadeExpansionChangeEvent.dragDownPxAmount field. + // It is too noisy and produces extra events that consumers won't care + // about + val info = + ShadeModel( + expansionAmount = event.fraction, + isExpanded = event.expanded, + isUserDragging = event.tracking + ) + trySendWithFailureLogging(info, TAG, "updated shade expansion info") + } + } + + shadeExpansionStateManager.addExpansionListener(callback) + trySendWithFailureLogging(ShadeModel(), TAG, "initial shade expansion info") + + awaitClose { shadeExpansionStateManager.removeExpansionListener(callback) } + } + .distinctUntilChanged() + + companion object { + private const val TAG = "ShadeRepository" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt new file mode 100644 index 000000000000..ce0f4283ff83 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt @@ -0,0 +1,28 @@ +/* + * 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.shade.domain.model + +import android.annotation.FloatRange + +/** Information about shade (NotificationPanel) expansion */ +data class ShadeModel( + /** 0 when collapsed, 1 when fully expanded. */ + @FloatRange(from = 0.0, to = 1.0) val expansionAmount: Float = 0f, + /** Whether the panel should be considered expanded */ + val isExpanded: Boolean = false, + /** Whether the user is actively dragging the panel. */ + val isUserDragging: Boolean = false, +) diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java index 6abf339685e4..ff26766fba66 100644 --- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java @@ -32,10 +32,10 @@ import javax.inject.Inject; * Dispatches shortcut to System UI components */ @SysUISingleton -public class ShortcutKeyDispatcher extends CoreStartable - implements ShortcutKeyServiceProxy.Callbacks { +public class ShortcutKeyDispatcher implements CoreStartable, ShortcutKeyServiceProxy.Callbacks { private static final String TAG = "ShortcutKeyDispatcher"; + private final Context mContext; private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this); private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); @@ -50,7 +50,7 @@ public class ShortcutKeyDispatcher extends CoreStartable @Inject public ShortcutKeyDispatcher(Context context) { - super(context); + mContext = context; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt index 7f7ff9cf4881..90c52bd8c9f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt @@ -17,9 +17,9 @@ package com.android.systemui.statusbar import android.app.PendingIntent -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotifInteractionLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.notification.collection.NotificationEntry import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index c290ce260cc6..184dc253bfc6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -189,7 +189,6 @@ public class NotificationLockscreenUserManagerImpl implements protected NotificationPresenter mPresenter; protected ContentObserver mLockscreenSettingsObserver; protected ContentObserver mSettingsObserver; - private boolean mHideSilentNotificationsOnLockscreen; @Inject public NotificationLockscreenUserManagerImpl(Context context, @@ -266,12 +265,6 @@ public class NotificationLockscreenUserManagerImpl implements UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( - mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS), - true, - mLockscreenSettingsObserver, - UserHandle.USER_ALL); - - mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, mSettingsObserver); @@ -340,9 +333,6 @@ public class NotificationLockscreenUserManagerImpl implements final boolean allowedByDpm = (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; - mHideSilentNotificationsOnLockscreen = mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1, mCurrentUserId) == 0; - setShowLockscreenNotifications(show && allowedByDpm); if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt index bbff0466cf50..8222c9d9ba59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt @@ -52,7 +52,10 @@ import javax.inject.Inject import kotlin.math.max /** - * A utility class to enable the downward swipe on when pulsing. + * A utility class that handles notification panel expansion when a user swipes downward on a + * notification from the pulsing state. + * If face-bypass is enabled, the user can swipe down anywhere on the screen (not just from a + * notification) to trigger the notification panel expansion. */ @SysUISingleton class PulseExpansionHandler @Inject @@ -62,7 +65,7 @@ constructor( private val bypassController: KeyguardBypassController, private val headsUpManager: HeadsUpManagerPhone, private val roundnessManager: NotificationRoundnessManager, - private val configurationController: ConfigurationController, + configurationController: ConfigurationController, private val statusBarStateController: StatusBarStateController, private val falsingManager: FalsingManager, private val lockscreenShadeTransitionController: LockscreenShadeTransitionController, 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 b3dd853cd2e1..402217dac185 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -71,9 +71,9 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; -import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogLevel; import com.android.systemui.log.dagger.StatusBarNetworkControllerLog; +import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.plugins.log.LogLevel; import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.policy.ConfigurationController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt index 17feaa842165..9bdff928c44b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.gesture -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.SwipeStatusBarAwayLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject /** Log messages for [SwipeStatusBarAwayGestureHandler]. */ 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 8f8813b80b5f..842204bbf621 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -118,6 +118,7 @@ class LockscreenSmartspaceController @Inject constructor( regionSamplingEnabled, updateFun ) + initializeTextColors(regionSamplingInstance) regionSamplingInstance.startRegionSampler() regionSamplingInstances.put(v, regionSamplingInstance) connectSession() @@ -361,18 +362,20 @@ class LockscreenSmartspaceController @Inject constructor( } } + private fun initializeTextColors(regionSamplingInstance: RegionSamplingInstance) { + val lightThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_LightWallpaper) + val darkColor = Utils.getColorAttrDefaultColor(lightThemeContext, R.attr.wallpaperTextColor) + + val darkThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI) + val lightColor = Utils.getColorAttrDefaultColor(darkThemeContext, R.attr.wallpaperTextColor) + + regionSamplingInstance.setForegroundColors(lightColor, darkColor) + } + private fun updateTextColorFromRegionSampler() { smartspaceViews.forEach { - val isRegionDark = regionSamplingInstances.getValue(it).currentRegionDarkness() - val themeID = if (isRegionDark.isDark) { - R.style.Theme_SystemUI - } else { - R.style.Theme_SystemUI_LightWallpaper - } - val themedContext = ContextThemeWrapper(context, themeID) - val wallpaperTextColor = - Utils.getColorAttrDefaultColor(themedContext, R.attr.wallpaperTextColor) - it.setPrimaryTextColor(wallpaperTextColor) + val textColor = regionSamplingInstances.getValue(it).currentForegroundColor() + it.setPrimaryTextColor(textColor) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java index 59022c0ffbf2..0a5e9867a17f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java @@ -66,11 +66,12 @@ import javax.inject.Inject; * splitted screen. */ @SysUISingleton -public class InstantAppNotifier extends CoreStartable - implements CommandQueue.Callbacks, KeyguardStateController.Callback { +public class InstantAppNotifier + implements CoreStartable, CommandQueue.Callbacks, KeyguardStateController.Callback { private static final String TAG = "InstantAppNotifier"; public static final int NUM_TASKS_FOR_INSTANT_APP_INFO = 5; + private final Context mContext; private final Handler mHandler = new Handler(); private final Executor mUiBgExecutor; private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>(); @@ -83,7 +84,7 @@ public class InstantAppNotifier extends CoreStartable CommandQueue commandQueue, @UiBackground Executor uiBgExecutor, KeyguardStateController keyguardStateController) { - super(context); + mContext = context; mCommandQueue = commandQueue; mUiBgExecutor = uiBgExecutor; mKeyguardStateController = keyguardStateController; @@ -289,7 +290,7 @@ public class InstantAppNotifier extends CoreStartable .setComponent(aiaComponent) .setAction(Intent.ACTION_VIEW) .addCategory(Intent.CATEGORY_BROWSABLE) - .addCategory("unique:" + System.currentTimeMillis()) + .setIdentifier("unique:" + System.currentTimeMillis()) .putExtra(Intent.EXTRA_PACKAGE_NAME, appInfo.packageName) .putExtra( Intent.EXTRA_VERSION_CODE, 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 7fbdd35796c1..36b8333688ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt @@ -17,30 +17,14 @@ package com.android.systemui.statusbar.notification import android.content.Context -import android.util.Log -import android.widget.Toast import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.util.Compile import javax.inject.Inject class NotifPipelineFlags @Inject constructor( val context: Context, val featureFlags: FeatureFlags ) { - fun checkLegacyPipelineEnabled(): Boolean { - if (Compile.IS_DEBUG) { - Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show() - } - if (featureFlags.isEnabled(Flags.NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE)) { - throw RuntimeException("Old pipeline code running with new pipeline enabled") - } else { - Log.d("NotifPipeline", "Old pipeline code running with new pipeline enabled", - Exception()) - } - return false - } - fun isDevLoggingEnabled(): Boolean = featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING) @@ -53,4 +37,12 @@ class NotifPipelineFlags @Inject constructor( fun fullScreenIntentRequiresKeyguard(): Boolean = featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD) + + val isStabilityIndexFixEnabled: Boolean by lazy { + featureFlags.isEnabled(Flags.STABILITY_INDEX_FIX) + } + + val isSemiStableSortEnabled: Boolean by lazy { + featureFlags.isEnabled(Flags.SEMI_STABLE_SORT) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt index ad3dfedcdb96..3058fbbc1031 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotifInteractionLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.notification.collection.NotificationEntry import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt index f8449ae8807b..84ab0d1190f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt @@ -68,6 +68,9 @@ data class ListAttachState private constructor( */ var stableIndex: Int = -1 + /** Access the index of the [section] or -1 if the entry does not have one */ + val sectionIndex: Int get() = section?.index ?: -1 + /** Copies the state of another instance. */ fun clone(other: ListAttachState) { parent = other.parent @@ -95,11 +98,13 @@ data class ListAttachState private constructor( * This can happen if the entry is removed from a group that was broken up or if the entry was * filtered out during any of the filtering steps. */ - fun detach() { + fun detach(includingStableIndex: Boolean) { parent = null section = null promoter = null - // stableIndex = -1 // TODO(b/241229236): Clear this once we fix the stability fragility + if (includingStableIndex) { + stableIndex = -1 + } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index e129ee45817a..3ae2545e4e10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -54,6 +54,9 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState; +import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort; +import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort.StableOrder; +import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper; import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator; @@ -96,11 +99,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated // TODO replace temp with collection pool for readability private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>(); + private NotifPipelineFlags mFlags; private final boolean mAlwaysLogList; private List<ListEntry> mNotifList = new ArrayList<>(); private List<ListEntry> mNewNotifList = new ArrayList<>(); + private final SemiStableSort mSemiStableSort = new SemiStableSort(); + private final StableOrder<ListEntry> mStableOrder = this::getStableOrderRank; private final PipelineState mPipelineState = new PipelineState(); private final Map<String, GroupEntry> mGroups = new ArrayMap<>(); private Collection<NotificationEntry> mAllEntries = Collections.emptyList(); @@ -141,6 +147,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { ) { mSystemClock = systemClock; mLogger = logger; + mFlags = flags; mAlwaysLogList = flags.isDevLoggingEnabled(); mInteractionTracker = interactionTracker; mChoreographer = pipelineChoreographer; @@ -527,7 +534,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { List<NotifFilter> filters) { Trace.beginSection("ShadeListBuilder.filterNotifs"); final long now = mSystemClock.uptimeMillis(); - for (ListEntry entry : entries) { + for (ListEntry entry : entries) { if (entry instanceof GroupEntry) { final GroupEntry groupEntry = (GroupEntry) entry; @@ -958,7 +965,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { * filtered out during any of the filtering steps. */ private void annulAddition(ListEntry entry) { - entry.getAttachState().detach(); + // NOTE(b/241229236): Don't clear stableIndex until we fix stability fragility + entry.getAttachState().detach(/* includingStableIndex= */ mFlags.isSemiStableSortEnabled()); } private void assignSections() { @@ -978,7 +986,16 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { private void sortListAndGroups() { Trace.beginSection("ShadeListBuilder.sortListAndGroups"); - // Assign sections to top-level elements and sort their children + if (mFlags.isSemiStableSortEnabled()) { + sortWithSemiStableSort(); + } else { + sortWithLegacyStability(); + } + Trace.endSection(); + } + + private void sortWithLegacyStability() { + // Sort all groups and the top level list for (ListEntry entry : mNotifList) { if (entry instanceof GroupEntry) { GroupEntry parent = (GroupEntry) entry; @@ -991,16 +1008,15 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { // Check for suppressed order changes if (!getStabilityManager().isEveryChangeAllowed()) { mForceReorderable = true; - boolean isSorted = isShadeSorted(); + boolean isSorted = isShadeSortedLegacy(); mForceReorderable = false; if (!isSorted) { getStabilityManager().onEntryReorderSuppressed(); } } - Trace.endSection(); } - private boolean isShadeSorted() { + private boolean isShadeSortedLegacy() { if (!isSorted(mNotifList, mTopLevelComparator)) { return false; } @@ -1014,6 +1030,43 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { return true; } + private void sortWithSemiStableSort() { + // Sort each group's children + boolean allSorted = true; + for (ListEntry entry : mNotifList) { + if (entry instanceof GroupEntry) { + GroupEntry parent = (GroupEntry) entry; + allSorted &= sortGroupChildren(parent.getRawChildren()); + } + } + // Sort each section within the top level list + mNotifList.sort(mTopLevelComparator); + if (!getStabilityManager().isEveryChangeAllowed()) { + for (List<ListEntry> subList : getSectionSubLists(mNotifList)) { + allSorted &= mSemiStableSort.stabilizeTo(subList, mStableOrder, mNewNotifList); + } + applyNewNotifList(); + } + assignIndexes(mNotifList); + if (!allSorted) { + // Report suppressed order changes + getStabilityManager().onEntryReorderSuppressed(); + } + } + + private Iterable<List<ListEntry>> getSectionSubLists(List<ListEntry> entries) { + return ShadeListBuilderHelper.INSTANCE.getSectionSubLists(entries); + } + + private boolean sortGroupChildren(List<NotificationEntry> entries) { + if (getStabilityManager().isEveryChangeAllowed()) { + entries.sort(mGroupChildrenComparator); + return true; + } else { + return mSemiStableSort.sort(entries, mStableOrder, mGroupChildrenComparator); + } + } + /** Determine whether the items in the list are sorted according to the comparator */ @VisibleForTesting public static <T> boolean isSorted(List<T> items, Comparator<? super T> comparator) { @@ -1036,27 +1089,41 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { /** * Assign the index of each notification relative to the total order */ - private static void assignIndexes(List<ListEntry> notifList) { + private void assignIndexes(List<ListEntry> notifList) { if (notifList.size() == 0) return; NotifSection currentSection = requireNonNull(notifList.get(0).getSection()); int sectionMemberIndex = 0; for (int i = 0; i < notifList.size(); i++) { - ListEntry entry = notifList.get(i); + final ListEntry entry = notifList.get(i); NotifSection section = requireNonNull(entry.getSection()); if (section.getIndex() != currentSection.getIndex()) { sectionMemberIndex = 0; currentSection = section; } - entry.getAttachState().setStableIndex(sectionMemberIndex); - if (entry instanceof GroupEntry) { - GroupEntry parent = (GroupEntry) entry; - for (int j = 0; j < parent.getChildren().size(); j++) { - entry = parent.getChildren().get(j); - entry.getAttachState().setStableIndex(sectionMemberIndex); - sectionMemberIndex++; + if (mFlags.isStabilityIndexFixEnabled()) { + entry.getAttachState().setStableIndex(sectionMemberIndex++); + if (entry instanceof GroupEntry) { + final GroupEntry parent = (GroupEntry) entry; + final NotificationEntry summary = parent.getSummary(); + if (summary != null) { + summary.getAttachState().setStableIndex(sectionMemberIndex++); + } + for (NotificationEntry child : parent.getChildren()) { + child.getAttachState().setStableIndex(sectionMemberIndex++); + } + } + } else { + // This old implementation uses the same index number for the group as the first + // child, and fails to assign an index to the summary. Remove once tested. + entry.getAttachState().setStableIndex(sectionMemberIndex); + if (entry instanceof GroupEntry) { + final GroupEntry parent = (GroupEntry) entry; + for (NotificationEntry child : parent.getChildren()) { + child.getAttachState().setStableIndex(sectionMemberIndex++); + } } + sectionMemberIndex++; } - sectionMemberIndex++; } } @@ -1196,7 +1263,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { o2.getSectionIndex()); if (cmp != 0) return cmp; - cmp = Integer.compare( + cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare( getStableOrderIndex(o1), getStableOrderIndex(o2)); if (cmp != 0) return cmp; @@ -1225,7 +1292,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> { - int cmp = Integer.compare( + int cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare( getStableOrderIndex(o1), getStableOrderIndex(o2)); if (cmp != 0) return cmp; @@ -1256,9 +1323,25 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { // let the stability manager constrain or allow reordering return -1; } + // NOTE(b/241229236): Can't use cleared section index until we fix stability fragility return entry.getPreviousAttachState().getStableIndex(); } + @Nullable + private Integer getStableOrderRank(ListEntry entry) { + if (getStabilityManager().isEntryReorderingAllowed(entry)) { + // let the stability manager constrain or allow reordering + return null; + } + if (entry.getAttachState().getSectionIndex() + != entry.getPreviousAttachState().getSectionIndex()) { + // stable index is only valid within the same section; otherwise we allow reordering + return null; + } + final int stableIndex = entry.getPreviousAttachState().getStableIndex(); + return stableIndex == -1 ? null : stableIndex; + } + private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) { final NotifFilter filter = findRejectingFilter(entry, now, filters); entry.getAttachState().setExcludingFilter(filter); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt index 211e37473a70..68d1319699d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.collection.coalescer -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject class GroupCoalescerLogger @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt index e8f352f60da0..2919def16304 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.notification.row.NotificationGuts import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt index 8625cdbc89d5..dfaa291c6bb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt @@ -1,9 +1,10 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.util.Log -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel + import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject private const val TAG = "HeadsUpCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt index c4f4ed54e2fa..9558f47af795 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt index c687e1bacbc9..d80445491bda 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject private const val TAG = "ShadeEventCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt new file mode 100644 index 000000000000..9ec8e07e73b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.listbuilder + +import androidx.annotation.VisibleForTesting +import kotlin.math.sign + +class SemiStableSort { + val preallocatedWorkspace by lazy { ArrayList<Any>() } + val preallocatedAdditions by lazy { ArrayList<Any>() } + val preallocatedMapToIndex by lazy { HashMap<Any, Int>() } + val preallocatedMapToIndexComparator: Comparator<Any> by lazy { + Comparator.comparingInt { item -> preallocatedMapToIndex[item] ?: -1 } + } + + /** + * Sort the given [items] such that items which have a [stableOrder] will all be in that order, + * items without a [stableOrder] will be sorted according to the comparator, and the two sets of + * items will be combined to have the fewest elements out of order according to the [comparator] + * . The result will be placed into the original [items] list. + */ + fun <T : Any> sort( + items: MutableList<T>, + stableOrder: StableOrder<in T>, + comparator: Comparator<in T>, + ): Boolean = + withWorkspace<T, Boolean> { workspace -> + val ordered = + sortTo( + items, + stableOrder, + comparator, + workspace, + ) + items.clear() + items.addAll(workspace) + return ordered + } + + /** + * Sort the given [items] such that items which have a [stableOrder] will all be in that order, + * items without a [stableOrder] will be sorted according to the comparator, and the two sets of + * items will be combined to have the fewest elements out of order according to the [comparator] + * . The result will be put into [output]. + */ + fun <T : Any> sortTo( + items: Iterable<T>, + stableOrder: StableOrder<in T>, + comparator: Comparator<in T>, + output: MutableList<T>, + ): Boolean { + if (DEBUG) println("\n> START from ${items.map { it to stableOrder.getRank(it) }}") + // If array already has elements, use subList to ensure we only append + val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size) + items.filterTo(result) { stableOrder.getRank(it) != null } + result.sortBy { stableOrder.getRank(it)!! } + val isOrdered = result.isSorted(comparator) + withAdditions<T> { additions -> + items.filterTo(additions) { stableOrder.getRank(it) == null } + additions.sortWith(comparator) + insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator) + } + return isOrdered + } + + /** + * Rearrange the [sortedItems] to enforce that items are in the [stableOrder], and store the + * result in [output]. Items with a [stableOrder] will be in that order, items without a + * [stableOrder] will remain in same relative order as the input, and the two sets of items will + * be combined to have the fewest elements moved from their locations in the original. + */ + fun <T : Any> stabilizeTo( + sortedItems: Iterable<T>, + stableOrder: StableOrder<in T>, + output: MutableList<T>, + ): Boolean { + // Append to the output array if present + val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size) + sortedItems.filterTo(result) { stableOrder.getRank(it) != null } + val stableRankComparator = compareBy<T> { stableOrder.getRank(it)!! } + val isOrdered = result.isSorted(stableRankComparator) + if (!isOrdered) { + result.sortWith(stableRankComparator) + } + if (result.isEmpty()) { + sortedItems.filterTo(result) { stableOrder.getRank(it) == null } + return isOrdered + } + withAdditions<T> { additions -> + sortedItems.filterTo(additions) { stableOrder.getRank(it) == null } + if (additions.isNotEmpty()) { + withIndexOfComparator(sortedItems) { comparator -> + insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator) + } + } + } + return isOrdered + } + + private inline fun <T : Any, R> withWorkspace(block: (ArrayList<T>) -> R): R { + preallocatedWorkspace.clear() + val result = block(preallocatedWorkspace as ArrayList<T>) + preallocatedWorkspace.clear() + return result + } + + private inline fun <T : Any> withAdditions(block: (ArrayList<T>) -> Unit) { + preallocatedAdditions.clear() + block(preallocatedAdditions as ArrayList<T>) + preallocatedAdditions.clear() + } + + private inline fun <T : Any> withIndexOfComparator( + sortedItems: Iterable<T>, + block: (Comparator<in T>) -> Unit + ) { + preallocatedMapToIndex.clear() + sortedItems.forEachIndexed { i, item -> preallocatedMapToIndex[item] = i } + block(preallocatedMapToIndexComparator as Comparator<in T>) + preallocatedMapToIndex.clear() + } + + companion object { + + /** + * This is the core of the algorithm. + * + * Insert [preSortedAdditions] (the elements to be inserted) into [existing] without + * changing the relative order of any elements already in [existing], even though those + * elements may be mis-ordered relative to the [comparator], such that the total number of + * elements which are ordered incorrectly according to the [comparator] is fewest. + */ + private fun <T> insertPreSortedElementsWithFewestMisOrderings( + existing: MutableList<T>, + preSortedAdditions: Iterable<T>, + comparator: Comparator<in T>, + ) { + if (DEBUG) println(" To $existing insert $preSortedAdditions with fewest misordering") + var iStart = 0 + preSortedAdditions.forEach { toAdd -> + if (DEBUG) println(" need to add $toAdd to $existing, starting at $iStart") + var cmpSum = 0 + var cmpSumMax = 0 + var iCmpSumMax = iStart + if (DEBUG) print(" ") + for (i in iCmpSumMax until existing.size) { + val cmp = comparator.compare(toAdd, existing[i]).sign + cmpSum += cmp + if (cmpSum > cmpSumMax) { + cmpSumMax = cmpSum + iCmpSumMax = i + 1 + } + if (DEBUG) print("sum[$i]=$cmpSum, ") + } + if (DEBUG) println("inserting $toAdd at $iCmpSumMax") + existing.add(iCmpSumMax, toAdd) + iStart = iCmpSumMax + 1 + } + } + + /** Determines if a list is correctly sorted according to the given comparator */ + @VisibleForTesting + fun <T> List<T>.isSorted(comparator: Comparator<T>): Boolean { + if (this.size <= 1) { + return true + } + val iterator = this.iterator() + var previous = iterator.next() + var current: T? + while (iterator.hasNext()) { + current = iterator.next() + if (comparator.compare(previous, current) > 0) { + return false + } + previous = current + } + return true + } + } + + fun interface StableOrder<T> { + fun getRank(item: T): Int? + } +} + +val DEBUG = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt new file mode 100644 index 000000000000..d8f75f61c05a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.listbuilder + +import com.android.systemui.statusbar.notification.collection.ListEntry + +object ShadeListBuilderHelper { + fun getSectionSubLists(entries: List<ListEntry>): Iterable<List<ListEntry>> = + getContiguousSubLists(entries, minLength = 1) { it.sectionIndex } + + inline fun <T : Any, K : Any> getContiguousSubLists( + itemList: List<T>, + minLength: Int = 1, + key: (T) -> K, + ): Iterable<List<T>> { + val subLists = mutableListOf<List<T>>() + val numEntries = itemList.size + var currentSectionStartIndex = 0 + var currentSectionKey: K? = null + for (i in 0 until numEntries) { + val sectionKey = key(itemList[i]) + if (currentSectionKey == null) { + currentSectionKey = sectionKey + } else if (currentSectionKey != sectionKey) { + val length = i - currentSectionStartIndex + if (length >= minLength) { + subLists.add(itemList.subList(currentSectionStartIndex, i)) + } + currentSectionStartIndex = i + currentSectionKey = sectionKey + } + } + val length = numEntries - currentSectionStartIndex + if (length >= minLength) { + subLists.add(itemList.subList(currentSectionStartIndex, numEntries)) + } + return subLists + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index d8dae5d23f42..8e052c7dcc5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.notification.collection.listbuilder -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.WARNING import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index aa27e1e407f0..911a2d0c2b36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -20,13 +20,13 @@ import android.os.RemoteException import android.service.notification.NotificationListenerService import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING -import com.android.systemui.log.LogLevel.WTF import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.WARNING +import com.android.systemui.plugins.log.LogLevel.WTF import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt index 38e3d496a60c..9c71e5c1054c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.collection.render -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection import com.android.systemui.util.Compile diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt index b6278d1d5f01..fde4ecb7bcaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.render +import javax.inject.Inject + /** An interface by which the pipeline can make updates to the notification root view. */ interface NotifStackController { /** Provides stats about the list of notifications attached to the shade */ @@ -42,6 +44,6 @@ data class NotifStats( * methods, rather than forcing us to add no-op implementations in their implementation every time * a method is added. */ -open class DefaultNotifStackController : NotifStackController { +open class DefaultNotifStackController @Inject constructor() : NotifStackController { override fun setNotifStats(stats: NotifStats) {} }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt index 6d1071c283e3..b4b9438cd6be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.collection.render -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import java.lang.RuntimeException import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java index 6f41425b506d..9a7610ddd354 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java @@ -114,7 +114,18 @@ public class HeadsUpViewBinder { */ public void unbindHeadsUpView(NotificationEntry entry) { abortBindCallback(entry); - mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP); + + // params may be null if the notification was already removed from the collection but we let + // it stick around during a launch animation. In this case, the heads up view has already + // been unbound, so we don't need to unbind it. + // TODO(b/253081345): Change this back to getStageParams and remove null check. + RowContentBindParams params = mStage.tryGetStageParams(entry); + if (params == null) { + mLogger.entryBindStageParamsNullOnUnbind(entry); + return; + } + + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP); mLogger.entryContentViewMarkedFreeable(entry); mStage.requestRebind(entry, e -> mLogger.entryUnbound(e)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt index d1feaa05c653..d4f11fc141f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.notification.interruption -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject @@ -47,6 +47,14 @@ class HeadsUpViewBinderLogger @Inject constructor(@NotificationHeadsUpLog val bu "start unbinding heads up entry $str1 " }) } + + fun entryBindStageParamsNullOnUnbind(entry: NotificationEntry) { + buffer.log(TAG, INFO, { + str1 = entry.logKey + }, { + "heads up entry bind stage params null on unbind $str1 " + }) + } } private const val TAG = "HeadsUpViewBinder" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index c956a2ea1836..e6dbcee10f60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -72,7 +72,6 @@ private interface KeyguardNotificationVisibilityProviderImplModule { @SysUISingleton private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( - context: Context, @Main private val handler: Handler, private val keyguardStateController: KeyguardStateController, private val lockscreenUserManager: NotificationLockscreenUserManager, @@ -82,7 +81,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val broadcastDispatcher: BroadcastDispatcher, private val secureSettings: SecureSettings, private val globalSettings: GlobalSettings -) : CoreStartable(context), KeyguardNotificationVisibilityProvider { +) : CoreStartable, KeyguardNotificationVisibilityProvider { private val showSilentNotifsUri = secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS) private val onStateChangedListeners = ListenerSet<Consumer<String>>() @@ -232,7 +231,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private fun readShowSilentNotificationSetting() { val showSilentNotifs = secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, - true, UserHandle.USER_CURRENT) + false, UserHandle.USER_CURRENT) hideSilentNotificationsOnLockscreen = !showSilentNotifs } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 99d320d1c7ca..073b6b041b81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.notification.interruption -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.dagger.NotificationInterruptLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.WARNING import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt index fe03b2ad6a32..10197a38527e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.logging -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.NotificationRenderLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.stack.NotificationSection diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java index 7c41800d880d..d626c18e46f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java @@ -21,6 +21,7 @@ import android.util.ArrayMap; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -64,7 +65,7 @@ public abstract class BindStage<Params> extends BindRequester { * Get the stage parameters for the entry. Clients should use this to modify how the stage * handles the notification content. */ - public final Params getStageParams(@NonNull NotificationEntry entry) { + public final @NonNull Params getStageParams(@NonNull NotificationEntry entry) { Params params = mContentParams.get(entry); if (params == null) { // TODO: This should throw an exception but there are some cases of re-entrant calls @@ -79,6 +80,17 @@ public abstract class BindStage<Params> extends BindRequester { return params; } + // TODO(b/253081345): Remove this method. + /** + * Get the stage parameters for the entry, or null if there are no stage parameters for the + * entry. + * + * @see #getStageParams(NotificationEntry) + */ + public final @Nullable Params tryGetStageParams(@NonNull NotificationEntry entry) { + return mContentParams.get(entry); + } + /** * Create a params entry for the notification for this stage. */ 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 1b006485c83d..087dc71f6cf2 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 @@ -1487,7 +1487,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.setAlpha(alpha); } if (mChildrenContainer != null) { - mChildrenContainer.setContentAlpha(alpha); + mChildrenContainer.setAlpha(alpha); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt index ab91926d466a..46fef3f973a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.row -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt index f9923b2254d7..8a5d29a1ae2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.row -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 2719dd88b7be..b2628e40e77e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -142,6 +142,11 @@ public class AmbientState implements Dumpable { */ private boolean mIsFlingRequiredAfterLockScreenSwipeUp = false; + /** + * Whether the shade is currently closing. + */ + private boolean mIsClosing; + @VisibleForTesting public boolean isFlingRequiredAfterLockScreenSwipeUp() { return mIsFlingRequiredAfterLockScreenSwipeUp; @@ -717,6 +722,20 @@ public class AmbientState implements Dumpable { && mStatusBarKeyguardViewManager.isBouncerInTransit(); } + /** + * @param isClosing Whether the shade is currently closing. + */ + public void setIsClosing(boolean isClosing) { + mIsClosing = isClosing; + } + + /** + * @return Whether the shade is currently closing. + */ + public boolean isClosing() { + return mIsClosing; + } + @Override public void dump(PrintWriter pw, String[] args) { pw.println("mTopPadding=" + mTopPadding); @@ -761,5 +780,6 @@ public class AmbientState implements Dumpable { + mIsFlingRequiredAfterLockScreenSwipeUp); pw.println("mZDistanceBetweenElements=" + mZDistanceBetweenElements); pw.println("mBaseZHeight=" + mBaseZHeight); + pw.println("mIsClosing=" + mIsClosing); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 0dda2632db66..7b23a56af836 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -461,20 +461,6 @@ public class NotificationChildrenContainer extends ViewGroup return mAttachedChildren; } - /** - * Sets the alpha on the content, while leaving the background of the container itself as is. - * - * @param alpha alpha value to apply to the content - */ - public void setContentAlpha(float alpha) { - for (int i = 0; i < mNotificationHeader.getChildCount(); i++) { - mNotificationHeader.getChildAt(i).setAlpha(alpha); - } - for (ExpandableNotificationRow child : getAttachedChildren()) { - child.setContentAlpha(alpha); - } - } - /** To be called any time the rows have been updated */ public void updateExpansionStates() { if (mChildrenExpanded || mUserLocked) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt index cb7dfe87f7fb..b61c55edadcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt @@ -17,9 +17,9 @@ package com.android.systemui.statusbar.notification.stack import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationSectionLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject private const val TAG = "NotifSections" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 55c577f1ea39..2272411b4314 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -255,7 +255,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mClearAllInProgress; private FooterClearAllListener mFooterClearAllListener; private boolean mFlingAfterUpEvent; - /** * Was the scroller scrolled to the top when the down motion was observed? */ @@ -4020,8 +4019,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable setOwnScrollY(0); } + @VisibleForTesting @ShadeViewRefactor(RefactorComponent.COORDINATOR) - private void setIsExpanded(boolean isExpanded) { + void setIsExpanded(boolean isExpanded) { boolean changed = isExpanded != mIsExpanded; mIsExpanded = isExpanded; mStackScrollAlgorithm.setIsExpanded(isExpanded); @@ -4842,13 +4842,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } + @VisibleForTesting @ShadeViewRefactor(RefactorComponent.COORDINATOR) - private void setOwnScrollY(int ownScrollY) { + void setOwnScrollY(int ownScrollY) { setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */); } @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) { + // Avoid Flicking during clear all + // when the shade finishes closing, onExpansionStopped will call + // resetScrollPosition to setOwnScrollY to 0 + if (mAmbientState.isClosing()) { + return; + } + if (ownScrollY != mOwnScrollY) { // We still want to call the normal scrolled changed for accessibility reasons onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt index 5f79c0e3913a..4c52db7f8732 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.notification.stack -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt index cb4a0884fea4..f5de678a8536 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.notification.stack -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject 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 9958ec7e87fb..604e1467f950 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -91,7 +91,6 @@ import android.util.EventLog; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; -import android.util.Slog; import android.view.Display; import android.view.IRemoteAnimationRunner; import android.view.IWindowManager; @@ -270,8 +269,7 @@ import dagger.Lazy; * </b> */ @SysUISingleton -public class CentralSurfacesImpl extends CoreStartable implements - CentralSurfaces { +public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private static final String BANNER_ACTION_CANCEL = "com.android.systemui.statusbar.banner_action_cancel"; @@ -307,6 +305,7 @@ public class CentralSurfacesImpl extends CoreStartable implements ONLY_CORE_APPS = onlyCoreApps; } + private final Context mContext; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks; private float mTransitionToFullShadeProgress = 0f; @@ -764,7 +763,7 @@ public class CentralSurfacesImpl extends CoreStartable implements DeviceStateManager deviceStateManager, WiredChargingRippleController wiredChargingRippleController, IDreamManager dreamManager) { - super(context); + mContext = context; mNotificationsController = notificationsController; mFragmentService = fragmentService; mLightBarController = lightBarController; @@ -3324,7 +3323,7 @@ public class CentralSurfacesImpl extends CoreStartable implements @Override public boolean onBackPressed() { if (mStatusBarKeyguardViewManager.canHandleBackPressed()) { - mStatusBarKeyguardViewManager.onBackPressed(false /* unused */); + mStatusBarKeyguardViewManager.onBackPressed(); return true; } if (mNotificationPanelViewController.isQsCustomizing()) { @@ -3550,7 +3549,7 @@ public class CentralSurfacesImpl extends CoreStartable implements public void setBouncerShowingOverDream(boolean bouncerShowingOverDream) { mBouncerShowingOverDream = bouncerShowingOverDream; } - + /** * Propagate the bouncer state to status bar components. * @@ -3812,8 +3811,7 @@ public class CentralSurfacesImpl extends CoreStartable implements if (mDevicePolicyManager.getCameraDisabled(null, mLockscreenUserManager.getCurrentUserId())) { return false; - } else if (mStatusBarKeyguardViewManager == null - || (isKeyguardShowing() && isKeyguardSecure())) { + } else if (isKeyguardShowing() && isKeyguardSecure()) { // Check if the admin has disabled the camera specifically for the keyguard return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, mLockscreenUserManager.getCurrentUserId()) @@ -4221,14 +4219,6 @@ public class CentralSurfacesImpl extends CoreStartable implements @Override public boolean isKeyguardSecure() { - if (mStatusBarKeyguardViewManager == null) { - // startKeyguard() hasn't been called yet, so we don't know. - // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this - // value onVisibilityChanged(). - Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false", - new Throwable()); - return false; - } return mStatusBarKeyguardViewManager.isSecure(); } @Override @@ -4288,11 +4278,6 @@ public class CentralSurfacesImpl extends CoreStartable implements .Callback() { @Override public void onFinished() { - if (mStatusBarKeyguardViewManager == null) { - Log.w(TAG, "Tried to notify keyguard visibility when " - + "mStatusBarKeyguardViewManager was null"); - return; - } if (mKeyguardStateController.isKeyguardFadingAway()) { mStatusBarKeyguardViewManager.onKeyguardFadedAway(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index d61c51e76e86..9bb4132490d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -91,6 +91,11 @@ public class KeyguardBouncer { mBouncerPromptReason = mCallback.getBouncerPromptReason(); } } + + @Override + public void onNonStrongBiometricAllowedChanged(int userId) { + mBouncerPromptReason = mCallback.getBouncerPromptReason(); + } }; private final Runnable mRemoveViewRunnable = this::removeView; private final KeyguardBypassController mKeyguardBypassController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index e3e85728ff83..5e26cf062b58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -47,7 +47,7 @@ class KeyguardLiftController @Inject constructor( private val asyncSensorManager: AsyncSensorManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val dumpManager: DumpManager -) : Dumpable, CoreStartable(context) { +) : Dumpable, CoreStartable { private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE) private var isListening = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt index 02b235493715..4839fe6a7bef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt @@ -19,9 +19,9 @@ package com.android.systemui.statusbar.phone import android.util.DisplayMetrics import android.view.View import com.android.internal.logging.nano.MetricsProto.MetricsEvent -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.LSShadeTransitionLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 9f932238007a..84907686835b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -249,6 +249,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private Callback mCallback; private boolean mWallpaperSupportsAmbientMode; private boolean mScreenOn; + private boolean mTransparentScrimBackground; // Scrim blanking callbacks private Runnable mPendingFrameCallback; @@ -341,6 +342,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mScrimBehind.setDefaultFocusHighlightEnabled(false); mNotificationsScrim.setDefaultFocusHighlightEnabled(false); mScrimInFront.setDefaultFocusHighlightEnabled(false); + mTransparentScrimBackground = notificationsScrim.getResources() + .getBoolean(R.bool.notification_scrim_transparent); updateScrims(); mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); } @@ -777,13 +780,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump float behindFraction = getInterpolatedFraction(); behindFraction = (float) Math.pow(behindFraction, 0.8f); if (mClipsQsScrim) { - mBehindAlpha = 1; - mNotificationsAlpha = behindFraction * mDefaultScrimAlpha; + mBehindAlpha = mTransparentScrimBackground ? 0 : 1; + mNotificationsAlpha = + mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha; } else { - mBehindAlpha = behindFraction * mDefaultScrimAlpha; + mBehindAlpha = + mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha; // Delay fade-in of notification scrim a bit further, to coincide with the // view fade in. Otherwise the empty panel can be quite jarring. - mNotificationsAlpha = MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f, + mNotificationsAlpha = mTransparentScrimBackground + ? 0 : MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f, mPanelExpansionFraction); } mBehindTint = mState.getBehindTint(); 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 b9312c7fc40a..8362e68c8dab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -203,7 +203,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (DEBUG) { Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()"); } - onBackPressed(false /* unused */); + onBackPressed(); }; private boolean mIsBackCallbackRegistered = false; @@ -225,6 +225,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb protected CentralSurfaces mCentralSurfaces; private NotificationPanelViewController mNotificationPanelViewController; private BiometricUnlockController mBiometricUnlockController; + private boolean mCentralSurfacesRegistered; private View mNotificationContainer; @@ -356,6 +357,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mNotificationContainer = notificationContainer; mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create( centralSurfaces.getKeyguardMessageArea()); + mCentralSurfacesRegistered = true; registerListeners(); } @@ -1086,21 +1088,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb /** * Notifies this manager that the back button has been pressed. */ - // TODO(b/244635782): This "accept boolean and ignore it, and always return false" was done - // to make it possible to check this in *and* allow merging to master, - // where ArcStatusBarKeyguardViewManager inherits this class, and its - // build will break if we change this interface. - // So, overall, while this function refactors the behavior of onBackPressed, - // (it now handles the back press, and no longer returns *whether* it did so) - // its interface is not changing right now (but will, in a follow-up CL). - public boolean onBackPressed(boolean ignored) { + public void onBackPressed() { if (!canHandleBackPressed()) { - return false; + return; } mCentralSurfaces.endAffordanceLaunch(); // The second condition is for SIM card locked bouncer - if (bouncerIsScrimmed() && needsFullscreenBouncer()) { + if (bouncerIsScrimmed() && !needsFullscreenBouncer()) { hideBouncer(false); updateStates(); } else { @@ -1116,7 +1111,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mNotificationPanelViewController.expandWithoutQs(); } } - return false; + return; } @Override @@ -1175,6 +1170,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb }; protected void updateStates() { + if (!mCentralSurfacesRegistered) { + return; + } boolean showing = mKeyguardStateController.isShowing(); boolean occluded = mKeyguardStateController.isOccluded(); boolean bouncerShowing = bouncerIsShowing(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt index b9a1413ff791..81edff45c505 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt @@ -17,12 +17,12 @@ package com.android.systemui.statusbar.phone import android.app.PendingIntent -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.dagger.NotifInteractionLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.WARNING import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt index 9ae378f34fc0..0e6b7f253bcf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.phone.fragment -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.CollapsedSbFragmentLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.DisableFlagsLogger import javax.inject.Inject 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 dbb1aa54d8ee..d3cf32fb44ce 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 @@ -18,10 +18,10 @@ package com.android.systemui.statusbar.pipeline.shared import android.net.Network import android.net.NetworkCapabilities -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.StatusBarConnectivityLog +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString import javax.inject.Inject import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt index 5b2d69564585..28a9b97b8ea6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt @@ -35,19 +35,15 @@ protected constructor( protected val controller: UserSwitcherController, ) : BaseAdapter() { - protected open val users: ArrayList<UserRecord> - get() = controller.users + protected open val users: List<UserRecord> + get() = controller.users.filter { !controller.isKeyguardShowing || !it.isRestricted } init { controller.addAdapter(WeakReference(this)) } override fun getCount(): Int { - return if (controller.isKeyguardShowing) { - users.count { !it.isRestricted } - } else { - users.size - } + return users.size } override fun getItem(position: Int): UserRecord { @@ -112,6 +108,7 @@ protected constructor( item.isGuest, item.isAddSupervisedUser, isTablet, + item.isManageUsers, ) return checkNotNull(context.getDrawable(iconRes)) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt index d7c81af53d8b..df1e80b78c9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -16,10 +16,10 @@ package com.android.systemui.statusbar.policy -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.VERBOSE import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.INFO +import com.android.systemui.plugins.log.LogLevel.VERBOSE import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index 494a4bbbdf9f..c1506541229d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -53,6 +53,7 @@ import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.ViewController; import java.util.ArrayList; +import java.util.List; import javax.inject.Inject; @@ -456,7 +457,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } void refreshUserOrder() { - ArrayList<UserRecord> users = super.getUsers(); + List<UserRecord> users = super.getUsers(); mUsersOrdered = new ArrayList<>(users.size()); for (int i = 0; i < users.size(); i++) { UserRecord record = users.get(i); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt index af39eeed26b0..935fc7f10198 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt @@ -249,7 +249,7 @@ constructor( override fun startActivity(intent: Intent) { if (useInteractor) { - activityStarter.startActivity(intent, /* dismissShade= */ false) + activityStarter.startActivity(intent, /* dismissShade= */ true) } else { _oldImpl.startActivity(intent) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java index 46d2f3ac9ce4..c294c370a601 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java @@ -49,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.LatencyTracker; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.users.UserCreatingDialog; import com.android.systemui.GuestResetOrExitSessionReceiver; import com.android.systemui.GuestResumeSessionReceiver; @@ -399,6 +400,16 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController { records.add(userRecord); } + if (canManageUsers()) { + records.add(LegacyUserDataHelper.createRecord( + mContext, + KeyguardUpdateMonitor.getCurrentUser(), + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + /* isRestricted= */ false, + /* isSwitchToEnabled= */ true + )); + } + mUiExecutor.execute(() -> { if (records != null) { mUsers = records; @@ -438,6 +449,14 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController { && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); } + @VisibleForTesting + boolean canManageUsers() { + UserInfo currentUser = mUserTracker.getUserInfo(); + return mUserSwitcherEnabled + && ((currentUser != null && currentUser.isAdmin()) + || mAddUsersFromLockScreen.getValue()); + } + private boolean createIsRestricted() { return !mAddUsersFromLockScreen.getValue(); } @@ -525,6 +544,8 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController { showAddUserDialog(dialogShower); } else if (record.isAddSupervisedUser) { startSupervisedUserActivity(); + } else if (record.isManageUsers) { + startActivity(new Intent(Settings.ACTION_USER_SETTINGS)); } else { onUserListItemClicked(record.info.id, record, dialogShower); } @@ -984,7 +1005,7 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController { @Override public void startActivity(Intent intent) { - mActivityStarter.startActivity(intent, true); + mActivityStarter.startActivity(intent, /* dismissShade= */ true); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 09298b60ff51..b1b8341d9584 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -37,19 +37,20 @@ import dagger.Lazy; * Serves as a collection of UI components, rather than showing its own UI. */ @SysUISingleton -public class TvStatusBar extends CoreStartable implements CommandQueue.Callbacks { +public class TvStatusBar implements CoreStartable, CommandQueue.Callbacks { private static final String ACTION_SHOW_PIP_MENU = "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU"; private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"; + private final Context mContext; private final CommandQueue mCommandQueue; private final Lazy<AssistManager> mAssistManagerLazy; @Inject public TvStatusBar(Context context, CommandQueue commandQueue, Lazy<AssistManager> assistManagerLazy) { - super(context); + mContext = context; mCommandQueue = commandQueue; mAssistManagerLazy = assistManagerLazy; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt index c1997446c126..b938c9002d90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt @@ -35,9 +35,9 @@ import javax.inject.Inject */ @SysUISingleton class VpnStatusObserver @Inject constructor( - context: Context, + private val context: Context, private val securityController: SecurityController -) : CoreStartable(context), +) : CoreStartable, SecurityController.SecurityControllerCallback { private var vpnConnected = false @@ -102,7 +102,7 @@ class VpnStatusObserver @Inject constructor( .apply { vpnName?.let { setContentText( - mContext.getString( + context.getString( R.string.notification_disclosure_vpn_text, it ) ) @@ -111,23 +111,23 @@ class VpnStatusObserver @Inject constructor( .build() private fun createVpnConnectedNotificationBuilder() = - Notification.Builder(mContext, NOTIFICATION_CHANNEL_TV_VPN) + Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN) .setSmallIcon(vpnIconId) .setVisibility(Notification.VISIBILITY_PUBLIC) .setCategory(Notification.CATEGORY_SYSTEM) .extend(Notification.TvExtender()) .setOngoing(true) - .setContentTitle(mContext.getString(R.string.notification_vpn_connected)) - .setContentIntent(VpnConfig.getIntentForStatusPanel(mContext)) + .setContentTitle(context.getString(R.string.notification_vpn_connected)) + .setContentIntent(VpnConfig.getIntentForStatusPanel(context)) private fun createVpnDisconnectedNotification() = - Notification.Builder(mContext, NOTIFICATION_CHANNEL_TV_VPN) + Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN) .setSmallIcon(vpnIconId) .setVisibility(Notification.VISIBILITY_PUBLIC) .setCategory(Notification.CATEGORY_SYSTEM) .extend(Notification.TvExtender()) .setTimeoutAfter(VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS) - .setContentTitle(mContext.getString(R.string.notification_vpn_disconnected)) + .setContentTitle(context.getString(R.string.notification_vpn_disconnected)) .build() companion object { @@ -137,4 +137,4 @@ class VpnStatusObserver @Inject constructor( private const val TAG = "TvVpnNotification" private const val VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS = 5_000L } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java index 8026ba517820..b92725bd7cf7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java @@ -18,13 +18,13 @@ package com.android.systemui.statusbar.tv.notifications; import android.annotation.Nullable; import android.app.Notification; -import android.content.Context; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log; import android.util.SparseArray; import com.android.systemui.CoreStartable; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.NotificationListener; import javax.inject.Inject; @@ -32,7 +32,8 @@ import javax.inject.Inject; /** * Keeps track of the notifications on TV. */ -public class TvNotificationHandler extends CoreStartable implements +@SysUISingleton +public class TvNotificationHandler implements CoreStartable, NotificationListener.NotificationHandler { private static final String TAG = "TvNotificationHandler"; private final NotificationListener mNotificationListener; @@ -41,8 +42,7 @@ public class TvNotificationHandler extends CoreStartable implements private Listener mUpdateListener; @Inject - public TvNotificationHandler(Context context, NotificationListener notificationListener) { - super(context); + public TvNotificationHandler(NotificationListener notificationListener) { mNotificationListener = notificationListener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java index 892fedcc8ce2..dbbd0b8613de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java @@ -35,14 +35,15 @@ import javax.inject.Inject; * Offers control methods for the notification panel handler on TV devices. */ @SysUISingleton -public class TvNotificationPanel extends CoreStartable implements CommandQueue.Callbacks { +public class TvNotificationPanel implements CoreStartable, CommandQueue.Callbacks { private static final String TAG = "TvNotificationPanel"; + private final Context mContext; private final CommandQueue mCommandQueue; private final String mNotificationHandlerPackage; @Inject public TvNotificationPanel(Context context, CommandQueue commandQueue) { - super(context); + mContext = context; mCommandQueue = commandQueue; mNotificationHandlerPackage = mContext.getResources().getString( com.android.internal.R.string.config_notificationHandlerPackage); diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index 4450b76a878c..d5d904c50f20 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -62,7 +62,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora @LayoutRes private val viewLayoutRes: Int, private val windowTitle: String, private val wakeReason: String, -) : CoreStartable(context) { +) : CoreStartable { /** * Window layout params that will be used as a starting point for the [windowLayoutParams] of * all subclasses. @@ -131,7 +131,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora ) cancelViewTimeout?.run() cancelViewTimeout = mainExecutor.executeDelayed( - { removeView(TemporaryDisplayRemovalReason.REASON_TIMEOUT) }, + { removeView(REMOVAL_REASON_TIMEOUT) }, timeout.toLong() ) } @@ -175,9 +175,6 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora */ fun removeView(removalReason: String) { val currentDisplayInfo = displayInfo ?: return - if (shouldIgnoreViewRemoval(currentDisplayInfo.info, removalReason)) { - return - } val currentView = currentDisplayInfo.view animateViewOut(currentView) { windowManager.removeView(currentView) } @@ -193,13 +190,6 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora } /** - * Returns true if a view removal request should be ignored and false otherwise. - * - * Allows subclasses to keep the view visible for longer in certain circumstances. - */ - open fun shouldIgnoreViewRemoval(info: T, removalReason: String): Boolean = false - - /** * A method implemented by subclasses to update [currentView] based on [newInfo]. */ abstract fun updateView(newInfo: T, currentView: ViewGroup) @@ -236,10 +226,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora ) } -object TemporaryDisplayRemovalReason { - const val REASON_TIMEOUT = "TIMEOUT" - const val REASON_SCREEN_TAP = "SCREEN_TAP" -} +private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT" private data class IconInfo( val iconName: String, diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt index 606a11a84686..a7185cb18c40 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt @@ -16,8 +16,8 @@ package com.android.systemui.temporarydisplay -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel /** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */ open class TemporaryViewLogger( diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt new file mode 100644 index 000000000000..45ce687a1a4d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt @@ -0,0 +1,198 @@ +/* + * 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.temporarydisplay.chipbar + +import android.content.Context +import android.graphics.Rect +import android.os.PowerManager +import android.view.Gravity +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.view.accessibility.AccessibilityManager +import android.widget.TextView +import com.android.internal.widget.CachingIconView +import com.android.systemui.Gefingerpoken +import com.android.systemui.R +import com.android.systemui.animation.Interpolators +import com.android.systemui.animation.ViewHierarchyAnimator +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription +import com.android.systemui.common.shared.model.Text.Companion.loadText +import com.android.systemui.common.ui.binder.IconViewBinder +import com.android.systemui.common.ui.binder.TextViewBinder +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.media.taptotransfer.common.MediaTttUtils +import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.temporarydisplay.TemporaryViewDisplayController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.view.ViewUtil +import javax.inject.Inject + +/** + * A coordinator for showing/hiding the chipbar. + * + * The chipbar is a UI element that displays on top of all content. It appears at the top of the + * screen and consists of an icon, one line of text, and an optional end icon or action. It will + * auto-dismiss after some amount of seconds. The user is *not* able to manually dismiss the + * chipbar. + * + * It should be only be used for critical and temporary information that the user *must* be aware + * of. In general, prefer using heads-up notifications, since they are dismissable and will remain + * in the list of notifications until the user dismisses them. + * + * Only one chipbar may be shown at a time. + * TODO(b/245610654): Should we just display whichever chipbar was most recently requested, or do we + * need to maintain a priority ordering? + * + * TODO(b/245610654): Remove all media-related items from this class so it's just for generic + * chipbars. + */ +@SysUISingleton +open class ChipbarCoordinator @Inject constructor( + context: Context, + @MediaTttSenderLogger logger: MediaTttLogger, + windowManager: WindowManager, + @Main mainExecutor: DelayableExecutor, + accessibilityManager: AccessibilityManager, + configurationController: ConfigurationController, + powerManager: PowerManager, + private val falsingManager: FalsingManager, + private val falsingCollector: FalsingCollector, + private val viewUtil: ViewUtil, +) : TemporaryViewDisplayController<ChipbarInfo, MediaTttLogger>( + context, + logger, + windowManager, + mainExecutor, + accessibilityManager, + configurationController, + powerManager, + R.layout.chipbar, + MediaTttUtils.WINDOW_TITLE, + MediaTttUtils.WAKE_REASON, +) { + + private lateinit var parent: ChipbarRootView + + override val windowLayoutParams = commonWindowLayoutParams.apply { + gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) + } + + override fun start() {} + + override fun updateView( + newInfo: ChipbarInfo, + currentView: ViewGroup + ) { + // TODO(b/245610654): Adding logging here. + + // Detect falsing touches on the chip. + parent = currentView.requireViewById(R.id.chipbar_root_view) + parent.touchHandler = object : Gefingerpoken { + override fun onTouchEvent(ev: MotionEvent?): Boolean { + falsingCollector.onTouchEvent(ev) + return false + } + } + + // ---- Start icon ---- + val iconView = currentView.requireViewById<CachingIconView>(R.id.start_icon) + IconViewBinder.bind(newInfo.startIcon, iconView) + + // ---- Text ---- + val textView = currentView.requireViewById<TextView>(R.id.text) + TextViewBinder.bind(textView, newInfo.text) + + // ---- End item ---- + // Loading + currentView.requireViewById<View>(R.id.loading).visibility = + (newInfo.endItem == ChipbarEndItem.Loading).visibleIfTrue() + + // Error + currentView.requireViewById<View>(R.id.error).visibility = + (newInfo.endItem == ChipbarEndItem.Error).visibleIfTrue() + + // Button + val buttonView = currentView.requireViewById<TextView>(R.id.end_button) + if (newInfo.endItem is ChipbarEndItem.Button) { + TextViewBinder.bind(buttonView, newInfo.endItem.text) + + val onClickListener = View.OnClickListener { clickedView -> + if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener + newInfo.endItem.onClickListener.onClick(clickedView) + } + + buttonView.setOnClickListener(onClickListener) + buttonView.visibility = View.VISIBLE + } else { + buttonView.visibility = View.GONE + } + + // ---- Overall accessibility ---- + currentView.requireViewById<ViewGroup>( + R.id.chipbar_inner + ).contentDescription = + "${newInfo.startIcon.contentDescription.loadContentDescription(context)} " + + "${newInfo.text.loadText(context)}" + } + + override fun animateViewIn(view: ViewGroup) { + val chipInnerView = view.requireViewById<ViewGroup>(R.id.chipbar_inner) + ViewHierarchyAnimator.animateAddition( + chipInnerView, + ViewHierarchyAnimator.Hotspot.TOP, + Interpolators.EMPHASIZED_DECELERATE, + duration = ANIMATION_DURATION, + includeMargins = true, + includeFadeIn = true, + // We can only request focus once the animation finishes. + onAnimationEnd = { chipInnerView.requestAccessibilityFocus() }, + ) + } + + override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { + ViewHierarchyAnimator.animateRemoval( + view.requireViewById<ViewGroup>(R.id.chipbar_inner), + ViewHierarchyAnimator.Hotspot.TOP, + Interpolators.EMPHASIZED_ACCELERATE, + ANIMATION_DURATION, + includeMargins = true, + onAnimationEnd, + ) + } + + override fun getTouchableRegion(view: View, outRect: Rect) { + viewUtil.setRectToViewWindowLocation(view, outRect) + } + + private fun Boolean.visibleIfTrue(): Int { + return if (this) { + View.VISIBLE + } else { + View.GONE + } + } +} + +const val SENDER_TAG = "MediaTapToTransferSender" +private const val ANIMATION_DURATION = 500L diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt new file mode 100644 index 000000000000..211a66387966 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.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.temporarydisplay.chipbar + +import android.view.View +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.temporarydisplay.TemporaryViewInfo + +/** + * A container for all the state needed to display a chipbar via [ChipbarCoordinator]. + * + * @property startIcon the icon to display at the start of the chipbar (on the left in LTR locales; + * on the right in RTL locales). + * @property text the text to display. + * @property endItem an optional end item to display at the end of the chipbar (on the right in LTR + * locales; on the left in RTL locales). + */ +data class ChipbarInfo( + val startIcon: Icon, + val text: Text, + val endItem: ChipbarEndItem?, +) : TemporaryViewInfo + +/** The possible items to display at the end of the chipbar. */ +sealed class ChipbarEndItem { + /** A loading icon should be displayed. */ + object Loading : ChipbarEndItem() + + /** An error icon should be displayed. */ + object Error : ChipbarEndItem() + + /** + * A button with the provided [text] and [onClickListener] functionality should be displayed. + */ + data class Button(val text: Text, val onClickListener: View.OnClickListener) : ChipbarEndItem() + + // TODO(b/245610654): Add support for a generic icon. +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt index 3373159fba4e..edec420bc408 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.media.taptotransfer.sender +package com.android.systemui.temporarydisplay.chipbar import android.content.Context import android.util.AttributeSet @@ -22,8 +22,8 @@ import android.view.MotionEvent import android.widget.FrameLayout import com.android.systemui.Gefingerpoken -/** A simple subclass that allows for observing touch events on chip. */ -class MediaTttChipRootView( +/** A simple subclass that allows for observing touch events on chipbar. */ +class ChipbarRootView( context: Context, attrs: AttributeSet? ) : FrameLayout(context, attrs) { diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index a345d99b47c6..3ecb15b9d79c 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -79,6 +79,7 @@ import org.json.JSONException; import org.json.JSONObject; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -100,7 +101,7 @@ import javax.inject.Inject; * associated work profiles */ @SysUISingleton -public class ThemeOverlayController extends CoreStartable implements Dumpable { +public class ThemeOverlayController implements CoreStartable, Dumpable { protected static final String TAG = "ThemeOverlayController"; private static final boolean DEBUG = true; @@ -114,6 +115,8 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { private final SecureSettings mSecureSettings; private final Executor mMainExecutor; private final Handler mBgHandler; + private final boolean mIsMonochromaticEnabled; + private final Context mContext; private final boolean mIsMonetEnabled; private final UserTracker mUserTracker; private final DeviceProvisionedController mDeviceProvisionedController; @@ -361,8 +364,8 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { UserManager userManager, DeviceProvisionedController deviceProvisionedController, UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags, @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) { - super(context); - + mContext = context; + mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEMES); mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET); mDeviceProvisionedController = deviceProvisionedController; mBroadcastDispatcher = broadcastDispatcher; @@ -665,8 +668,13 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { // Allow-list of Style objects that can be created from a setting string, i.e. can be // used as a system-wide theme. // - Content intentionally excluded, intended for media player, not system-wide - List<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT, - Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT); + List<Style> validStyles = new ArrayList<>(Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, + Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT)); + + if (mIsMonochromaticEnabled) { + validStyles.add(Style.MONOCHROMATIC); + } + Style style = mThemeStyle; final String overlayPackageJson = mSecureSettings.getStringForUser( Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt index 51541bd3032e..fda511433143 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt @@ -16,11 +16,11 @@ package com.android.systemui.toast -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogMessage import com.android.systemui.log.dagger.ToastLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogMessage import javax.inject.Inject private const val TAG = "ToastLog" diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java index 9eb34a42a0a9..ed14c8ad150c 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -50,13 +50,14 @@ import javax.inject.Inject; * Controls display of text toasts. */ @SysUISingleton -public class ToastUI extends CoreStartable implements CommandQueue.Callbacks { +public class ToastUI implements CoreStartable, CommandQueue.Callbacks { // values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds private static final int TOAST_SHORT_TIME = 2000; // 2 seconds private static final String TAG = "ToastUI"; + private final Context mContext; private final CommandQueue mCommandQueue; private final INotificationManager mNotificationManager; private final IAccessibilityManager mIAccessibilityManager; @@ -90,7 +91,7 @@ public class ToastUI extends CoreStartable implements CommandQueue.Callbacks { @Nullable IAccessibilityManager accessibilityManager, ToastFactory toastFactory, ToastLogger toastLogger ) { - super(context); + mContext = context; mCommandQueue = commandQueue; mNotificationManager = notificationManager; mIAccessibilityManager = accessibilityManager; @@ -179,7 +180,7 @@ public class ToastUI extends CoreStartable implements CommandQueue.Callbacks { } @Override - protected void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(Configuration newConfig) { if (newConfig.orientation != mOrientation) { mOrientation = newConfig.orientation; if (mToast != null) { diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index 3ce5ca3d0301..10a09dd169e8 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -203,9 +203,9 @@ public abstract class TvSystemUIModule { @Provides @SysUISingleton - static TvNotificationHandler provideTvNotificationHandler(Context context, + static TvNotificationHandler provideTvNotificationHandler( NotificationListener notificationListener) { - return new TvNotificationHandler(context, notificationListener); + return new TvNotificationHandler(notificationListener); } /** diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index fc20ac241e38..6ed3a093c8e3 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -26,8 +26,6 @@ import android.os.Trace import android.view.Choreographer import android.view.Display import android.view.DisplayInfo -import android.view.IRotationWatcher -import android.view.IWindowManager import android.view.Surface import android.view.SurfaceControl import android.view.SurfaceControlViewHost @@ -40,6 +38,7 @@ import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.LinearLightRevealEffect import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.util.traceSection import com.android.wm.shell.displayareahelper.DisplayAreaHelper import java.util.Optional @@ -58,7 +57,7 @@ constructor( private val displayAreaHelper: Optional<DisplayAreaHelper>, @Main private val executor: Executor, @UiBackground private val backgroundExecutor: Executor, - private val windowManagerInterface: IWindowManager + private val rotationChangeProvider: RotationChangeProvider, ) { private val transitionListener = TransitionListener() @@ -78,7 +77,7 @@ constructor( fun init() { deviceStateManager.registerCallback(executor, FoldListener()) unfoldTransitionProgressProvider.addCallback(transitionListener) - windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId) + rotationChangeProvider.addCallback(rotationWatcher) val containerBuilder = SurfaceControl.Builder(SurfaceSession()) @@ -86,7 +85,9 @@ constructor( .setName("unfold-overlay-container") displayAreaHelper.get().attachToRootDisplayArea( - Display.DEFAULT_DISPLAY, containerBuilder) { builder -> + Display.DEFAULT_DISPLAY, + containerBuilder + ) { builder -> executor.execute { overlayContainer = builder.build() @@ -244,8 +245,8 @@ constructor( } } - private inner class RotationWatcher : IRotationWatcher.Stub() { - override fun onRotationChanged(newRotation: Int) = + private inner class RotationWatcher : RotationChangeProvider.RotationListener { + override fun onRotationChanged(newRotation: Int) { traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") { if (currentRotation != newRotation) { currentRotation = newRotation @@ -253,6 +254,7 @@ constructor( root?.relayout(getLayoutParams()) } } + } } private inner class FoldListener : diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index eea6ac0e72e9..59ad24a3e7bb 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -17,11 +17,11 @@ package com.android.systemui.unfold import android.content.Context -import android.view.IWindowManager import com.android.systemui.keyguard.LifecycleScreenStatusProvider import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.system.SystemUnfoldSharedModule import com.android.systemui.unfold.updates.FoldStateProvider +import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider @@ -65,11 +65,11 @@ class UnfoldTransitionModule { @Singleton fun provideNaturalRotationProgressProvider( context: Context, - windowManager: IWindowManager, + rotationChangeProvider: RotationChangeProvider, unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> ): Optional<NaturalRotationUnfoldProgressProvider> = unfoldTransitionProgressProvider.map { provider -> - NaturalRotationUnfoldProgressProvider(context, windowManager, provider) + NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, provider) } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 4dc78f9ec8a6..bf706735d531 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -21,7 +21,6 @@ import android.app.Notification; import android.app.Notification.Action; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -56,11 +55,12 @@ import javax.inject.Inject; /** */ @SysUISingleton -public class StorageNotification extends CoreStartable { +public class StorageNotification implements CoreStartable { private static final String TAG = "StorageNotification"; private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME"; private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD"; + private final Context mContext; // TODO: delay some notifications to avoid bumpy fast operations @@ -69,7 +69,7 @@ public class StorageNotification extends CoreStartable { @Inject public StorageNotification(Context context) { - super(context); + mContext = context; } private static class MoveInfo { diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index 108ab43977e9..7f1195b78c77 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -16,426 +16,41 @@ package com.android.systemui.user -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable -import android.graphics.drawable.GradientDrawable -import android.graphics.drawable.LayerDrawable import android.os.Bundle -import android.os.UserManager -import android.provider.Settings -import android.util.Log -import android.view.LayoutInflater -import android.view.MotionEvent import android.view.View -import android.view.ViewGroup -import android.widget.AdapterView -import android.widget.ArrayAdapter -import android.widget.ImageView -import android.widget.TextView -import android.window.OnBackInvokedCallback -import android.window.OnBackInvokedDispatcher import androidx.activity.ComponentActivity -import androidx.constraintlayout.helper.widget.Flow import androidx.lifecycle.ViewModelProvider -import com.android.internal.annotations.VisibleForTesting -import com.android.internal.util.UserIcons -import com.android.settingslib.Utils -import com.android.systemui.Gefingerpoken import com.android.systemui.R -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.plugins.FalsingManager.LOW_PENALTY -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter -import com.android.systemui.statusbar.policy.UserSwitcherController -import com.android.systemui.user.data.source.UserRecord import com.android.systemui.user.ui.binder.UserSwitcherViewBinder import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel import dagger.Lazy import javax.inject.Inject -import kotlin.math.ceil - -private const val USER_VIEW = "user_view" /** Support a fullscreen user switcher */ open class UserSwitcherActivity @Inject constructor( - private val userSwitcherController: UserSwitcherController, - private val broadcastDispatcher: BroadcastDispatcher, private val falsingCollector: FalsingCollector, - private val falsingManager: FalsingManager, - private val userManager: UserManager, - private val userTracker: UserTracker, - private val flags: FeatureFlags, private val viewModelFactory: Lazy<UserSwitcherViewModel.Factory>, ) : ComponentActivity() { - private lateinit var parent: UserSwitcherRootView - private lateinit var broadcastReceiver: BroadcastReceiver - private var popupMenu: UserSwitcherPopupMenu? = null - private lateinit var addButton: View - private var addUserRecords = mutableListOf<UserRecord>() - private val onBackCallback = OnBackInvokedCallback { finish() } - private val userSwitchedCallback: UserTracker.Callback = - object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { - finish() - } - } - // When the add users options become available, insert another option to manage users - private val manageUserRecord = - UserRecord( - null /* info */, - null /* picture */, - false /* isGuest */, - false /* isCurrent */, - false /* isAddUser */, - false /* isRestricted */, - false /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - - private val adapter: UserAdapter by lazy { UserAdapter() } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - createActivity() - } - - @VisibleForTesting - fun createActivity() { setContentView(R.layout.user_switcher_fullscreen) window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) - if (isUsingModernArchitecture()) { - Log.d(TAG, "Using modern architecture.") - val viewModel = - ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java] - UserSwitcherViewBinder.bind( - view = requireViewById(R.id.user_switcher_root), - viewModel = viewModel, - lifecycleOwner = this, - layoutInflater = layoutInflater, - falsingCollector = falsingCollector, - onFinish = this::finish, - ) - return - } else { - Log.d(TAG, "Not using modern architecture.") - } - - parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root) - - parent.touchHandler = - object : Gefingerpoken { - override fun onTouchEvent(ev: MotionEvent?): Boolean { - falsingCollector.onTouchEvent(ev) - return false - } - } - - requireViewById<View>(R.id.cancel).apply { setOnClickListener { _ -> finish() } } - - addButton = - requireViewById<View>(R.id.add).apply { setOnClickListener { _ -> showPopupMenu() } } - - onBackInvokedDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, - onBackCallback + val viewModel = + ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java] + UserSwitcherViewBinder.bind( + view = requireViewById(R.id.user_switcher_root), + viewModel = viewModel, + lifecycleOwner = this, + layoutInflater = layoutInflater, + falsingCollector = falsingCollector, + onFinish = this::finish, ) - - userSwitcherController.init(parent) - initBroadcastReceiver() - - parent.post { buildUserViews() } - userTracker.addCallback(userSwitchedCallback, mainExecutor) - } - - private fun showPopupMenu() { - val items = mutableListOf<UserRecord>() - addUserRecords.forEach { items.add(it) } - - var popupMenuAdapter = - ItemAdapter( - this, - R.layout.user_switcher_fullscreen_popup_item, - layoutInflater, - { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, - { item: UserRecord -> - adapter.findUserIcon(item, true).mutate().apply { - setTint( - resources.getColor( - R.color.user_switcher_fullscreen_popup_item_tint, - getTheme() - ) - ) - } - } - ) - popupMenuAdapter.addAll(items) - - popupMenu = - UserSwitcherPopupMenu(this).apply { - setAnchorView(addButton) - setAdapter(popupMenuAdapter) - setOnItemClickListener { parent: AdapterView<*>, view: View, pos: Int, id: Long -> - if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) { - return@setOnItemClickListener - } - // -1 for the header - val item = popupMenuAdapter.getItem(pos - 1) - if (item == manageUserRecord) { - val i = Intent().setAction(Settings.ACTION_USER_SETTINGS) - this@UserSwitcherActivity.startActivity(i) - } else { - adapter.onUserListItemClicked(item) - } - - dismiss() - popupMenu = null - - if (!item.isAddUser) { - this@UserSwitcherActivity.finish() - } - } - - show() - } - } - - private fun buildUserViews() { - var count = 0 - var start = 0 - for (i in 0 until parent.getChildCount()) { - if (parent.getChildAt(i).getTag() == USER_VIEW) { - if (count == 0) start = i - count++ - } - } - parent.removeViews(start, count) - addUserRecords.clear() - val flow = requireViewById<Flow>(R.id.flow) - val totalWidth = parent.width - val userViewCount = adapter.getTotalUserViews() - val maxColumns = getMaxColumns(userViewCount) - val horizontalGap = - resources.getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap) - val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap - val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns - - flow.setMaxElementsWrap(maxColumns) - - for (i in 0 until adapter.getCount()) { - val item = adapter.getItem(i) - if (adapter.doNotRenderUserView(item)) { - addUserRecords.add(item) - } else { - val userView = adapter.getView(i, null, parent) - userView.requireViewById<ImageView>(R.id.user_switcher_icon).apply { - val lp = layoutParams - if (maxWidgetDiameter < lp.width) { - lp.width = maxWidgetDiameter - lp.height = maxWidgetDiameter - layoutParams = lp - } - } - - userView.setId(View.generateViewId()) - parent.addView(userView) - - // Views must have an id and a parent in order for Flow to lay them out - flow.addView(userView) - - userView.setOnClickListener { v -> - if (falsingManager.isFalseTap(LOW_PENALTY) || !v.isEnabled()) { - return@setOnClickListener - } - - if (!item.isCurrent || item.isGuest) { - adapter.onUserListItemClicked(item) - } - } - } - } - - if (!addUserRecords.isEmpty()) { - addUserRecords.add(manageUserRecord) - addButton.visibility = View.VISIBLE - } else { - addButton.visibility = View.GONE - } - } - - override fun onBackPressed() { - if (isUsingModernArchitecture()) { - return super.onBackPressed() - } - - finish() - } - - override fun onDestroy() { - super.onDestroy() - if (isUsingModernArchitecture()) { - return - } - destroyActivity() - } - - @VisibleForTesting - fun destroyActivity() { - onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackCallback) - broadcastDispatcher.unregisterReceiver(broadcastReceiver) - userTracker.removeCallback(userSwitchedCallback) - } - - private fun initBroadcastReceiver() { - broadcastReceiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val action = intent.getAction() - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - finish() - } - } - } - - val filter = IntentFilter() - filter.addAction(Intent.ACTION_SCREEN_OFF) - broadcastDispatcher.registerReceiver(broadcastReceiver, filter) - } - - @VisibleForTesting - fun getMaxColumns(userCount: Int): Int { - return if (userCount < 5) 4 else ceil(userCount / 2.0).toInt() - } - - private fun isUsingModernArchitecture(): Boolean { - return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY) - } - - /** Provides views to populate the option menu. */ - private class ItemAdapter( - val parentContext: Context, - val resource: Int, - val layoutInflater: LayoutInflater, - val textGetter: (UserRecord) -> String, - val iconGetter: (UserRecord) -> Drawable - ) : ArrayAdapter<UserRecord>(parentContext, resource) { - - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val item = getItem(position) - val view = convertView ?: layoutInflater.inflate(resource, parent, false) - - view.requireViewById<ImageView>(R.id.icon).apply { setImageDrawable(iconGetter(item)) } - view.requireViewById<TextView>(R.id.text).apply { setText(textGetter(item)) } - - return view - } - } - - private inner class UserAdapter : BaseUserSwitcherAdapter(userSwitcherController) { - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val item = getItem(position) - var view = convertView as ViewGroup? - if (view == null) { - view = - layoutInflater.inflate(R.layout.user_switcher_fullscreen_item, parent, false) - as ViewGroup - } - (view.getChildAt(0) as ImageView).apply { setImageDrawable(getDrawable(item)) } - (view.getChildAt(1) as TextView).apply { setText(getName(getContext(), item)) } - - view.setEnabled(item.isSwitchToEnabled) - UserSwitcherController.setSelectableAlpha(view) - view.setTag(USER_VIEW) - return view - } - - override fun getName(context: Context, item: UserRecord, isTablet: Boolean): String { - return if (item == manageUserRecord) { - getString(R.string.manage_users) - } else { - super.getName(context, item, isTablet) - } - } - - fun findUserIcon(item: UserRecord, isTablet: Boolean = false): Drawable { - if (item == manageUserRecord) { - return getDrawable(R.drawable.ic_manage_users) - } - if (item.info == null) { - return getIconDrawable(this@UserSwitcherActivity, item, isTablet) - } - val userIcon = userManager.getUserIcon(item.info.id) - if (userIcon != null) { - return BitmapDrawable(userIcon) - } - return UserIcons.getDefaultUserIcon(resources, item.info.id, false) - } - - fun getTotalUserViews(): Int { - return users.count { item -> !doNotRenderUserView(item) } - } - - fun doNotRenderUserView(item: UserRecord): Boolean { - return item.isAddUser || item.isAddSupervisedUser || item.isGuest && item.info == null - } - - private fun getDrawable(item: UserRecord): Drawable { - var drawable = - if (item.isGuest) { - getDrawable(R.drawable.ic_account_circle) - } else { - findUserIcon(item) - } - drawable.mutate() - - if (!item.isCurrent && !item.isSwitchToEnabled) { - drawable.setTint( - resources.getColor( - R.color.kg_user_switcher_restricted_avatar_icon_color, - getTheme() - ) - ) - } - - val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() as LayerDrawable - if (item == userSwitcherController.currentUserRecord) { - (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply { - val stroke = - resources.getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width) - val color = - Utils.getColorAttrDefaultColor( - this@UserSwitcherActivity, - com.android.internal.R.attr.colorAccentPrimary - ) - - setStroke(stroke, color) - } - } - - ld.setDrawableByLayerId(R.id.user_avatar, drawable) - return ld - } - - override fun notifyDataSetChanged() { - super.notifyDataSetChanged() - buildUserViews() - } - } - - companion object { - private const val TAG = "UserSwitcherActivity" } } 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 919e699652bc..d768b6dc195a 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 @@ -321,6 +321,7 @@ constructor( return when { isAddUser -> false isAddSupervisedUser -> false + isManageUsers -> false isGuest -> info != null else -> true } @@ -346,6 +347,7 @@ constructor( isAddUser -> UserActionModel.ADD_USER isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER isGuest -> UserActionModel.ENTER_GUEST_MODE + isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT else -> error("Don't know how to convert to UserActionModel: $this") } } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt index 9370286d7ee7..d4fb5634bd1d 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt @@ -47,6 +47,9 @@ data class UserRecord( * If not disabled, this is `null`. */ @JvmField val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin? = null, + + /** Whether this record is to go to the Settings page to manage users. */ + @JvmField val isManageUsers: Boolean = false ) { /** Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. */ fun copyWithIsCurrent(isCurrent: Boolean): UserRecord { diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt index 1b4746a99f8f..dc004f3603a0 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt @@ -82,6 +82,15 @@ object UserActionsUtil { ) } + fun canManageUsers( + repository: UserRepository, + isUserSwitcherEnabled: Boolean, + isAddUsersFromLockScreenEnabled: Boolean, + ): Boolean { + return isUserSwitcherEnabled && + (repository.getSelectedUserInfo().isAdmin || isAddUsersFromLockScreenEnabled) + } + /** * Returns `true` if the current user is allowed to add users to the device; `false` otherwise. */ diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt index 142a328b2bc4..0d5c64b83e6e 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt @@ -102,6 +102,7 @@ constructor( interface UserCallback { /** Returns `true` if this callback can be cleaned-up. */ fun isEvictable(): Boolean = false + /** Notifies that the state of users on the device has changed. */ fun onUserStateChanged() } @@ -164,10 +165,11 @@ constructor( get() = if (isNewImpl) { combine( + repository.selectedUserInfo, repository.userInfos, repository.userSwitcherSettings, keyguardInteractor.isKeyguardShowing, - ) { userInfos, settings, isDeviceLocked -> + ) { _, userInfos, settings, isDeviceLocked -> buildList { val hasGuestUser = userInfos.any { it.isGuest } if ( @@ -183,35 +185,45 @@ constructor( add(UserActionModel.ENTER_GUEST_MODE) } - if (isDeviceLocked && !settings.isAddUsersFromLockscreen) { + if (!isDeviceLocked || settings.isAddUsersFromLockscreen) { // The device is locked and our setting to allow actions that add users // from the lock-screen is not enabled. The guest action from above is // always allowed, even when the device is locked, but the various "add // user" actions below are not. We can finish building the list here. - return@buildList - } - if ( - UserActionsUtil.canCreateUser( - manager, - repository, - settings.isUserSwitcherEnabled, - settings.isAddUsersFromLockscreen, - ) - ) { - add(UserActionModel.ADD_USER) + val canCreateUsers = + UserActionsUtil.canCreateUser( + manager, + repository, + settings.isUserSwitcherEnabled, + settings.isAddUsersFromLockscreen, + ) + + if (canCreateUsers) { + add(UserActionModel.ADD_USER) + } + + if ( + UserActionsUtil.canCreateSupervisedUser( + manager, + repository, + settings.isUserSwitcherEnabled, + settings.isAddUsersFromLockscreen, + supervisedUserPackageName, + ) + ) { + add(UserActionModel.ADD_SUPERVISED_USER) + } } if ( - UserActionsUtil.canCreateSupervisedUser( - manager, + UserActionsUtil.canManageUsers( repository, settings.isUserSwitcherEnabled, settings.isAddUsersFromLockscreen, - supervisedUserPackageName, ) ) { - add(UserActionModel.ADD_SUPERVISED_USER) + add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) } } } @@ -224,18 +236,7 @@ constructor( } .flatMapLatest { isActionable -> if (isActionable) { - repository.actions.map { actions -> - actions + - if (actions.isNotEmpty()) { - // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT - // because that's a user switcher specific action that is - // not known to the our data source or other features. - listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) - } else { - // If no actions, don't add the navigate action. - emptyList() - } - } + repository.actions } else { // If not actionable it means that we're not allowed to show actions // when @@ -264,7 +265,10 @@ constructor( toRecord( action = it, selectedUserId = selectedUserInfo.id, - isAddFromLockscreenEnabled = settings.isAddUsersFromLockscreen, + isRestricted = + it != UserActionModel.ENTER_GUEST_MODE && + it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT && + !settings.isAddUsersFromLockscreen, ) } ) @@ -482,12 +486,12 @@ constructor( .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) .setPackage(supervisedUserPackageName) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - /* dismissShade= */ false, + /* dismissShade= */ true, ) UserActionModel.NAVIGATE_TO_USER_MANAGEMENT -> activityStarter.startActivity( Intent(Settings.ACTION_USER_SETTINGS), - /* dismissShade= */ false, + /* dismissShade= */ true, ) } } else { @@ -575,20 +579,13 @@ constructor( private suspend fun toRecord( action: UserActionModel, selectedUserId: Int, - isAddFromLockscreenEnabled: Boolean, + isRestricted: Boolean, ): UserRecord { return LegacyUserDataHelper.createRecord( context = applicationContext, selectedUserId = selectedUserId, actionType = action, - isRestricted = - if (action == UserActionModel.ENTER_GUEST_MODE) { - // Entering guest mode is never restricted, so it's allowed to happen from the - // lockscreen even if the "add from lockscreen" system setting is off. - false - } else { - !isAddFromLockscreenEnabled - }, + isRestricted = isRestricted, isSwitchToEnabled = canSwitchUsers(selectedUserId) && // If the user is auto-created is must not be currently resetting. diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt index 137de1544b2d..03a7470a3fe6 100644 --- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt @@ -80,6 +80,7 @@ object LegacyUserDataHelper { context = context, selectedUserId = selectedUserId, ), + isManageUsers = actionType == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) } @@ -90,6 +91,7 @@ object LegacyUserDataHelper { record.isAddUser -> UserActionModel.ADD_USER record.isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER record.isGuest -> UserActionModel.ENTER_GUEST_MODE + record.isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT else -> error("Not a known action: $record") } } diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt index 15fdc352d864..e74232df3ac3 100644 --- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt @@ -22,7 +22,6 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import com.android.systemui.R import com.android.systemui.user.data.source.UserRecord -import kotlin.math.ceil /** * Defines utility functions for helping with legacy UI code for users. @@ -33,16 +32,6 @@ import kotlin.math.ceil */ object LegacyUserUiHelper { - /** Returns the maximum number of columns for user items in the user switcher. */ - fun getMaxUserSwitcherItemColumns(userCount: Int): Int { - // TODO(b/243844097): remove this once we remove the old user switcher implementation. - return if (userCount < 5) { - 4 - } else { - ceil(userCount / 2.0).toInt() - } - } - @JvmStatic @DrawableRes fun getUserSwitcherActionIconResourceId( @@ -50,6 +39,7 @@ object LegacyUserUiHelper { isGuest: Boolean, isAddSupervisedUser: Boolean, isTablet: Boolean = false, + isManageUsers: Boolean, ): Int { return if (isAddUser && isTablet) { R.drawable.ic_account_circle_filled @@ -59,6 +49,8 @@ object LegacyUserUiHelper { R.drawable.ic_account_circle } else if (isAddSupervisedUser) { R.drawable.ic_add_supervised_user + } else if (isManageUsers) { + R.drawable.ic_manage_users } else { R.drawable.ic_avatar_user } @@ -85,6 +77,7 @@ object LegacyUserUiHelper { isAddUser = record.isAddUser, isAddSupervisedUser = record.isAddSupervisedUser, isTablet = isTablet, + isManageUsers = record.isManageUsers, ) ) } @@ -114,8 +107,9 @@ object LegacyUserUiHelper { isAddUser: Boolean, isAddSupervisedUser: Boolean, isTablet: Boolean = false, + isManageUsers: Boolean, ): Int { - check(isGuest || isAddUser || isAddSupervisedUser) + check(isGuest || isAddUser || isAddSupervisedUser || isManageUsers) return when { isGuest && isGuestUserAutoCreated && isGuestUserResetting -> @@ -125,6 +119,7 @@ object LegacyUserUiHelper { isGuest -> com.android.internal.R.string.guest_name isAddUser -> com.android.settingslib.R.string.user_add_user isAddSupervisedUser -> R.string.add_user_supervised + isManageUsers -> R.string.manage_users else -> error("This should never happen!") } } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt index 6e7b5232d818..f7e19c0ca810 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt @@ -30,6 +30,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.plugins.FalsingManager import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.domain.model.ShowDialogRequestModel +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect @@ -41,19 +42,19 @@ import kotlinx.coroutines.launch class UserSwitcherDialogCoordinator @Inject constructor( - @Application private val context: Context, - @Application private val applicationScope: CoroutineScope, - private val falsingManager: FalsingManager, - private val broadcastSender: BroadcastSender, - private val dialogLaunchAnimator: DialogLaunchAnimator, - private val interactor: UserInteractor, - private val featureFlags: FeatureFlags, -) : CoreStartable(context) { + @Application private val context: Lazy<Context>, + @Application private val applicationScope: Lazy<CoroutineScope>, + private val falsingManager: Lazy<FalsingManager>, + private val broadcastSender: Lazy<BroadcastSender>, + private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>, + private val interactor: Lazy<UserInteractor>, + private val featureFlags: Lazy<FeatureFlags>, +) : CoreStartable { private var currentDialog: Dialog? = null override fun start() { - if (featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) { + if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) { return } @@ -62,8 +63,8 @@ constructor( } private fun startHandlingDialogShowRequests() { - applicationScope.launch { - interactor.dialogShowRequests.filterNotNull().collect { request -> + applicationScope.get().launch { + interactor.get().dialogShowRequests.filterNotNull().collect { request -> currentDialog?.let { if (it.isShowing) { it.cancel() @@ -74,48 +75,48 @@ constructor( when (request) { is ShowDialogRequestModel.ShowAddUserDialog -> AddUserDialog( - context = context, + context = context.get(), userHandle = request.userHandle, isKeyguardShowing = request.isKeyguardShowing, showEphemeralMessage = request.showEphemeralMessage, - falsingManager = falsingManager, - broadcastSender = broadcastSender, - dialogLaunchAnimator = dialogLaunchAnimator, + falsingManager = falsingManager.get(), + broadcastSender = broadcastSender.get(), + dialogLaunchAnimator = dialogLaunchAnimator.get(), ) is ShowDialogRequestModel.ShowUserCreationDialog -> UserCreatingDialog( - context, + context.get(), request.isGuest, ) is ShowDialogRequestModel.ShowExitGuestDialog -> ExitGuestDialog( - context = context, + context = context.get(), guestUserId = request.guestUserId, isGuestEphemeral = request.isGuestEphemeral, targetUserId = request.targetUserId, isKeyguardShowing = request.isKeyguardShowing, - falsingManager = falsingManager, - dialogLaunchAnimator = dialogLaunchAnimator, + falsingManager = falsingManager.get(), + dialogLaunchAnimator = dialogLaunchAnimator.get(), onExitGuestUserListener = request.onExitGuestUser, ) } currentDialog?.show() - interactor.onDialogShown() + interactor.get().onDialogShown() } } } private fun startHandlingDialogDismissRequests() { - applicationScope.launch { - interactor.dialogDismissRequests.filterNotNull().collect { + applicationScope.get().launch { + interactor.get().dialogDismissRequests.filterNotNull().collect { currentDialog?.let { if (it.isShowing) { it.cancel() } } - interactor.onDialogDismissed() + interactor.get().onDialogDismissed() } } } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt index 5b83df7b4a36..d857e85bac53 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt @@ -19,7 +19,6 @@ package com.android.systemui.user.ui.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import com.android.systemui.R import com.android.systemui.common.ui.drawable.CircularDrawable import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -30,6 +29,7 @@ import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper import com.android.systemui.user.shared.model.UserActionModel import com.android.systemui.user.shared.model.UserModel import javax.inject.Inject +import kotlin.math.ceil import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine @@ -52,8 +52,7 @@ private constructor( userInteractor.users.map { models -> models.map { user -> toViewModel(user) } } /** The maximum number of columns that the user selection grid should use. */ - val maximumUserColumns: Flow<Int> = - users.map { LegacyUserUiHelper.getMaxUserSwitcherItemColumns(it.size) } + val maximumUserColumns: Flow<Int> = users.map { getMaxUserSwitcherItemColumns(it.size) } private val _isMenuVisible = MutableStateFlow(false) /** @@ -63,17 +62,7 @@ private constructor( val isMenuVisible: Flow<Boolean> = _isMenuVisible /** The user action menu. */ val menu: Flow<List<UserActionViewModel>> = - userInteractor.actions.map { actions -> - if (isNewImpl && actions.isNotEmpty()) { - // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because that's a user - // switcher specific action that is not known to the our data source or other - // features. - actions + listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) - } else { - actions - } - .map { action -> toViewModel(action) } - } + userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } } /** Whether the button to open the user action menu is visible. */ val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() } @@ -118,6 +107,15 @@ private constructor( _isMenuVisible.value = false } + /** Returns the maximum number of columns for user items in the user switcher. */ + private fun getMaxUserSwitcherItemColumns(userCount: Int): Int { + return if (userCount < 5) { + 4 + } else { + ceil(userCount / 2.0).toInt() + } + } + private fun createFinishRequestedFlow(): Flow<Boolean> { var mostRecentSelectedUserId: Int? = null var mostRecentIsInteractive: Boolean? = null @@ -171,27 +169,23 @@ private constructor( return UserActionViewModel( viewKey = model.ordinal.toLong(), iconResourceId = - if (model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) { - R.drawable.ic_manage_users - } else { - LegacyUserUiHelper.getUserSwitcherActionIconResourceId( - isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER, - isAddUser = model == UserActionModel.ADD_USER, - isGuest = model == UserActionModel.ENTER_GUEST_MODE, - ) - }, + LegacyUserUiHelper.getUserSwitcherActionIconResourceId( + isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER, + isAddUser = model == UserActionModel.ADD_USER, + isGuest = model == UserActionModel.ENTER_GUEST_MODE, + isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + isTablet = true, + ), textResourceId = - if (model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) { - R.string.manage_users - } else { - LegacyUserUiHelper.getUserSwitcherActionTextResourceId( - isGuest = model == UserActionModel.ENTER_GUEST_MODE, - isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated, - isGuestUserResetting = guestUserInteractor.isGuestUserResetting, - isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER, - isAddUser = model == UserActionModel.ADD_USER, - ) - }, + LegacyUserUiHelper.getUserSwitcherActionTextResourceId( + isGuest = model == UserActionModel.ENTER_GUEST_MODE, + isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated, + isGuestUserResetting = guestUserInteractor.isGuestUserResetting, + isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER, + isAddUser = model == UserActionModel.ADD_USER, + isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + isTablet = true, + ), onClicked = { userInteractor.executeAction(action = model) // We don't finish because we want to show a dialog over the full-screen UI and diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index 53da213eb38e..2efeda932ff3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -32,7 +32,8 @@ import java.util.Arrays; import javax.inject.Inject; // NOT Singleton. Started per-user. -public class NotificationChannels extends CoreStartable { +/** */ +public class NotificationChannels implements CoreStartable { public static String ALERTS = "ALR"; public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP"; // Deprecated. Please use or create a more specific channel that users will better understand @@ -45,9 +46,11 @@ public class NotificationChannels extends CoreStartable { public static String INSTANT = "INS"; public static String SETUP = "STP"; + private final Context mContext; + @Inject public NotificationChannels(Context context) { - super(context); + mContext = context; } public static void createAll(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java index ecb365f43e3f..2c317dd391c0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java @@ -172,10 +172,14 @@ public abstract class Condition implements CallbackController<Condition.Callback return Boolean.TRUE.equals(mIsConditionMet); } - private boolean shouldLog() { + protected final boolean shouldLog() { return Log.isLoggable(mTag, Log.DEBUG); } + protected final String getTag() { + return mTag; + } + /** * Callback that receives updates about whether the condition has been fulfilled. */ diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java index 4824f6744c6e..cb430ba454f0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java @@ -117,6 +117,7 @@ public class Monitor { final SubscriptionState state = new SubscriptionState(subscription); mExecutor.execute(() -> { + if (shouldLog()) Log.d(mTag, "adding subscription"); mSubscriptions.put(token, state); // Add and associate conditions. @@ -143,7 +144,7 @@ public class Monitor { */ public void removeSubscription(@NotNull Subscription.Token token) { mExecutor.execute(() -> { - if (shouldLog()) Log.d(mTag, "removing callback"); + if (shouldLog()) Log.d(mTag, "removing subscription"); if (!mSubscriptions.containsKey(token)) { Log.e(mTag, "subscription not present:" + token); return; diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java index 619e50b47f13..a0a0372426ec 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java @@ -564,12 +564,13 @@ public class GarbageMonitor implements Dumpable { /** */ @SysUISingleton - public static class Service extends CoreStartable implements Dumpable { + public static class Service implements CoreStartable, Dumpable { + private final Context mContext; private final GarbageMonitor mGarbageMonitor; @Inject public Service(Context context, GarbageMonitor garbageMonitor) { - super(context); + mContext = context; mGarbageMonitor = garbageMonitor; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index 87fb2a692682..0b3521b048c4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -31,18 +31,19 @@ import java.io.PrintWriter; import javax.inject.Inject; @SysUISingleton -public class VolumeUI extends CoreStartable { +public class VolumeUI implements CoreStartable { private static final String TAG = "VolumeUI"; private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); private final Handler mHandler = new Handler(); private boolean mEnabled; + private final Context mContext; private VolumeDialogComponent mVolumeComponent; @Inject public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) { - super(context); + mContext = context; mVolumeComponent = volumeDialogComponent; } @@ -59,8 +60,7 @@ public class VolumeUI extends CoreStartable { } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); + public void onConfigurationChanged(Configuration newConfig) { if (!mEnabled) return; mVolumeComponent.onConfigurationChanged(newConfig); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 3472cb1c2a7d..fbc6a582da2e 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -89,8 +89,10 @@ import javax.inject.Inject; * -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces */ @SysUISingleton -public final class WMShell extends CoreStartable - implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> { +public final class WMShell implements + CoreStartable, + CommandQueue.Callbacks, + ProtoTraceable<SystemUiTraceProto> { private static final String TAG = WMShell.class.getName(); private static final int INVALID_SYSUI_STATE_MASK = SYSUI_STATE_DIALOG_SHOWING @@ -102,6 +104,7 @@ public final class WMShell extends CoreStartable | SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED; + private final Context mContext; // Shell interfaces private final ShellInterface mShell; private final Optional<Pip> mPipOptional; @@ -163,7 +166,8 @@ public final class WMShell extends CoreStartable private WakefulnessLifecycle.Observer mWakefulnessObserver; @Inject - public WMShell(Context context, + public WMShell( + Context context, ShellInterface shell, Optional<Pip> pipOptional, Optional<SplitScreen> splitScreenOptional, @@ -179,7 +183,7 @@ public final class WMShell extends CoreStartable WakefulnessLifecycle wakefulnessLifecycle, UserTracker userTracker, @Main Executor sysUiMainExecutor) { - super(context); + mContext = context; mShell = shell; mCommandQueue = commandQueue; mConfigurationController = configurationController; diff --git a/packages/SystemUI/tests/Android.bp b/packages/SystemUI/tests/Android.bp new file mode 100644 index 000000000000..3c418ed49adc --- /dev/null +++ b/packages/SystemUI/tests/Android.bp @@ -0,0 +1,50 @@ +// +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_test { + name: "SystemUITests", + + dxflags: ["--multi-dex"], + platform_apis: true, + test_suites: ["device-tests"], + static_libs: ["SystemUI-tests"], + compile_multilib: "both", + + jni_libs: [ + "libdexmakerjvmtiagent", + "libmultiplejvmtiagentsinterferenceagent", + "libstaticjvmtiagent", + ], + libs: [ + "android.test.runner", + "telephony-common", + "android.test.base", + ], + aaptflags: [ + "--extra-packages com.android.systemui", + ], + + // sign this with platform cert, so this test is allowed to inject key events into + // UI it doesn't own. This is necessary to allow screenshots to be taken + certificate: "platform", + + additional_manifests: ["AndroidManifest.xml"], + manifest: "AndroidManifest-base.xml", +} diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk deleted file mode 100644 index ff5165d4e7cf..000000000000 --- a/packages/SystemUI/tests/Android.mk +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright (C) 2011 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_USE_AAPT2 := true -LOCAL_MODULE_TAGS := tests - -LOCAL_JACK_FLAGS := --multi-dex native -LOCAL_DX_FLAGS := --multi-dex - -LOCAL_PACKAGE_NAME := SystemUITests -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE -LOCAL_PRIVATE_PLATFORM_APIS := true -LOCAL_COMPATIBILITY_SUITE := device-tests - -LOCAL_STATIC_ANDROID_LIBRARIES := \ - SystemUI-tests - -LOCAL_MULTILIB := both - -LOCAL_JNI_SHARED_LIBRARIES := \ - libdexmakerjvmtiagent \ - libmultiplejvmtiagentsinterferenceagent \ - libstaticjvmtiagent - -LOCAL_JAVA_LIBRARIES := \ - android.test.runner \ - telephony-common \ - android.test.base \ - -LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui - -# sign this with platform cert, so this test is allowed to inject key events into -# UI it doesn't own. This is necessary to allow screenshots to be taken -LOCAL_CERTIFICATE := platform - -LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest.xml -LOCAL_MANIFEST_FILE := AndroidManifest-base.xml - -# Provide jack a list of classes to exclude from code coverage. -# This is needed because the SystemUITests compile SystemUI source directly, rather than using -# LOCAL_INSTRUMENTATION_FOR := SystemUI. -# -# We want to exclude the test classes from code coverage measurements, but they share the same -# package as the rest of SystemUI so they can't be easily filtered by package name. -# -# Generate a comma separated list of patterns based on the test source files under src/ -# SystemUI classes are in ../src/ so they won't be excluded. -# Example: -# Input files: src/com/android/systemui/Test.java src/com/android/systemui/AnotherTest.java -# Generated exclude list: com.android.systemui.Test*,com.android.systemui.AnotherTest* - -# Filter all src files under src/ to just java files -local_java_files := $(filter %.java,$(call all-java-files-under, src)) -# Transform java file names into full class names. -# This only works if the class name matches the file name and the directory structure -# matches the package. -local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files))) -local_comma := , -local_empty := -local_space := $(local_empty) $(local_empty) -# Convert class name list to jacoco exclude list -# This appends a * to all classes and replace the space separators with commas. -jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes))) - -LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.* -LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude) - -ifeq ($(EXCLUDE_SYSTEMUI_TESTS),) - include $(BUILD_PACKAGE) -endif - -# Reset variables -local_java_files := -local_classes := -local_comma := -local_space := -jacoco_exclude := diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt new file mode 100644 index 000000000000..9d6aff219148 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard + +import android.content.Context +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.util.AttributeSet +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class BouncerKeyguardMessageAreaTest : SysuiTestCase() { + class FakeBouncerKeyguardMessageArea(context: Context, attrs: AttributeSet?) : + BouncerKeyguardMessageArea(context, attrs) { + override val SHOW_DURATION_MILLIS = 0L + override val HIDE_DURATION_MILLIS = 0L + } + lateinit var underTest: BouncerKeyguardMessageArea + + @Before + fun setup() { + underTest = FakeBouncerKeyguardMessageArea(context, null) + } + + @Test + fun testSetSameMessage() { + val underTestSpy = spy(underTest) + underTestSpy.setMessage("abc") + underTestSpy.setMessage("abc") + verify(underTestSpy, times(1)).text = "abc" + } + + @Test + fun testSetDifferentMessage() { + underTest.setMessage("abc") + underTest.setMessage("def") + assertThat(underTest.text).isEqualTo("def") + } + + @Test + fun testSetNullMessage() { + underTest.setMessage(null) + assertThat(underTest.text).isEqualTo("") + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 8a2c35410586..03efd06d0e5e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -17,17 +17,21 @@ package com.android.keyguard import android.content.BroadcastReceiver import android.testing.AndroidTestingRunner +import android.view.View import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.flags.FeatureFlags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockEvents import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceEvents -import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.mockito.any @@ -37,6 +41,9 @@ 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 import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule @@ -57,7 +64,7 @@ import org.mockito.junit.MockitoJUnit class ClockEventControllerTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() - @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var keyguardInteractor: KeyguardInteractor @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher @Mock private lateinit var batteryController: BatteryController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @@ -72,8 +79,11 @@ class ClockEventControllerTest : SysuiTestCase() { @Mock private lateinit var largeClockController: ClockFaceController @Mock private lateinit var smallClockEvents: ClockFaceEvents @Mock private lateinit var largeClockEvents: ClockFaceEvents + @Mock private lateinit var parentView: View + @Mock private lateinit var transitionRepository: KeyguardTransitionRepository + private lateinit var repository: FakeKeyguardRepository - private lateinit var clockEventController: ClockEventController + private lateinit var underTest: ClockEventController @Before fun setUp() { @@ -86,8 +96,11 @@ class ClockEventControllerTest : SysuiTestCase() { whenever(clock.events).thenReturn(events) whenever(clock.animations).thenReturn(animations) - clockEventController = ClockEventController( - statusBarStateController, + repository = FakeKeyguardRepository() + + underTest = ClockEventController( + KeyguardInteractor(repository = repository), + KeyguardTransitionInteractor(repository = transitionRepository), broadcastDispatcher, batteryController, keyguardUpdateMonitor, @@ -98,31 +111,33 @@ class ClockEventControllerTest : SysuiTestCase() { bgExecutor, featureFlags ) + underTest.clock = clock + + runBlocking(IMMEDIATE) { + underTest.registerListeners(parentView) + + repository.setDozing(true) + repository.setDozeAmount(1f) + } } @Test fun clockSet_validateInitialization() { - clockEventController.clock = clock - verify(clock).initialize(any(), anyFloat(), anyFloat()) } @Test fun clockUnset_validateState() { - clockEventController.clock = clock - clockEventController.clock = null + underTest.clock = null - assertEquals(clockEventController.clock, null) + assertEquals(underTest.clock, null) } @Test - fun themeChanged_verifyClockPaletteUpdated() { - clockEventController.clock = clock + fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) { verify(smallClockEvents).onRegionDarknessChanged(anyBoolean()) verify(largeClockEvents).onRegionDarknessChanged(anyBoolean()) - clockEventController.registerListeners() - val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() verify(configurationController).addCallback(capture(captor)) captor.value.onThemeChanged() @@ -131,13 +146,10 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun fontChanged_verifyFontSizeUpdated() { - clockEventController.clock = clock + fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) { verify(smallClockEvents).onRegionDarknessChanged(anyBoolean()) verify(largeClockEvents).onRegionDarknessChanged(anyBoolean()) - clockEventController.registerListeners() - val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() verify(configurationController).addCallback(capture(captor)) captor.value.onDensityOrFontScaleChanged() @@ -146,10 +158,7 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() { - clockEventController.clock = clock - clockEventController.registerListeners() - + fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) { val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() verify(batteryController).addCallback(capture(batteryCaptor)) val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>() @@ -161,26 +170,21 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() { - clockEventController.clock = clock - clockEventController.registerListeners() - - val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() - verify(batteryController).addCallback(capture(batteryCaptor)) - val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>() - verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) - keyguardCaptor.value.onKeyguardVisibilityChanged(true) - batteryCaptor.value.onBatteryLevelChanged(10, false, true) - batteryCaptor.value.onBatteryLevelChanged(10, false, true) - - verify(animations, times(1)).charge() - } + fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() = + runBlocking(IMMEDIATE) { + val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() + verify(batteryController).addCallback(capture(batteryCaptor)) + val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) + keyguardCaptor.value.onKeyguardVisibilityChanged(true) + batteryCaptor.value.onBatteryLevelChanged(10, false, true) + batteryCaptor.value.onBatteryLevelChanged(10, false, true) + + verify(animations, times(1)).charge() + } @Test - fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() { - clockEventController.clock = clock - clockEventController.registerListeners() - + fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) { val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() verify(batteryController).addCallback(capture(batteryCaptor)) val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>() @@ -192,25 +196,20 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() { - clockEventController.clock = clock - clockEventController.registerListeners() - - val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() - verify(batteryController).addCallback(capture(batteryCaptor)) - val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>() - verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) - keyguardCaptor.value.onKeyguardVisibilityChanged(true) - batteryCaptor.value.onBatteryLevelChanged(10, false, false) - - verify(animations, never()).charge() - } + fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() = + runBlocking(IMMEDIATE) { + val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() + verify(batteryController).addCallback(capture(batteryCaptor)) + val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) + keyguardCaptor.value.onKeyguardVisibilityChanged(true) + batteryCaptor.value.onBatteryLevelChanged(10, false, false) + + verify(animations, never()).charge() + } @Test - fun localeCallback_verifyClockNotified() { - clockEventController.clock = clock - clockEventController.registerListeners() - + fun localeCallback_verifyClockNotified() = runBlocking(IMMEDIATE) { val captor = argumentCaptor<BroadcastReceiver>() verify(broadcastDispatcher).registerReceiver( capture(captor), any(), eq(null), eq(null), anyInt(), eq(null) @@ -221,10 +220,7 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun keyguardCallback_visibilityChanged_clockDozeCalled() { - clockEventController.clock = clock - clockEventController.registerListeners() - + fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) { val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() verify(keyguardUpdateMonitor).registerCallback(capture(captor)) @@ -236,10 +232,7 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun keyguardCallback_timeFormat_clockNotified() { - clockEventController.clock = clock - clockEventController.registerListeners() - + fun keyguardCallback_timeFormat_clockNotified() = runBlocking(IMMEDIATE) { val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() verify(keyguardUpdateMonitor).registerCallback(capture(captor)) captor.value.onTimeFormatChanged("12h") @@ -248,11 +241,8 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun keyguardCallback_timezoneChanged_clockNotified() { + fun keyguardCallback_timezoneChanged_clockNotified() = runBlocking(IMMEDIATE) { val mockTimeZone = mock<TimeZone>() - clockEventController.clock = clock - clockEventController.registerListeners() - val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() verify(keyguardUpdateMonitor).registerCallback(capture(captor)) captor.value.onTimeZoneChanged(mockTimeZone) @@ -261,10 +251,7 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun keyguardCallback_userSwitched_clockNotified() { - clockEventController.clock = clock - clockEventController.registerListeners() - + fun keyguardCallback_userSwitched_clockNotified() = runBlocking(IMMEDIATE) { val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() verify(keyguardUpdateMonitor).registerCallback(capture(captor)) captor.value.onUserSwitchComplete(10) @@ -273,25 +260,27 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun keyguardCallback_verifyKeyguardChanged() { - clockEventController.clock = clock - clockEventController.registerListeners() + fun keyguardCallback_verifyKeyguardChanged() = runBlocking(IMMEDIATE) { + val job = underTest.listenForDozeAmount(this) + repository.setDozeAmount(0.4f) - val captor = argumentCaptor<StatusBarStateController.StateListener>() - verify(statusBarStateController).addCallback(capture(captor)) - captor.value.onDozeAmountChanged(0.4f, 0.6f) + yield() verify(animations).doze(0.4f) + + job.cancel() } @Test - fun unregisterListeners_validate() { - clockEventController.clock = clock - clockEventController.unregisterListeners() + fun unregisterListeners_validate() = runBlocking(IMMEDIATE) { + underTest.unregisterListeners() verify(broadcastDispatcher).unregisterReceiver(any()) verify(configurationController).removeCallback(any()) verify(batteryController).removeCallback(any()) verify(keyguardUpdateMonitor).removeCallback(any()) - verify(statusBarStateController).removeCallback(any()) + } + + companion object { + private val IMMEDIATE = Dispatchers.Main.immediate } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt index aa671d1e3790..91b544b8265c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt @@ -17,7 +17,6 @@ package com.android.keyguard import android.hardware.biometrics.BiometricSourceType -import org.mockito.Mockito.verify import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId @@ -30,9 +29,10 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.Captor import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -63,7 +63,6 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker) whenever(sessionTracker.getSessionId(anyInt())).thenReturn(sessionId) keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger( - mContext, uiEventLogger, keyguardUpdateMonitor, sessionTracker) @@ -195,4 +194,4 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallbackCaptor.capture()) updateMonitorCallback = updateMonitorCallbackCaptor.value } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 400caa3a352a..bb03a47e025c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -265,6 +265,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { private void verifyAttachment(VerificationMode times) { verify(mClockRegistry, times).registerClockChangeListener( any(ClockRegistry.ClockChangeListener.class)); - verify(mClockEventController, times).registerListeners(); + verify(mClockEventController, times).registerListeners(mView); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java index 69524e5a4537..5d2b0ca4e7ea 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java @@ -17,13 +17,11 @@ package com.android.keyguard; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; -import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -92,19 +90,4 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase { mMessageAreaController.setIsVisible(true); verify(mKeyguardMessageArea).setIsVisible(true); } - - @Test - public void testSetMessageIfEmpty_empty() { - mMessageAreaController.setMessage(""); - mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin); - verify(mKeyguardMessageArea).setMessage(R.string.keyguard_enter_your_pin); - } - - @Test - public void testSetMessageIfEmpty_notEmpty() { - mMessageAreaController.setMessage("abc"); - mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin); - verify(mKeyguardMessageArea, never()).setMessage(getContext() - .getResources().getText(R.string.keyguard_enter_your_pin)); - } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index b89dbd98968a..b369098cafc0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -114,9 +114,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { } @Test - fun onResume_testSetInitialText() { - keyguardPasswordViewController.onResume(KeyguardSecurityView.SCREEN_ON) - verify(mKeyguardMessageAreaController) - .setMessageIfEmpty(R.string.keyguard_enter_your_password) + fun startAppearAnimation() { + keyguardPasswordViewController.startAppearAnimation() + verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 3262a77b7711..9eff70487c74 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -100,16 +100,16 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { } @Test - fun onPause_clearsTextField() { + fun onPause_resetsText() { mKeyguardPatternViewController.init() mKeyguardPatternViewController.onPause() - verify(mKeyguardMessageAreaController).setMessage("") + verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern) } + @Test - fun onResume_setInitialText() { - mKeyguardPatternViewController.onResume(KeyguardSecurityView.SCREEN_ON) - verify(mKeyguardMessageAreaController) - .setMessageIfEmpty(R.string.keyguard_enter_your_pattern) + fun startAppearAnimation() { + mKeyguardPatternViewController.startAppearAnimation() + verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index 97d556b04aa4..ce1101f389c0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -113,11 +113,4 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON); verify(mPasswordEntry).requestFocus(); } - - @Test - public void onResume_setInitialText() { - mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON); - verify(mKeyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin); - } } - diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 9e5bfe53ea05..d9efdeaea04c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -98,6 +98,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Test fun startAppearAnimation() { pinViewController.startAppearAnimation() - verify(keyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin) + verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index c6ebaa8bb46c..48e82397e826 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -221,15 +221,17 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { public void onResourcesUpdate_callsThroughOnRotationChange() { // Rotation is the same, shouldn't cause an update mKeyguardSecurityContainerController.updateResources(); - verify(mView, never()).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); // Update rotation. Should trigger update mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; mKeyguardSecurityContainerController.updateResources(); - verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); } private void touchDown() { @@ -263,8 +265,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); } @Test @@ -275,8 +278,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); } @Test @@ -285,8 +289,26 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { setupGetSecurityView(); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); - verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); + } + + @Test + public void addUserSwitcherCallback() { + ArgumentCaptor<KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback> + captor = ArgumentCaptor.forClass( + KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class); + + setupGetSecurityView(); + + mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); + verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class), + any(UserSwitcherController.class), + captor.capture()); + captor.getValue().showUnlockToContinueMessage(); + verify(mKeyguardPasswordViewControllerMock).showMessage( + getContext().getString(R.string.keyguard_unlock_to_continue), null); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 52f8825c724b..82d3ca785161 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -119,7 +119,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { int systemBarInsetAmount = 0; mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, () -> {}); Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); @@ -141,7 +141,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { int systemBarInsetAmount = paddingBottom + 1; mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, () -> {}); Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); @@ -158,9 +158,10 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @Test public void testDefaultViewMode() { mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, () -> { + }); mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, () -> {}); ConstraintSet.Constraint viewFlipperConstraint = getViewConstraint(mSecurityViewFlipper.getId()); assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID); @@ -377,7 +378,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { private void setupUserSwitcher() { when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT); mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER, - mGlobalSettings, mFalsingManager, mUserSwitcherController); + mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> {}); } private ArrayList<UserRecord> buildUserRecords(int count) { @@ -387,7 +388,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { 0 /* flags */); users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */, false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */, null /* enforcedAdmin */)); + false /* isAddSupervisedUser */, null /* enforcedAdmin */, + false /* isManageUsers */)); } return users; } @@ -395,7 +397,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { private void setupForUpdateKeyguardPosition(boolean oneHandedMode) { int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT; mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, () -> {}); } /** Get the ConstraintLayout constraint of the view. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index df10dfe9f160..181839ab512f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -231,7 +232,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { } @Override - protected void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mExecutor.runAllReady(); } @@ -255,6 +256,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { }); mScreenDecorations.mDisplayInfo = mDisplayInfo; doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio(); + doNothing().when(mScreenDecorations).updateOverlayProviderViews(any()); reset(mTunerService); try { @@ -1005,18 +1007,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize()); assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize()); - setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, - getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px) - /* roundedTopDrawable */, - getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px) - /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/); + doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio(); mDisplayInfo.rotation = Surface.ROTATION_270; mScreenDecorations.onConfigurationChanged(null); - assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize()); - assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize()); + assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize()); + assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize()); } @Test @@ -1293,51 +1290,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { } @Test - public void testOnDisplayChanged_hwcLayer() { - setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, - null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); - decorationSupport.format = PixelFormat.R_8; - doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); - - // top cutout - mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); - - mScreenDecorations.start(); - - final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer; - spyOn(hwcLayer); - doReturn(mDisplay).when(hwcLayer).getDisplay(); - - mScreenDecorations.mDisplayListener.onDisplayChanged(1); - - verify(hwcLayer, times(1)).onDisplayChanged(any()); - } - - @Test - public void testOnDisplayChanged_nonHwcLayer() { - setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, - null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - - // top cutout - mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); - - mScreenDecorations.start(); - - final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView) - mScreenDecorations.getOverlayView(R.id.display_cutout); - assertNotNull(cutoutView); - spyOn(cutoutView); - doReturn(mDisplay).when(cutoutView).getDisplay(); - - mScreenDecorations.mDisplayListener.onDisplayChanged(1); - - verify(cutoutView, times(1)).onDisplayChanged(any()); - } - - @Test public void testHasSameProvidersWithNullOverlays() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt index 986e7cdbf6ad..6ab54a374d30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt @@ -935,6 +935,251 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) } + /* ******** start of animatesViewRemoval_includeMarginsTrue tests ******** */ + @Test + fun animatesViewRemoval_includeMarginsTrue_center() { + setUpRootWithChildren(includeMarginsOnFirstChild = true) + val removedChild = rootView.getChildAt(0) + val originalLeft = removedChild.left + val originalTop = removedChild.top + val originalRight = removedChild.right + val originalBottom = removedChild.bottom + + val success = ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.CENTER, + includeMargins = true, + ) + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + val expectedX = ((originalLeft - M_LEFT) + (originalRight + M_RIGHT)) / 2 + val expectedY = ((originalTop - M_TOP) + (originalBottom + M_BOTTOM)) / 2 + + checkBounds( + removedChild, + l = expectedX, + t = expectedY, + r = expectedX, + b = expectedY + ) + } + + @Test + fun animatesViewRemoval_includeMarginsTrue_left() { + setUpRootWithChildren(includeMarginsOnFirstChild = true) + val removedChild = rootView.getChildAt(0) + val originalLeft = removedChild.left + val originalTop = removedChild.top + val originalBottom = removedChild.bottom + + val success = ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.LEFT, + includeMargins = true, + ) + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds( + removedChild, + l = originalLeft - M_LEFT, + t = originalTop, + r = originalLeft - M_LEFT, + b = originalBottom + ) + } + + @Test + fun animatesViewRemoval_includeMarginsTrue_topLeft() { + setUpRootWithChildren(includeMarginsOnFirstChild = true) + val removedChild = rootView.getChildAt(0) + val originalLeft = removedChild.left + val originalTop = removedChild.top + + val success = ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT, + includeMargins = true, + ) + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds( + removedChild, + l = originalLeft - M_LEFT, + t = originalTop - M_TOP, + r = originalLeft - M_LEFT, + b = originalTop - M_TOP + ) + } + + @Test + fun animatesViewRemoval_includeMarginsTrue_top() { + setUpRootWithChildren(includeMarginsOnFirstChild = true) + val removedChild = rootView.getChildAt(0) + val originalLeft = removedChild.left + val originalTop = removedChild.top + val originalRight = removedChild.right + + val success = ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.TOP, + includeMargins = true, + ) + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds( + removedChild, + l = originalLeft, + t = originalTop - M_TOP, + r = originalRight, + b = originalTop - M_TOP + ) + } + + @Test + fun animatesViewRemoval_includeMarginsTrue_topRight() { + setUpRootWithChildren(includeMarginsOnFirstChild = true) + val removedChild = rootView.getChildAt(0) + val originalTop = removedChild.top + val originalRight = removedChild.right + + val success = ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT, + includeMargins = true, + ) + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds( + removedChild, + l = originalRight + M_RIGHT, + t = originalTop - M_TOP, + r = originalRight + M_RIGHT, + b = originalTop - M_TOP + ) + } + + @Test + fun animatesViewRemoval_includeMarginsTrue_right() { + setUpRootWithChildren(includeMarginsOnFirstChild = true) + val removedChild = rootView.getChildAt(0) + val originalTop = removedChild.top + val originalRight = removedChild.right + val originalBottom = removedChild.bottom + + val success = ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.RIGHT, + includeMargins = true, + ) + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds( + removedChild, + l = originalRight + M_RIGHT, + t = originalTop, + r = originalRight + M_RIGHT, + b = originalBottom + ) + } + + @Test + fun animatesViewRemoval_includeMarginsTrue_bottomRight() { + setUpRootWithChildren(includeMarginsOnFirstChild = true) + val removedChild = rootView.getChildAt(0) + val originalRight = removedChild.right + val originalBottom = removedChild.bottom + + val success = ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT, + includeMargins = true, + ) + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds( + removedChild, + l = originalRight + M_RIGHT, + t = originalBottom + M_BOTTOM, + r = originalRight + M_RIGHT, + b = originalBottom + M_BOTTOM + ) + } + + @Test + fun animatesViewRemoval_includeMarginsTrue_bottom() { + setUpRootWithChildren(includeMarginsOnFirstChild = true) + val removedChild = rootView.getChildAt(0) + val originalLeft = removedChild.left + val originalRight = removedChild.right + val originalBottom = removedChild.bottom + + val success = ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.BOTTOM, + includeMargins = true, + ) + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds( + removedChild, + l = originalLeft, + t = originalBottom + M_BOTTOM, + r = originalRight, + b = originalBottom + M_BOTTOM + ) + } + + @Test + fun animatesViewRemoval_includeMarginsTrue_bottomLeft() { + setUpRootWithChildren(includeMarginsOnFirstChild = true) + val removedChild = rootView.getChildAt(0) + val originalLeft = removedChild.left + val originalBottom = removedChild.bottom + + val success = ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT, + includeMargins = true, + ) + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds( + removedChild, + l = originalLeft - M_LEFT, + t = originalBottom + M_BOTTOM, + r = originalLeft - M_LEFT, + b = originalBottom + M_BOTTOM + ) + } + /* ******** end of animatesViewRemoval_includeMarginsTrue tests ******** */ + @Test fun animatesChildrenDuringViewRemoval() { setUpRootWithChildren() @@ -1215,7 +1460,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { checkBounds(rootView, l = 10, t = 10, r = 50, b = 50) } - private fun setUpRootWithChildren() { + private fun setUpRootWithChildren(includeMarginsOnFirstChild: Boolean = false) { rootView = LinearLayout(mContext) (rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL (rootView as LinearLayout).weightSum = 1f @@ -1229,13 +1474,26 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val secondChild = View(mContext) rootView.addView(secondChild) - val childParams = LinearLayout.LayoutParams( + val firstChildParams = LinearLayout.LayoutParams( + 0 /* width */, + LinearLayout.LayoutParams.MATCH_PARENT + ) + firstChildParams.weight = 0.5f + if (includeMarginsOnFirstChild) { + firstChildParams.leftMargin = M_LEFT + firstChildParams.topMargin = M_TOP + firstChildParams.rightMargin = M_RIGHT + firstChildParams.bottomMargin = M_BOTTOM + } + firstChild.layoutParams = firstChildParams + + val secondChildParams = LinearLayout.LayoutParams( 0 /* width */, LinearLayout.LayoutParams.MATCH_PARENT ) - childParams.weight = 0.5f - firstChild.layoutParams = childParams - secondChild.layoutParams = childParams + secondChildParams.weight = 0.5f + secondChild.layoutParams = secondChildParams + firstGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */) (firstGrandChild.layoutParams as RelativeLayout.LayoutParams) .addRule(RelativeLayout.ALIGN_PARENT_START) @@ -1315,3 +1573,9 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { } } } + +// Margin values. +private const val M_LEFT = 14 +private const val M_TOP = 16 +private const val M_RIGHT = 18 +private const val M_BOTTOM = 20 diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java index b2a9e8209495..6bc7308a6a40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -145,6 +145,35 @@ public class BrightLineClassifierTest extends SysuiTestCase { } @Test + public void testIsFalseTouch_SeekBar_FalseTouch() { + when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble())) + .thenReturn(mFalsedResult); + when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isTrue(); + } + + @Test + public void testIsFalseTouch_SeekBar_RealTouch() { + when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isFalse(); + } + + @Test + public void testIsFalseTouch_SeekBar_FalseTap() { + when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble())) + .thenReturn(mFalsedResult); + when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isTrue(); + } + + @Test + public void testIsFalseTouch_SeekBar_RealTap() { + when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble())) + .thenReturn(mFalsedResult); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isFalse(); + } + + @Test public void testIsFalseTouch_ClassifierBRejects() { when(mClassifierB.classifyGesture(anyInt(), anyDouble(), anyDouble())) .thenReturn(mFalsedResult); 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 91214a85ddd5..e7e6918325a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java @@ -38,6 +38,8 @@ 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; @@ -47,6 +49,9 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import javax.inject.Provider; @SmallTest @RunWith(AndroidJUnit4.class) @@ -55,11 +60,15 @@ public class ClipboardListenerTest extends SysuiTestCase { @Mock private ClipboardManager mClipboardManager; @Mock - private ClipboardOverlayControllerFactory mClipboardOverlayControllerFactory; + private ClipboardOverlayControllerLegacyFactory mClipboardOverlayControllerLegacyFactory; + @Mock + private ClipboardOverlayControllerLegacy mOverlayControllerLegacy; @Mock private ClipboardOverlayController mOverlayController; @Mock private UiEventLogger mUiEventLogger; + @Mock + private FeatureFlags mFeatureFlags; private DeviceConfigProxyFake mDeviceConfigProxy; private ClipData mSampleClipData; @@ -72,12 +81,17 @@ public class ClipboardListenerTest extends SysuiTestCase { @Captor private ArgumentCaptor<String> mStringCaptor; + @Spy + private Provider<ClipboardOverlayController> mOverlayControllerProvider; + @Before public void setup() { + mOverlayControllerProvider = () -> mOverlayController; + MockitoAnnotations.initMocks(this); - when(mClipboardOverlayControllerFactory.create(any())).thenReturn( - mOverlayController); + when(mClipboardOverlayControllerLegacyFactory.create(any())) + .thenReturn(mOverlayControllerLegacy); when(mClipboardManager.hasPrimaryClip()).thenReturn(true); @@ -94,7 +108,8 @@ public class ClipboardListenerTest extends SysuiTestCase { mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, "false", false); ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy, - mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger); + mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory, + mClipboardManager, mUiEventLogger, mFeatureFlags); listener.start(); verifyZeroInteractions(mClipboardManager); verifyZeroInteractions(mUiEventLogger); @@ -105,7 +120,8 @@ public class ClipboardListenerTest extends SysuiTestCase { mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, "true", false); ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy, - mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger); + mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory, + mClipboardManager, mUiEventLogger, mFeatureFlags); listener.start(); verify(mClipboardManager).addPrimaryClipChangedListener(any()); verifyZeroInteractions(mUiEventLogger); @@ -113,16 +129,58 @@ 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); ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy, - mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger); + mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory, + mClipboardManager, mUiEventLogger, mFeatureFlags); listener.start(); listener.onPrimaryClipChanged(); - verify(mClipboardOverlayControllerFactory).create(any()); + verify(mClipboardOverlayControllerLegacyFactory).create(any()); - verify(mOverlayController).setClipData(mClipDataCaptor.capture(), mStringCaptor.capture()); + 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(); + + listener.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. + + listener.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); + ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy, + mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory, + mClipboardManager, mUiEventLogger, mFeatureFlags); + listener.start(); + listener.onPrimaryClipChanged(); + + verify(mOverlayControllerProvider).get(); + + verify(mOverlayController).setClipData( + mClipDataCaptor.capture(), mStringCaptor.capture()); assertEquals(mSampleClipData, mClipDataCaptor.getValue()); assertEquals(mSampleSource, mStringCaptor.getValue()); @@ -134,14 +192,15 @@ public class ClipboardListenerTest extends SysuiTestCase { listener.onPrimaryClipChanged(); - verify(mClipboardOverlayControllerFactory, times(2)).create(any()); + verify(mOverlayControllerProvider, times(2)).get(); // Not calling the runnable here, just change the clip again and verify that the overlay is // NOT recreated. listener.onPrimaryClipChanged(); - verify(mClipboardOverlayControllerFactory, times(2)).create(any()); + verify(mOverlayControllerProvider, times(2)).get(); + verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory); } @Test @@ -169,4 +228,40 @@ public class ClipboardListenerTest extends SysuiTestCase { assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData, ClipboardListener.SHELL_PACKAGE, false)); } + + @Test + public void test_logging_enterAndReenter() { + when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false); + + ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy, + mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory, + mClipboardManager, mUiEventLogger, mFeatureFlags); + listener.start(); + + listener.onPrimaryClipChanged(); + listener.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); + + ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy, + mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory, + mClipboardManager, mUiEventLogger, mFeatureFlags); + listener.start(); + + listener.onPrimaryClipChanged(); + listener.onPrimaryClipChanged(); + + verify(mUiEventLogger, times(1)).log( + ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource); + verify(mUiEventLogger, times(1)).log( + ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java new file mode 100644 index 000000000000..677c7bdcffe1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -0,0 +1,185 @@ +/* + * 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 static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_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 org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.animation.Animator; +import android.content.ClipData; +import android.content.ClipDescription; +import android.net.Uri; +import android.os.PersistableBundle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.logging.UiEventLogger; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastSender; +import com.android.systemui.screenshot.TimeoutHandler; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Ignore("b/254635291") +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ClipboardOverlayControllerTest extends SysuiTestCase { + + private ClipboardOverlayController mOverlayController; + @Mock + private ClipboardOverlayView mClipboardOverlayView; + @Mock + private ClipboardOverlayWindow mClipboardOverlayWindow; + @Mock + private BroadcastSender mBroadcastSender; + @Mock + private TimeoutHandler mTimeoutHandler; + @Mock + private UiEventLogger mUiEventLogger; + + @Mock + private Animator mAnimator; + + private ClipData mSampleClipData; + + @Captor + private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor; + private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks; + + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator); + when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator); + + mSampleClipData = new ClipData("Test", new String[]{"text/plain"}, + new ClipData.Item("Test Item")); + + mOverlayController = new ClipboardOverlayController( + mContext, + mClipboardOverlayView, + mClipboardOverlayWindow, + getFakeBroadcastDispatcher(), + mBroadcastSender, + mTimeoutHandler, + mUiEventLogger); + verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture()); + mCallbacks = mOverlayCallbacksCaptor.getValue(); + } + + @Test + public void test_setClipData_nullData() { + ClipData clipData = null; + mOverlayController.setClipData(clipData, ""); + + verify(mClipboardOverlayView, times(1)).showDefaultTextPreview(); + verify(mClipboardOverlayView, times(0)).showShareChip(); + verify(mClipboardOverlayView, times(1)).getEnterAnimation(); + } + + @Test + public void test_setClipData_invalidImageData() { + ClipData clipData = new ClipData("", new String[]{"image/png"}, + new ClipData.Item(Uri.parse(""))); + + mOverlayController.setClipData(clipData, ""); + + verify(mClipboardOverlayView, times(1)).showDefaultTextPreview(); + verify(mClipboardOverlayView, times(0)).showShareChip(); + verify(mClipboardOverlayView, times(1)).getEnterAnimation(); + } + + @Test + public void test_setClipData_textData() { + mOverlayController.setClipData(mSampleClipData, ""); + + verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false); + verify(mClipboardOverlayView, times(1)).showShareChip(); + verify(mClipboardOverlayView, times(1)).getEnterAnimation(); + } + + @Test + public void test_setClipData_sensitiveTextData() { + ClipDescription description = mSampleClipData.getDescription(); + PersistableBundle b = new PersistableBundle(); + b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true); + description.setExtras(b); + ClipData data = new ClipData(description, mSampleClipData.getItemAt(0)); + mOverlayController.setClipData(data, ""); + + verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true); + verify(mClipboardOverlayView, times(1)).showShareChip(); + verify(mClipboardOverlayView, times(1)).getEnterAnimation(); + } + + @Test + public void test_setClipData_repeatedCalls() { + when(mAnimator.isRunning()).thenReturn(true); + + mOverlayController.setClipData(mSampleClipData, ""); + mOverlayController.setClipData(mSampleClipData, ""); + + verify(mClipboardOverlayView, times(1)).getEnterAnimation(); + } + + @Test + public void test_viewCallbacks_onShareTapped() { + mOverlayController.setClipData(mSampleClipData, ""); + + mCallbacks.onShareButtonTapped(); + + verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED); + verify(mClipboardOverlayView, times(1)).getExitAnimation(); + } + + @Test + public void test_viewCallbacks_onDismissTapped() { + mOverlayController.setClipData(mSampleClipData, ""); + + mCallbacks.onDismissButtonTapped(); + + verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED); + verify(mClipboardOverlayView, times(1)).getExitAnimation(); + } + + @Test + public void test_multipleDismissals_dismissesOnce() { + mCallbacks.onSwipeDismissInitiated(mAnimator); + mCallbacks.onDismissButtonTapped(); + mCallbacks.onSwipeDismissInitiated(mAnimator); + mCallbacks.onDismissButtonTapped(); + + verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED); + verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java deleted file mode 100644 index c7c2cd8d7b4b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java +++ /dev/null @@ -1,93 +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 static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ClipData; -import android.content.ClipboardManager; -import android.provider.DeviceConfig; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.logging.UiEventLogger; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.util.DeviceConfigProxyFake; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class ClipboardOverlayEventTest extends SysuiTestCase { - - @Mock - private ClipboardManager mClipboardManager; - @Mock - private ClipboardOverlayControllerFactory mClipboardOverlayControllerFactory; - @Mock - private ClipboardOverlayController mOverlayController; - @Mock - private UiEventLogger mUiEventLogger; - - private final String mSampleSource = "Example source"; - - private ClipboardListener mClipboardListener; - - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - when(mClipboardOverlayControllerFactory.create(any())).thenReturn( - mOverlayController); - when(mClipboardManager.hasPrimaryClip()).thenReturn(true); - - ClipData sampleClipData = new ClipData("Test", new String[]{"text/plain"}, - new ClipData.Item("Test Item")); - when(mClipboardManager.getPrimaryClip()).thenReturn(sampleClipData); - when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource); - - DeviceConfigProxyFake deviceConfigProxy = new DeviceConfigProxyFake(); - deviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, - "true", false); - - mClipboardListener = new ClipboardListener(getContext(), deviceConfigProxy, - mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger); - } - - @Test - public void test_enterAndReenter() { - 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); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt index f93336134900..93a1868b72f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt @@ -24,12 +24,11 @@ import androidx.annotation.DrawableRes import androidx.test.filters.SmallTest import com.android.internal.R as InternalR import com.android.systemui.R as SystemUIR -import com.android.systemui.tests.R import com.android.systemui.SysuiTestCase +import com.android.systemui.tests.R import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test - import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @@ -102,14 +101,11 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize) assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize) - setupResources(radius = 100, - roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px), - roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px)) - + roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f roundedCornerResDelegate.updateDisplayUniqueId(null, 1) - assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize) - assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize) + assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize) + assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index 6a55a60c2fda..5bbd8109d8f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -16,6 +16,9 @@ package com.android.systemui.doze; +import static android.content.res.Configuration.UI_MODE_NIGHT_YES; +import static android.content.res.Configuration.UI_MODE_TYPE_CAR; + import static com.android.systemui.doze.DozeMachine.State.DOZE; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED; @@ -38,16 +41,17 @@ 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.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.UiModeManager; import android.content.res.Configuration; import android.hardware.display.AmbientDisplayConfiguration; import android.testing.AndroidTestingRunner; import android.testing.UiThreadTest; import android.view.Display; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -78,25 +82,30 @@ public class DozeMachineTest extends SysuiTestCase { @Mock private DozeHost mHost; @Mock - private UiModeManager mUiModeManager; + private DozeMachine.Part mPartMock; + @Mock + private DozeMachine.Part mAnotherPartMock; private DozeServiceFake mServiceFake; private WakeLockFake mWakeLockFake; - private AmbientDisplayConfiguration mConfigMock; - private DozeMachine.Part mPartMock; + private AmbientDisplayConfiguration mAmbientDisplayConfigMock; @Before public void setUp() { MockitoAnnotations.initMocks(this); mServiceFake = new DozeServiceFake(); mWakeLockFake = new WakeLockFake(); - mConfigMock = mock(AmbientDisplayConfiguration.class); - mPartMock = mock(DozeMachine.Part.class); + mAmbientDisplayConfigMock = mock(AmbientDisplayConfiguration.class); when(mDockManager.isDocked()).thenReturn(false); when(mDockManager.isHidden()).thenReturn(false); - mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake, - mWakefulnessLifecycle, mUiModeManager, mDozeLog, mDockManager, - mHost, new DozeMachine.Part[]{mPartMock}); + mMachine = new DozeMachine(mServiceFake, + mAmbientDisplayConfigMock, + mWakeLockFake, + mWakefulnessLifecycle, + mDozeLog, + mDockManager, + mHost, + new DozeMachine.Part[]{mPartMock, mAnotherPartMock}); } @Test @@ -108,7 +117,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testInitialize_goesToDoze() { - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); mMachine.requestState(INITIALIZED); @@ -118,7 +127,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testInitialize_goesToAod() { - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); mMachine.requestState(INITIALIZED); @@ -138,7 +147,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testInitialize_afterDockPaused_goesToDoze() { - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); when(mDockManager.isDocked()).thenReturn(true); when(mDockManager.isHidden()).thenReturn(true); @@ -151,7 +160,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_goesToDoze() { when(mHost.isAlwaysOnSuppressed()).thenReturn(true); - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); mMachine.requestState(INITIALIZED); @@ -162,7 +171,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_goesToDoze() { when(mHost.isAlwaysOnSuppressed()).thenReturn(true); - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); mMachine.requestState(INITIALIZED); @@ -184,7 +193,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_afterDockPaused_goesToDoze() { when(mHost.isAlwaysOnSuppressed()).thenReturn(true); - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); when(mDockManager.isDocked()).thenReturn(true); when(mDockManager.isHidden()).thenReturn(true); @@ -197,7 +206,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_afterDockPaused_goesToDoze() { when(mHost.isAlwaysOnSuppressed()).thenReturn(true); - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); when(mDockManager.isDocked()).thenReturn(true); when(mDockManager.isHidden()).thenReturn(true); @@ -209,7 +218,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testPulseDone_goesToDoze() { - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); mMachine.requestState(INITIALIZED); mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); mMachine.requestState(DOZE_PULSING); @@ -222,7 +231,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testPulseDone_goesToAoD() { - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); mMachine.requestState(INITIALIZED); mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); mMachine.requestState(DOZE_PULSING); @@ -236,7 +245,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testPulseDone_alwaysOnSuppressed_goesToSuppressed() { when(mHost.isAlwaysOnSuppressed()).thenReturn(true); - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); mMachine.requestState(INITIALIZED); mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); mMachine.requestState(DOZE_PULSING); @@ -287,7 +296,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testPulseDone_afterDockPaused_goesToDoze() { - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); when(mDockManager.isDocked()).thenReturn(true); when(mDockManager.isHidden()).thenReturn(true); mMachine.requestState(INITIALIZED); @@ -303,7 +312,7 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testPulseDone_alwaysOnSuppressed_afterDockPaused_goesToDoze() { when(mHost.isAlwaysOnSuppressed()).thenReturn(true); - when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); when(mDockManager.isDocked()).thenReturn(true); when(mDockManager.isHidden()).thenReturn(true); mMachine.requestState(INITIALIZED); @@ -471,7 +480,9 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testTransitionToInitialized_carModeIsEnabled() { - when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); + Configuration configuration = configWithCarNightUiMode(); + + mMachine.onConfigurationChanged(configuration); mMachine.requestState(INITIALIZED); verify(mPartMock).transitionTo(UNINITIALIZED, INITIALIZED); @@ -481,7 +492,9 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testTransitionToFinish_carModeIsEnabled() { - when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); + Configuration configuration = configWithCarNightUiMode(); + + mMachine.onConfigurationChanged(configuration); mMachine.requestState(INITIALIZED); mMachine.requestState(FINISH); @@ -490,7 +503,9 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testDozeToDozeSuspendTriggers_carModeIsEnabled() { - when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); + Configuration configuration = configWithCarNightUiMode(); + + mMachine.onConfigurationChanged(configuration); mMachine.requestState(INITIALIZED); mMachine.requestState(DOZE); @@ -499,7 +514,9 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testDozeAoDToDozeSuspendTriggers_carModeIsEnabled() { - when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); + Configuration configuration = configWithCarNightUiMode(); + + mMachine.onConfigurationChanged(configuration); mMachine.requestState(INITIALIZED); mMachine.requestState(DOZE_AOD); @@ -508,7 +525,9 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testDozePulsingBrightDozeSuspendTriggers_carModeIsEnabled() { - when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); + Configuration configuration = configWithCarNightUiMode(); + + mMachine.onConfigurationChanged(configuration); mMachine.requestState(INITIALIZED); mMachine.requestState(DOZE_PULSING_BRIGHT); @@ -517,7 +536,9 @@ public class DozeMachineTest extends SysuiTestCase { @Test public void testDozeAodDockedDozeSuspendTriggers_carModeIsEnabled() { - when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); + Configuration configuration = configWithCarNightUiMode(); + + mMachine.onConfigurationChanged(configuration); mMachine.requestState(INITIALIZED); mMachine.requestState(DOZE_AOD_DOCKED); @@ -525,7 +546,35 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testOnConfigurationChanged_propagatesUiModeTypeToParts() { + Configuration newConfig = configWithCarNightUiMode(); + + mMachine.onConfigurationChanged(newConfig); + + verify(mPartMock).onUiModeTypeChanged(UI_MODE_TYPE_CAR); + verify(mAnotherPartMock).onUiModeTypeChanged(UI_MODE_TYPE_CAR); + } + + @Test + public void testOnConfigurationChanged_propagatesOnlyUiModeChangesToParts() { + Configuration newConfig = configWithCarNightUiMode(); + + mMachine.onConfigurationChanged(newConfig); + mMachine.onConfigurationChanged(newConfig); + + verify(mPartMock, times(1)).onUiModeTypeChanged(UI_MODE_TYPE_CAR); + verify(mAnotherPartMock, times(1)).onUiModeTypeChanged(UI_MODE_TYPE_CAR); + } + + @Test public void testDozeSuppressTriggers_screenState() { assertEquals(Display.STATE_OFF, DOZE_SUSPEND_TRIGGERS.screenState(null)); } + + @NonNull + private Configuration configWithCarNightUiMode() { + Configuration configuration = Configuration.EMPTY; + configuration.uiMode = UI_MODE_TYPE_CAR | UI_MODE_NIGHT_YES; + return configuration; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java index 0f29dcd5a939..32b994538e12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java @@ -10,14 +10,14 @@ * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions andatest + * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.doze; -import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE; -import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE; +import static android.content.res.Configuration.UI_MODE_TYPE_CAR; +import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL; import static com.android.systemui.doze.DozeMachine.State.DOZE; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; @@ -26,17 +26,16 @@ import static com.android.systemui.doze.DozeMachine.State.FINISH; import static com.android.systemui.doze.DozeMachine.State.INITIALIZED; import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.UiModeManager; -import android.content.BroadcastReceiver; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration; import android.hardware.display.AmbientDisplayConfiguration; import android.testing.AndroidTestingRunner; import android.testing.UiThreadTest; @@ -44,13 +43,13 @@ import android.testing.UiThreadTest; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.statusbar.phone.BiometricUnlockController; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.AdditionalMatchers; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -71,10 +70,6 @@ public class DozeSuppressorTest extends SysuiTestCase { @Mock private AmbientDisplayConfiguration mConfig; @Mock - private BroadcastDispatcher mBroadcastDispatcher; - @Mock - private UiModeManager mUiModeManager; - @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; @Mock private BiometricUnlockController mBiometricUnlockController; @@ -83,13 +78,6 @@ public class DozeSuppressorTest extends SysuiTestCase { private DozeMachine mDozeMachine; @Captor - private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor; - @Captor - private ArgumentCaptor<IntentFilter> mIntentFilterCaptor; - private BroadcastReceiver mBroadcastReceiver; - private IntentFilter mIntentFilter; - - @Captor private ArgumentCaptor<DozeHost.Callback> mDozeHostCaptor; private DozeHost.Callback mDozeHostCallback; @@ -106,8 +94,6 @@ public class DozeSuppressorTest extends SysuiTestCase { mDozeHost, mConfig, mDozeLog, - mBroadcastDispatcher, - mUiModeManager, mBiometricUnlockControllerLazy); mDozeSuppressor.setDozeMachine(mDozeMachine); @@ -122,36 +108,35 @@ public class DozeSuppressorTest extends SysuiTestCase { public void testRegistersListenersOnInitialized_unregisteredOnFinish() { // check that receivers and callbacks registered mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); - captureBroadcastReceiver(); captureDozeHostCallback(); // check that receivers and callbacks are unregistered mDozeSuppressor.transitionTo(INITIALIZED, FINISH); - verify(mBroadcastDispatcher).unregisterReceiver(mBroadcastReceiver); verify(mDozeHost).removeCallback(mDozeHostCallback); } @Test public void testSuspendTriggersDoze_carMode() { // GIVEN car mode - when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR); + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); // WHEN dozing begins mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); // THEN doze continues with all doze triggers disabled. - verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS); + verify(mDozeMachine, atLeastOnce()).requestState(DOZE_SUSPEND_TRIGGERS); + verify(mDozeMachine, never()) + .requestState(AdditionalMatchers.not(eq(DOZE_SUSPEND_TRIGGERS))); } @Test public void testSuspendTriggersDoze_enterCarMode() { // GIVEN currently dozing mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); - captureBroadcastReceiver(); mDozeSuppressor.transitionTo(INITIALIZED, DOZE); // WHEN car mode entered - mBroadcastReceiver.onReceive(null, new Intent(ACTION_ENTER_CAR_MODE)); + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); // THEN doze continues with all doze triggers disabled. verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS); @@ -160,13 +145,13 @@ public class DozeSuppressorTest extends SysuiTestCase { @Test public void testDozeResume_exitCarMode() { // GIVEN currently suspended, with AOD not enabled + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(false); mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); - captureBroadcastReceiver(); mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS); // WHEN exiting car mode - mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE)); + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_NORMAL); // THEN doze is resumed verify(mDozeMachine).requestState(DOZE); @@ -175,19 +160,53 @@ public class DozeSuppressorTest extends SysuiTestCase { @Test public void testDozeAoDResume_exitCarMode() { // GIVEN currently suspended, with AOD not enabled + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true); mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); - captureBroadcastReceiver(); mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS); // WHEN exiting car mode - mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE)); + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_NORMAL); // THEN doze AOD is resumed verify(mDozeMachine).requestState(DOZE_AOD); } @Test + public void testUiModeDoesNotChange_noStateTransition() { + mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); + clearInvocations(mDozeMachine); + + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); + + verify(mDozeMachine, times(1)).requestState(DOZE_SUSPEND_TRIGGERS); + verify(mDozeMachine, never()) + .requestState(AdditionalMatchers.not(eq(DOZE_SUSPEND_TRIGGERS))); + } + + @Test + public void testUiModeTypeChange_whenDozeMachineIsNotReady_doesNotDoAnything() { + when(mDozeMachine.isUninitializedOrFinished()).thenReturn(true); + + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); + + verify(mDozeMachine, never()).requestState(any()); + } + + @Test + public void testUiModeTypeChange_CarModeEnabledAndDozeMachineNotReady_suspendsTriggersAfter() { + when(mDozeMachine.isUninitializedOrFinished()).thenReturn(true); + mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR); + verify(mDozeMachine, never()).requestState(any()); + + mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED); + + verify(mDozeMachine, times(1)).requestState(DOZE_SUSPEND_TRIGGERS); + } + + @Test public void testEndDoze_unprovisioned() { // GIVEN device unprovisioned when(mDozeHost.isProvisioned()).thenReturn(false); @@ -276,14 +295,4 @@ public class DozeSuppressorTest extends SysuiTestCase { verify(mDozeHost).addCallback(mDozeHostCaptor.capture()); mDozeHostCallback = mDozeHostCaptor.getValue(); } - - private void captureBroadcastReceiver() { - verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(), - mIntentFilterCaptor.capture()); - mBroadcastReceiver = mBroadcastReceiverCaptor.getValue(); - mIntentFilter = mIntentFilterCaptor.getValue(); - assertEquals(2, mIntentFilter.countActions()); - org.hamcrest.MatcherAssert.assertThat(() -> mIntentFilter.actionsIterator(), - containsInAnyOrder(ACTION_ENTER_CAR_MODE, ACTION_EXIT_CAR_MODE)); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java index 571dd3d1faf3..9f4a7c820efc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java @@ -71,7 +71,7 @@ public class ComplicationTypesUpdaterTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>()); - mController = new ComplicationTypesUpdater(mContext, mDreamBackend, mExecutor, + mController = new ComplicationTypesUpdater(mDreamBackend, mExecutor, mSecureSettings, mDreamOverlayStateController); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java index 314a30b2d14a..ec448f94ba83 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java @@ -82,7 +82,6 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase { public void testComplicationAdded() { final DreamClockTimeComplication.Registrant registrant = new DreamClockTimeComplication.Registrant( - mContext, mDreamOverlayStateController, mComplication); registrant.start(); 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 db6082d52501..aa8c93edce68 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 @@ -115,7 +115,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Test public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = - new DreamHomeControlsComplication.Registrant(mContext, mComplication, + new DreamHomeControlsComplication.Registrant(mComplication, mDreamOverlayStateController, mControlsComponent); registrant.start(); @@ -128,7 +128,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Test public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = - new DreamHomeControlsComplication.Registrant(mContext, mComplication, + new DreamHomeControlsComplication.Registrant(mComplication, mDreamOverlayStateController, mControlsComponent); registrant.start(); @@ -141,7 +141,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Test public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = - new DreamHomeControlsComplication.Registrant(mContext, mComplication, + new DreamHomeControlsComplication.Registrant(mComplication, mDreamOverlayStateController, mControlsComponent); registrant.start(); @@ -154,7 +154,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Test public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() { final DreamHomeControlsComplication.Registrant registrant = - new DreamHomeControlsComplication.Registrant(mContext, mComplication, + new DreamHomeControlsComplication.Registrant(mComplication, mDreamOverlayStateController, mControlsComponent); registrant.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java index fa8f88a08368..c8b2b2556828 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.smartspace.SmartspaceTarget; -import android.content.Context; import android.testing.AndroidTestingRunner; import android.view.View; @@ -48,8 +47,6 @@ import java.util.Collections; @SmallTest @RunWith(AndroidTestingRunner.class) public class SmartSpaceComplicationTest extends SysuiTestCase { - @Mock - private Context mContext; @Mock private DreamSmartspaceController mSmartspaceController; @@ -80,7 +77,6 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { private SmartSpaceComplication.Registrant getRegistrant() { return new SmartSpaceComplication.Registrant( - mContext, mDreamOverlayStateController, mComplication, mSmartspaceController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt index fc672016a886..65b44a14d2ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt @@ -17,11 +17,13 @@ package com.android.systemui.dump import androidx.test.filters.SmallTest +import com.android.systemui.CoreStartable import com.android.systemui.Dumpable import com.android.systemui.SysuiTestCase -import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.mockito.Mock @@ -30,6 +32,8 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.io.PrintWriter +import java.io.StringWriter +import javax.inject.Provider @SmallTest class DumpHandlerTest : SysuiTestCase() { @@ -66,7 +70,9 @@ class DumpHandlerTest : SysuiTestCase() { mContext, dumpManager, logBufferEulogizer, - mutableMapOf(), + mutableMapOf( + EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() } + ), exceptionHandlerManager ) } @@ -154,4 +160,20 @@ class DumpHandlerTest : SysuiTestCase() { verify(buffer1).dump(pw, 0) verify(buffer2).dump(pw, 0) } -}
\ No newline at end of file + + @Test + fun testConfigDump() { + // GIVEN a StringPrintWriter + val stringWriter = StringWriter() + val spw = PrintWriter(stringWriter) + + // When a config dump is requested + dumpHandler.dump(spw, arrayOf("config")) + + assertThat(stringWriter.toString()).contains(EmptyCoreStartable::class.java.simpleName) + } + + private class EmptyCoreStartable : CoreStartable { + override fun start() {} + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt index bd029a727ee3..64547f4463d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt @@ -16,9 +16,9 @@ package com.android.systemui.dump -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.plugins.log.LogcatEchoTracker /** * Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests. diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java deleted file mode 100644 index b5e9e8decb4c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java +++ /dev/null @@ -1,152 +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.keyguard; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyObject; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.res.Resources; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.View; - -import androidx.test.filters.SmallTest; - -import com.android.keyguard.AnimatableClockController; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.settingslib.Utils; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shared.clocks.AnimatableClockView; -import com.android.systemui.statusbar.policy.BatteryController; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.MockitoSession; -import org.mockito.quality.Strictness; - -import java.util.concurrent.Executor; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class AnimatableClockControllerTest extends SysuiTestCase { - @Mock - private AnimatableClockView mClockView; - @Mock - private StatusBarStateController mStatusBarStateController; - @Mock - private BroadcastDispatcher mBroadcastDispatcher; - @Mock - private BatteryController mBatteryController; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - private Resources mResources; - @Mock - private Executor mMainExecutor; - @Mock - private Executor mBgExecutor; - @Mock - private FeatureFlags mFeatureFlags; - - private MockitoSession mStaticMockSession; - private AnimatableClockController mAnimatableClockController; - - // Capture listeners so that they can be used to send events - @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor = - ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); - private View.OnAttachStateChangeListener mAttachListener; - - @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor; - private StatusBarStateController.StateListener mStatusBarStateCallback; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mStaticMockSession = mockitoSession() - .mockStatic(Utils.class) - .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used - .startMocking(); - when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0); - - mAnimatableClockController = new AnimatableClockController( - mClockView, - mStatusBarStateController, - mBroadcastDispatcher, - mBatteryController, - mKeyguardUpdateMonitor, - mResources, - mMainExecutor, - mBgExecutor, - mFeatureFlags - ); - mAnimatableClockController.init(); - captureAttachListener(); - } - - @After - public void tearDown() { - mStaticMockSession.finishMocking(); - } - - @Test - public void testOnAttachedUpdatesDozeStateToTrue() { - // GIVEN dozing - when(mStatusBarStateController.isDozing()).thenReturn(true); - when(mStatusBarStateController.getDozeAmount()).thenReturn(1f); - - // WHEN the clock view gets attached - mAttachListener.onViewAttachedToWindow(mClockView); - - // THEN the clock controller updated its dozing state to true - assertTrue(mAnimatableClockController.isDozing()); - } - - @Test - public void testOnAttachedUpdatesDozeStateToFalse() { - // GIVEN not dozing - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarStateController.getDozeAmount()).thenReturn(0f); - - // WHEN the clock view gets attached - mAttachListener.onViewAttachedToWindow(mClockView); - - // THEN the clock controller updated its dozing state to false - assertFalse(mAnimatableClockController.isDozing()); - } - - private void captureAttachListener() { - verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture()); - mAttachListener = mAttachCaptor.getValue(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 39f3c96803c3..4c986bffd172 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -19,6 +19,7 @@ package com.android.systemui.keyguard; import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -229,6 +230,28 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @Test + public void testBouncerPrompt_nonStrongIdleTimeout() { + // GIVEN trust agents enabled and biometrics are enrolled + when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(true); + when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(true); + + // WHEN the strong auth reason is STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT + KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker = + mock(KeyguardUpdateMonitor.StrongAuthTracker.class); + when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(strongAuthTracker); + when(strongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true); + when(strongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout( + anyInt())).thenReturn(false); + when(strongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn( + STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT); + + // THEN the bouncer prompt reason should return + // STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT + assertEquals(KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT, + mViewMediator.mViewMediatorCallback.getBouncerPromptReason()); + } + + @Test public void testHideSurfaceBehindKeyguardMarksKeyguardNotGoingAway() { mViewMediator.hideSurfaceBehindKeyguard(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt new file mode 100644 index 000000000000..1b34100b1cef --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -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.systemui.keyguard.data.repository + +import android.animation.AnimationHandler.AnimationFrameCallbackProvider +import android.animation.ValueAnimator +import android.util.Log +import android.util.Log.TerribleFailure +import android.util.Log.TerribleFailureHandler +import android.view.Choreographer.FrameCallback +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Interpolators +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +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 com.google.common.truth.Truth.assertThat +import java.math.BigDecimal +import java.math.RoundingMode +import java.util.UUID +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield +import org.junit.After +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class KeyguardTransitionRepositoryTest : SysuiTestCase() { + + private lateinit var underTest: KeyguardTransitionRepository + private lateinit var oldWtfHandler: TerribleFailureHandler + private lateinit var wtfHandler: WtfHandler + + @Before + fun setUp() { + underTest = KeyguardTransitionRepository() + wtfHandler = WtfHandler() + oldWtfHandler = Log.setWtfHandler(wtfHandler) + } + + @After + fun tearDown() { + oldWtfHandler?.let { Log.setWtfHandler(it) } + } + + @Test + fun `startTransition runs animator to completion`() = + runBlocking(IMMEDIATE) { + val (animator, provider) = setupAnimator(this) + + val steps = mutableListOf<TransitionStep>() + val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) + + underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator)) + + val startTime = System.currentTimeMillis() + while (animator.isRunning()) { + yield() + if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) { + fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION") + } + } + + assertSteps(steps, listWithStep(BigDecimal(.1))) + + job.cancel() + provider.stop() + } + + @Test + fun `startTransition called during another transition fails`() { + underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null)) + underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null)) + + assertThat(wtfHandler.failed).isTrue() + } + + @Test + fun `Null animator enables manual control with updateTransition`() = + runBlocking(IMMEDIATE) { + val steps = mutableListOf<TransitionStep>() + val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) + + val uuid = + underTest.startTransition( + TransitionInfo( + ownerName = OWNER_NAME, + from = AOD, + to = LOCKSCREEN, + animator = null, + ) + ) + + checkNotNull(uuid).let { + underTest.updateTransition(it, 0.5f, TransitionState.RUNNING) + underTest.updateTransition(it, 1f, TransitionState.FINISHED) + } + + assertThat(steps.size).isEqualTo(3) + assertThat(steps[0]) + .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED)) + assertThat(steps[1]) + .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING)) + assertThat(steps[2]) + .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED)) + job.cancel() + } + + @Test + fun `Attempt to manually update transition with invalid UUID throws exception`() { + underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING) + assertThat(wtfHandler.failed).isTrue() + } + + @Test + fun `Attempt to manually update transition after FINISHED state throws exception`() { + val uuid = + underTest.startTransition( + TransitionInfo( + ownerName = OWNER_NAME, + from = AOD, + to = LOCKSCREEN, + animator = null, + ) + ) + + checkNotNull(uuid).let { + underTest.updateTransition(it, 1f, TransitionState.FINISHED) + underTest.updateTransition(it, 0.5f, TransitionState.RUNNING) + } + assertThat(wtfHandler.failed).isTrue() + } + + private fun listWithStep(step: BigDecimal): List<BigDecimal> { + val steps = mutableListOf<BigDecimal>() + + var i = BigDecimal.ZERO + while (i.compareTo(BigDecimal.ONE) <= 0) { + steps.add(i) + i = (i + step).setScale(2, RoundingMode.HALF_UP) + } + + return steps + } + + private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) { + // + 2 accounts for start and finish of automated transition + assertThat(steps.size).isEqualTo(fractions.size + 2) + + assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED)) + fractions.forEachIndexed { index, fraction -> + assertThat(steps[index + 1]) + .isEqualTo( + TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING) + ) + } + assertThat(steps[steps.size - 1]) + .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED)) + + assertThat(wtfHandler.failed).isFalse() + } + + private fun setupAnimator( + scope: CoroutineScope + ): Pair<ValueAnimator, TestFrameCallbackProvider> { + val animator = + ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(ANIMATION_DURATION) + } + + val provider = TestFrameCallbackProvider(animator, scope) + provider.start() + + return Pair(animator, provider) + } + + /** Gives direct control over ValueAnimator. See [AnimationHandler] */ + private class TestFrameCallbackProvider( + private val animator: ValueAnimator, + private val scope: CoroutineScope, + ) : AnimationFrameCallbackProvider { + + private var frameCount = 1L + private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null)) + private var job: Job? = null + + fun start() { + animator.getAnimationHandler().setProvider(this) + + job = + scope.launch { + frames.collect { + // Delay is required for AnimationHandler to properly register a callback + delay(1) + val (frameNumber, callback) = it + callback?.doFrame(frameNumber) + } + } + } + + fun stop() { + job?.cancel() + animator.getAnimationHandler().setProvider(null) + } + + override fun postFrameCallback(cb: FrameCallback) { + frames.value = Pair(++frameCount, cb) + } + override fun postCommitCallback(runnable: Runnable) {} + override fun getFrameTime() = frameCount + override fun getFrameDelay() = 1L + override fun setFrameDelay(delay: Long) {} + } + + private class WtfHandler : TerribleFailureHandler { + var failed = false + override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) { + failed = true + } + } + + companion object { + private const val MAX_TEST_DURATION = 100L + private const val ANIMATION_DURATION = 10L + private const val OWNER_NAME = "Test" + private val IMMEDIATE = Dispatchers.Main.immediate + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index b6d7559dbcbb..b4d5464d1177 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -12,20 +12,20 @@ * WITHOUT WARRANTIES 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.usecase +package com.android.systemui.keyguard.domain.interactor import android.content.Intent import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry @@ -195,6 +195,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller + @Mock private lateinit var expandable: Expandable private lateinit var underTest: KeyguardQuickAffordanceInteractor @@ -208,6 +209,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + whenever(expandable.activityLaunchController()).thenReturn(animationController) homeControls = object : FakeKeyguardQuickAffordanceConfig() {} underTest = @@ -259,7 +261,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { underTest.onQuickAffordanceClicked( configKey = homeControls::class, - animationController = animationController, + expandable = expandable, ) if (startActivity) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 1dd919aba88d..65fd6e576650 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -12,9 +12,10 @@ * WITHOUT WARRANTIES 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.usecase +package com.android.systemui.keyguard.domain.interactor import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils @@ -22,13 +23,12 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController @@ -103,6 +103,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { homeControls.setState( KeyguardQuickAffordanceConfig.State.Visible( icon = ICON, + toggle = KeyguardQuickAffordanceToggleState.On, ) ) @@ -123,6 +124,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(visibleModel.icon).isEqualTo(ICON) assertThat(visibleModel.icon.contentDescription) .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID)) + assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.On) job.cancel() } @@ -152,6 +154,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(visibleModel.icon).isEqualTo(ICON) assertThat(visibleModel.icon.contentDescription) .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID)) + assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.NotSupported) job.cancel() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt index 6ea1daa7704f..e99c139e9e7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt @@ -17,7 +17,7 @@ package com.android.systemui.keyguard.domain.quickaffordance -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -40,7 +40,7 @@ abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state override fun onQuickAffordanceClicked( - animationController: ActivityLaunchAnimator.Controller?, + expandable: Expandable?, ): OnClickedResult { return onClickedResult } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt index dede4ec0210c..a809f0547ee6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt @@ -20,7 +20,7 @@ package com.android.systemui.keyguard.domain.quickaffordance import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.dagger.ControlsComponent import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult @@ -44,7 +44,7 @@ import org.mockito.MockitoAnnotations class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Mock private lateinit var component: ControlsComponent - @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller + @Mock private lateinit var expandable: Expandable private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig @@ -103,7 +103,7 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() { fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest { whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true)) - val onClickedResult = underTest.onQuickAffordanceClicked(animationController) + val onClickedResult = underTest.onQuickAffordanceClicked(expandable) assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java) assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue() @@ -113,7 +113,7 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() { fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest { whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false)) - val onClickedResult = underTest.onQuickAffordanceClicked(animationController) + val onClickedResult = underTest.onQuickAffordanceClicked(expandable) assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java) assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt index 0a4478f27448..98dc4c4f6f76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt @@ -24,11 +24,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.plugins.ActivityStarter import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.android.systemui.wallet.controller.QuickAccessWalletController import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn @@ -40,7 +42,6 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest @@ -135,8 +136,11 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Test fun onQuickAffordanceClicked() { val animationController: ActivityLaunchAnimator.Controller = mock() + val expandable: Expandable = mock { + whenever(this.activityLaunchController()).thenReturn(animationController) + } - assertThat(underTest.onQuickAffordanceClicked(animationController)) + assertThat(underTest.onQuickAffordanceClicked(expandable)) .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled) verify(walletController) .startQuickAccessUiIntent( 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 96544e7b7da6..d674c89c0e14 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 @@ -20,7 +20,7 @@ import android.content.Intent import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -31,6 +31,7 @@ import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePositio import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController @@ -59,7 +60,7 @@ import org.mockito.MockitoAnnotations @RunWith(JUnit4::class) class KeyguardBottomAreaViewModelTest : SysuiTestCase() { - @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller + @Mock private lateinit var expandable: Expandable @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper @Mock private lateinit var lockPatternUtils: LockPatternUtils @Mock private lateinit var keyguardStateController: KeyguardStateController @@ -130,6 +131,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { TestConfig( isVisible = true, isClickable = true, + isActivated = true, icon = mock(), canShowWhileLocked = false, intent = Intent("action"), @@ -505,6 +507,12 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { } KeyguardQuickAffordanceConfig.State.Visible( icon = testConfig.icon ?: error("Icon is unexpectedly null!"), + toggle = + when (testConfig.isActivated) { + true -> KeyguardQuickAffordanceToggleState.On + false -> KeyguardQuickAffordanceToggleState.Off + null -> KeyguardQuickAffordanceToggleState.NotSupported + } ) } else { KeyguardQuickAffordanceConfig.State.Hidden @@ -521,12 +529,13 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { checkNotNull(viewModel) assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible) assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable) + assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated) if (testConfig.isVisible) { assertThat(viewModel.icon).isEqualTo(testConfig.icon) viewModel.onClicked.invoke( KeyguardQuickAffordanceViewModel.OnClickedParameters( configKey = configKey, - animationController = animationController, + expandable = expandable, ) ) if (testConfig.intent != null) { @@ -542,6 +551,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { private data class TestConfig( val isVisible: Boolean, val isClickable: Boolean = false, + val isActivated: Boolean = false, val icon: Icon? = null, val canShowWhileLocked: Boolean = false, val intent: Intent? = null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java index b8e9cf48f3e2..dc5522efe406 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java @@ -82,7 +82,6 @@ public class SessionTrackerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mSessionTracker = new SessionTracker( - mContext, mStatusBarService, mAuthController, mKeyguardUpdateMonitor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt index 5ad354247a04..7e0be6dd1815 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt @@ -25,6 +25,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION +import com.android.systemui.media.MediaCarouselController.Companion.DURATION +import com.android.systemui.media.MediaCarouselController.Companion.PAGINATION_DELAY +import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER +import com.android.systemui.media.MediaHierarchyManager.Companion.LOCATION_QS import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener @@ -71,7 +76,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Mock lateinit var dumpManager: DumpManager @Mock lateinit var logger: MediaUiEventLogger @Mock lateinit var debugLogger: MediaCarouselControllerLogger - @Mock lateinit var mediaPlayer: MediaControlPanel @Mock lateinit var mediaViewController: MediaViewController @Mock lateinit var smartspaceMediaData: SmartspaceMediaData @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener> @@ -102,8 +106,8 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(mediaDataManager).addListener(capture(listener)) verify(visualStabilityProvider) .addPersistentReorderingAllowedListener(capture(visualStabilityCallback)) - whenever(mediaControlPanelFactory.get()).thenReturn(mediaPlayer) - whenever(mediaPlayer.mediaViewController).thenReturn(mediaViewController) + whenever(mediaControlPanelFactory.get()).thenReturn(panel) + whenever(panel.mediaViewController).thenReturn(mediaViewController) whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData) MediaPlayerData.clear() } @@ -184,6 +188,10 @@ class MediaCarouselControllerTest : SysuiTestCase() { for ((index, key) in MediaPlayerData.playerKeys().withIndex()) { assertEquals(expected.get(index).first, key.data.notificationKey) } + + for ((index, key) in MediaPlayerData.visiblePlayerKeys().withIndex()) { + assertEquals(expected.get(index).first, key.data.notificationKey) + } } @Test @@ -199,6 +207,22 @@ class MediaCarouselControllerTest : SysuiTestCase() { } @Test + fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() { + testPlayerOrdering() + + // If smartspace is prioritized + listener.value.onSmartspaceMediaDataLoaded( + SMARTSPACE_KEY, + EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true), + true + ) + + // Then it should be shown immediately after any actively playing controls + assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec) + assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec) + } + + @Test fun testOrderWithSmartspace_notPrioritized() { testPlayerOrdering() @@ -212,6 +236,31 @@ class MediaCarouselControllerTest : SysuiTestCase() { } @Test + fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() { + testPlayerOrdering() + // playing paused player + listener.value.onMediaDataLoaded("paused local", + "paused local", + DATA.copy(active = true, isPlaying = true, + playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)) + listener.value.onMediaDataLoaded("playing local", + "playing local", + DATA.copy(active = true, isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true) + ) + + assertEquals( + MediaPlayerData.getMediaPlayerIndex("paused local"), + mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex + ) + // paused player order should stays the same in visibleMediaPLayer map. + // paused player order should be first in mediaPlayer map. + assertEquals( + MediaPlayerData.visiblePlayerKeys().elementAt(3), + MediaPlayerData.playerKeys().elementAt(0) + ) + } + @Test fun testSwipeDismiss_logged() { mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke() @@ -276,6 +325,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!)) } + @Test fun testMediaLoaded_ScrollToActivePlayer() { listener.value.onMediaDataLoaded("playing local", null, @@ -287,9 +337,9 @@ class MediaCarouselControllerTest : SysuiTestCase() { DATA.copy(active = true, isPlaying = false, playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)) // adding a media recommendation card. - MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel, - false, clock) - mediaCarouselController.shouldScrollToActivePlayer = true + listener.value.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, + false) + mediaCarouselController.shouldScrollToKey = true // switching between media players. listener.value.onMediaDataLoaded("playing local", "playing local", @@ -309,8 +359,11 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() { - MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel, - false, clock) + listener.value.onSmartspaceMediaDataLoaded( + SMARTSPACE_KEY, + EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true), + false + ) listener.value.onMediaDataLoaded("playing local", null, DATA.copy(active = true, isPlaying = true, @@ -326,10 +379,12 @@ class MediaCarouselControllerTest : SysuiTestCase() { // Replaying the same media player one more time. // And check that the card stays in its position. + mediaCarouselController.shouldScrollToKey = true listener.value.onMediaDataLoaded("playing local", null, DATA.copy(active = true, isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false) + playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false, + packageName = "PACKAGE_NAME") ) playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local") assertEquals(playerIndex, 0) @@ -398,4 +453,24 @@ class MediaCarouselControllerTest : SysuiTestCase() { // added to the end because it was active less recently. assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) } + + @Test + fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() { + val delta = 0.0001F + val paginationSquishMiddle = TRANSFORM_BEZIER.getInterpolation( + (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION) + val paginationSquishEnd = TRANSFORM_BEZIER.getInterpolation( + (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION) + whenever(mediaHostStatesManager.mediaHostStates) + .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState)) + whenever(mediaHostState.visible).thenReturn(true) + mediaCarouselController.currentEndLocation = LOCATION_QS + whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle) + mediaCarouselController.updatePageIndicatorAlpha() + assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta) + + whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd) + mediaCarouselController.updatePageIndicatorAlpha() + assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt index 3d3ac836d264..83168cb87dfe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt @@ -305,7 +305,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // Then we save an update with the current time verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor))) componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex()) - ?.dropLastWhile { it.isEmpty() }.forEach { + .dropLastWhile { it.isEmpty() }.forEach { val result = it.split("/") assertThat(result.size).isEqualTo(3) assertThat(result[2].toLong()).isEqualTo(currentTime) @@ -392,7 +392,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // Then we store the new lastPlayed time verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor))) componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex()) - ?.dropLastWhile { it.isEmpty() }.forEach { + .dropLastWhile { it.isEmpty() }.forEach { val result = it.split("/") assertThat(result.size).isEqualTo(3) assertThat(result[2].toLong()).isEqualTo(currentTime) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt new file mode 100644 index 000000000000..622a512720d9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt @@ -0,0 +1,188 @@ +/* + * 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 + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION +import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY +import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY +import com.android.systemui.media.MediaCarouselController.Companion.DURATION +import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY +import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY +import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER +import com.android.systemui.util.animation.MeasurementInput +import com.android.systemui.util.animation.TransitionLayout +import com.android.systemui.util.animation.TransitionViewState +import com.android.systemui.util.animation.WidgetState +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.floatThat +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidTestingRunner::class) +class MediaViewControllerTest : SysuiTestCase() { + private val mediaHostStateHolder = MediaHost.MediaHostStateHolder() + private val mediaHostStatesManager = MediaHostStatesManager() + private val configurationController = + com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context) + private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0) + private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0) + @Mock lateinit var logger: MediaViewLogger + @Mock private lateinit var mockViewState: TransitionViewState + @Mock private lateinit var mockCopiedState: TransitionViewState + @Mock private lateinit var detailWidgetState: WidgetState + @Mock private lateinit var controlWidgetState: WidgetState + @Mock private lateinit var mediaTitleWidgetState: WidgetState + @Mock private lateinit var mediaContainerWidgetState: WidgetState + + val delta = 0.0001F + + private lateinit var mediaViewController: MediaViewController + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + mediaViewController = + MediaViewController(context, configurationController, mediaHostStatesManager, logger) + } + + @Test + fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() { + mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) + player.measureState = TransitionViewState().apply { this.height = 100 } + mediaHostStateHolder.expansion = 1f + val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + mediaHostStateHolder.measurementInput = + MeasurementInput(widthMeasureSpec, heightMeasureSpec) + + // Test no squish + mediaHostStateHolder.squishFraction = 1f + assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100) + + // Test half squish + mediaHostStateHolder.squishFraction = 0.5f + assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50) + } + + @Test + fun testObtainViewState_applySquishFraction_toRecommendationTransitionViewState_height() { + mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION) + recommendation.measureState = TransitionViewState().apply { this.height = 100 } + mediaHostStateHolder.expansion = 1f + val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + mediaHostStateHolder.measurementInput = + MeasurementInput(widthMeasureSpec, heightMeasureSpec) + + // Test no squish + mediaHostStateHolder.squishFraction = 1f + assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100) + + // Test half squish + mediaHostStateHolder.squishFraction = 0.5f + assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50) + } + + @Test + fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forMediaPlayer() { + whenever(mockViewState.copy()).thenReturn(mockCopiedState) + whenever(mockCopiedState.widgetStates) + .thenReturn( + mutableMapOf( + R.id.media_progress_bar to controlWidgetState, + R.id.header_artist to detailWidgetState + ) + ) + + val detailSquishMiddle = + TRANSFORM_BEZIER.getInterpolation( + (DETAILS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION + ) + mediaViewController.squishViewState(mockViewState, detailSquishMiddle) + verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } + + val detailSquishEnd = + TRANSFORM_BEZIER.getInterpolation((DETAILS_DELAY + DURATION) / ANIMATION_BASE_DURATION) + mediaViewController.squishViewState(mockViewState, detailSquishEnd) + verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } + + val controlSquishMiddle = + TRANSFORM_BEZIER.getInterpolation( + (CONTROLS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION + ) + mediaViewController.squishViewState(mockViewState, controlSquishMiddle) + verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } + + val controlSquishEnd = + TRANSFORM_BEZIER.getInterpolation((CONTROLS_DELAY + DURATION) / ANIMATION_BASE_DURATION) + mediaViewController.squishViewState(mockViewState, controlSquishEnd) + verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } + } + + @Test + fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() { + whenever(mockViewState.copy()).thenReturn(mockCopiedState) + whenever(mockCopiedState.widgetStates) + .thenReturn( + mutableMapOf( + R.id.media_title1 to mediaTitleWidgetState, + R.id.media_cover1_container to mediaContainerWidgetState + ) + ) + + val containerSquishMiddle = + TRANSFORM_BEZIER.getInterpolation( + (MEDIACONTAINERS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION + ) + mediaViewController.squishViewState(mockViewState, containerSquishMiddle) + verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } + + val containerSquishEnd = + TRANSFORM_BEZIER.getInterpolation( + (MEDIACONTAINERS_DELAY + DURATION) / ANIMATION_BASE_DURATION + ) + mediaViewController.squishViewState(mockViewState, containerSquishEnd) + verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } + + val titleSquishMiddle = + TRANSFORM_BEZIER.getInterpolation( + (MEDIATITLES_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION + ) + mediaViewController.squishViewState(mockViewState, titleSquishMiddle) + verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } + + val titleSquishEnd = + TRANSFORM_BEZIER.getInterpolation( + (MEDIATITLES_DELAY + DURATION) / ANIMATION_BASE_DURATION + ) + mediaViewController.squishViewState(mockViewState, titleSquishEnd) + verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index 2f52950a9ee4..af530163e289 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -73,7 +73,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testOnMediaDataLoaded_complicationAddition() { - final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, + final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); @@ -94,7 +94,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testOnMediaDataRemoved_complicationRemoval() { - final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, + final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); @@ -114,7 +114,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testOnMediaDataLoaded_complicationRemoval() { - final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, + final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); @@ -139,7 +139,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { public void testOnMediaDataLoaded_mediaComplicationDisabled_doesNotAddComplication() { when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(false); - final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, + final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); 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/common/MediaTttLoggerTest.kt index 1078cdaa57c4..e009e8651f2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt @@ -19,9 +19,9 @@ 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.LogBuffer import com.android.systemui.log.LogBufferFactory -import com.android.systemui.log.LogcatEchoTracker +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 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 7c83cb74bb77..6a4c0f60466d 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 @@ -22,6 +22,9 @@ import android.graphics.drawable.Drawable import androidx.test.filters.SmallTest import com.android.systemui.R 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.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -62,6 +65,34 @@ class MediaTttUtilsTest : SysuiTestCase() { } @Test + fun getIconFromPackageName_nullPackageName_returnsDefault() { + val icon = MediaTttUtils.getIconFromPackageName(context, appPackageName = null, logger) + + val expectedDesc = + ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name) + .loadContentDescription(context) + assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc) + } + + @Test + fun getIconFromPackageName_invalidPackageName_returnsDefault() { + val icon = MediaTttUtils.getIconFromPackageName(context, "fakePackageName", logger) + + val expectedDesc = + ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name) + .loadContentDescription(context) + assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc) + } + + @Test + fun getIconFromPackageName_validPackageName_returnsAppInfo() { + val icon = MediaTttUtils.getIconFromPackageName(context, PACKAGE_NAME, logger) + + assertThat(icon) + .isEqualTo(Icon.Loaded(appIconFromPackageName, ContentDescription.Loaded(APP_NAME))) + } + + @Test fun getIconInfoFromPackageName_nullPackageName_returnsDefault() { val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger) 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 9577274eef8d..8c3ae3d01f1d 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 @@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase +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 @@ -49,6 +50,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -70,6 +72,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController @Mock + private lateinit var mediaTttFlags: MediaTttFlags + @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var viewUtil: ViewUtil @@ -85,6 +89,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true) fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!! whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable) @@ -107,6 +112,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { configurationController, powerManager, Handler.getMain(), + mediaTttFlags, receiverUiEventLogger, viewUtil, ) @@ -118,6 +124,30 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { } @Test + fun commandQueueCallback_flagOff_noCallbackAdded() { + reset(commandQueue) + whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false) + + controllerReceiver = MediaTttChipControllerReceiver( + commandQueue, + context, + logger, + windowManager, + FakeExecutor(FakeSystemClock()), + accessibilityManager, + configurationController, + powerManager, + Handler.getMain(), + mediaTttFlags, + receiverUiEventLogger, + viewUtil, + ) + controllerReceiver.start() + + verify(commandQueue, never()).addCallback(any()) + } + + @Test fun commandQueueCallback_closeToSender_triggersChip() { val appName = "FakeAppName" commandQueueCallback.updateMediaTapToTransferReceiverDisplay( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt deleted file mode 100644 index 3a8a51d42f77..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ /dev/null @@ -1,868 +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.media.taptotransfer.sender - -import android.app.StatusBarManager -import android.content.Context -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable -import android.media.MediaRoute2Info -import android.os.PowerManager -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import android.view.accessibility.AccessibilityManager -import android.widget.ImageView -import android.widget.TextView -import androidx.test.filters.SmallTest -import com.android.internal.logging.testing.UiEventLoggerFake -import com.android.internal.statusbar.IUndoMediaTransferCallback -import com.android.systemui.R -import com.android.systemui.SysuiTestCase -import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.media.taptotransfer.common.MediaTttLogger -import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.util.concurrency.DelayableExecutor -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.time.FakeSystemClock -import com.android.systemui.util.view.ViewUtil -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.ArgumentMatchers.anyInt -import org.mockito.Mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper -class MediaTttChipControllerSenderTest : SysuiTestCase() { - private lateinit var controllerSender: TestMediaTttChipControllerSender - - @Mock - private lateinit var packageManager: PackageManager - @Mock - private lateinit var applicationInfo: ApplicationInfo - @Mock - private lateinit var logger: MediaTttLogger - @Mock - private lateinit var accessibilityManager: AccessibilityManager - @Mock - private lateinit var configurationController: ConfigurationController - @Mock - private lateinit var powerManager: PowerManager - @Mock - private lateinit var windowManager: WindowManager - @Mock - private lateinit var commandQueue: CommandQueue - @Mock - private lateinit var falsingManager: FalsingManager - @Mock - private lateinit var falsingCollector: FalsingCollector - @Mock - private lateinit var viewUtil: ViewUtil - private lateinit var commandQueueCallback: CommandQueue.Callbacks - private lateinit var fakeAppIconDrawable: Drawable - private lateinit var fakeClock: FakeSystemClock - private lateinit var fakeExecutor: FakeExecutor - private lateinit var uiEventLoggerFake: UiEventLoggerFake - private lateinit var senderUiEventLogger: MediaTttSenderUiEventLogger - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!! - whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) - whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable) - whenever(packageManager.getApplicationInfo( - eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>() - )).thenReturn(applicationInfo) - context.setMockPackageManager(packageManager) - - fakeClock = FakeSystemClock() - fakeExecutor = FakeExecutor(fakeClock) - - uiEventLoggerFake = UiEventLoggerFake() - senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake) - - whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT) - - controllerSender = TestMediaTttChipControllerSender( - commandQueue, - context, - logger, - windowManager, - fakeExecutor, - accessibilityManager, - configurationController, - powerManager, - senderUiEventLogger, - falsingManager, - falsingCollector, - viewUtil, - ) - controllerSender.start() - - val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) - verify(commandQueue).addCallback(callbackCaptor.capture()) - commandQueueCallback = callbackCaptor.value!! - } - - @Test - fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, - routeInfo, - null - ) - - assertThat(getChipView().getChipText()).isEqualTo( - almostCloseToStartCast().state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id - ) - } - - @Test - fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, - routeInfo, - null - ) - - assertThat(getChipView().getChipText()).isEqualTo( - almostCloseToEndCast().state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id - ) - } - - @Test - fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, - routeInfo, - null - ) - - assertThat(getChipView().getChipText()).isEqualTo( - transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id - ) - } - - @Test - fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, - routeInfo, - null - ) - - assertThat(getChipView().getChipText()).isEqualTo( - transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id - ) - } - - @Test - fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, - routeInfo, - null - ) - - assertThat(getChipView().getChipText()).isEqualTo( - transferToReceiverSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id - ) - } - - @Test - fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, - routeInfo, - null - ) - - assertThat(getChipView().getChipText()).isEqualTo( - transferToThisDeviceSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id - ) - } - - @Test - fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, - routeInfo, - null - ) - - assertThat(getChipView().getChipText()).isEqualTo( - transferToReceiverFailed().state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id - ) - } - - @Test - fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, - routeInfo, - null - ) - - assertThat(getChipView().getChipText()).isEqualTo( - transferToThisDeviceFailed().state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id - ) - } - - @Test - fun commandQueueCallback_farFromReceiver_noChipShown() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, - routeInfo, - null - ) - - verify(windowManager, never()).addView(any(), any()) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER.id - ) - } - - @Test - fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, - routeInfo, - null - ) - - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, - routeInfo, - null - ) - - val viewCaptor = ArgumentCaptor.forClass(View::class.java) - verify(windowManager).addView(viewCaptor.capture(), any()) - verify(windowManager).removeView(viewCaptor.value) - } - - @Test - fun commandQueueCallback_invalidStateParam_noChipShown() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - 100, - routeInfo, - null - ) - - verify(windowManager, never()).addView(any(), any()) - } - - @Test - fun receivesNewStateFromCommandQueue_isLogged() { - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, - routeInfo, - null - ) - - verify(logger).logStateChange(any(), any(), any()) - } - - @Test - fun almostCloseToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() { - val state = almostCloseToStartCast() - controllerSender.displayView(state) - - val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) - assertThat(chipView.getChipText()).isEqualTo( - state.state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) - assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) - } - - @Test - fun almostCloseToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() { - val state = almostCloseToEndCast() - controllerSender.displayView(state) - - val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) - assertThat(chipView.getChipText()).isEqualTo( - state.state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) - assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) - } - - @Test - fun transferToReceiverTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() { - val state = transferToReceiverTriggered() - controllerSender.displayView(state) - - val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) - assertThat(chipView.getChipText()).isEqualTo( - state.state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) - assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) - } - - @Test - fun transferToThisDeviceTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() { - val state = transferToThisDeviceTriggered() - controllerSender.displayView(state) - - val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) - assertThat(chipView.getChipText()).isEqualTo( - state.state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) - assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) - } - - @Test - fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { - val state = transferToReceiverSucceeded() - controllerSender.displayView(state) - - val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) - assertThat(chipView.getChipText()).isEqualTo( - state.state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) - assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) - } - - @Test - fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() { - controllerSender.displayView(transferToReceiverSucceeded(undoCallback = null)) - - val chipView = getChipView() - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) - } - - @Test - fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() { - val undoCallback = object : IUndoMediaTransferCallback.Stub() { - override fun onUndoTriggered() {} - } - controllerSender.displayView(transferToReceiverSucceeded(undoCallback)) - - val chipView = getChipView() - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE) - assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue() - } - - @Test - fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() { - var undoCallbackCalled = false - val undoCallback = object : IUndoMediaTransferCallback.Stub() { - override fun onUndoTriggered() { - undoCallbackCalled = true - } - } - - controllerSender.displayView(transferToReceiverSucceeded(undoCallback)) - getChipView().getUndoButton().performClick() - - assertThat(undoCallbackCalled).isTrue() - } - - @Test - fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() { - whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true) - var undoCallbackCalled = false - val undoCallback = object : IUndoMediaTransferCallback.Stub() { - override fun onUndoTriggered() { - undoCallbackCalled = true - } - } - - controllerSender.displayView(transferToReceiverSucceeded(undoCallback)) - getChipView().getUndoButton().performClick() - - assertThat(undoCallbackCalled).isFalse() - } - - @Test - fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() { - whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false) - var undoCallbackCalled = false - val undoCallback = object : IUndoMediaTransferCallback.Stub() { - override fun onUndoTriggered() { - undoCallbackCalled = true - } - } - - controllerSender.displayView(transferToReceiverSucceeded(undoCallback)) - getChipView().getUndoButton().performClick() - - assertThat(undoCallbackCalled).isTrue() - } - - @Test - fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() { - val undoCallback = object : IUndoMediaTransferCallback.Stub() { - override fun onUndoTriggered() {} - } - controllerSender.displayView(transferToReceiverSucceeded(undoCallback)) - - getChipView().getUndoButton().performClick() - - assertThat(getChipView().getChipText()).isEqualTo( - transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id - ) - } - - @Test - fun transferToThisDeviceSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { - val state = transferToThisDeviceSucceeded() - controllerSender.displayView(state) - - val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) - assertThat(chipView.getChipText()).isEqualTo( - state.state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) - assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) - } - - @Test - fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() { - controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback = null)) - - val chipView = getChipView() - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) - } - - @Test - fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() { - val undoCallback = object : IUndoMediaTransferCallback.Stub() { - override fun onUndoTriggered() {} - } - controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback)) - - val chipView = getChipView() - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE) - assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue() - } - - @Test - fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() { - var undoCallbackCalled = false - val undoCallback = object : IUndoMediaTransferCallback.Stub() { - override fun onUndoTriggered() { - undoCallbackCalled = true - } - } - - controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback)) - getChipView().getUndoButton().performClick() - - assertThat(undoCallbackCalled).isTrue() - } - - @Test - fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() { - val undoCallback = object : IUndoMediaTransferCallback.Stub() { - override fun onUndoTriggered() {} - } - controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback)) - - getChipView().getUndoButton().performClick() - - assertThat(getChipView().getChipText()).isEqualTo( - transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id - ) - } - - @Test - fun transferToReceiverFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() { - val state = transferToReceiverFailed() - controllerSender.displayView(state) - - val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) - assertThat(getChipView().getChipText()).isEqualTo( - state.state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) - assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE) - } - - @Test - fun transferToThisDeviceFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() { - val state = transferToThisDeviceFailed() - controllerSender.displayView(state) - - val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) - assertThat(getChipView().getChipText()).isEqualTo( - state.state.getChipTextString(context, OTHER_DEVICE_NAME) - ) - assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) - assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE) - } - - @Test - fun changeFromAlmostCloseToStartToTransferTriggered_loadingIconAppears() { - controllerSender.displayView(almostCloseToStartCast()) - controllerSender.displayView(transferToReceiverTriggered()) - - assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE) - } - - @Test - fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() { - controllerSender.displayView(transferToReceiverTriggered()) - controllerSender.displayView(transferToReceiverSucceeded()) - - assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE) - } - - @Test - fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() { - controllerSender.displayView(transferToReceiverTriggered()) - controllerSender.displayView( - transferToReceiverSucceeded( - object : IUndoMediaTransferCallback.Stub() { - override fun onUndoTriggered() {} - } - ) - ) - - assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE) - } - - @Test - fun changeFromTransferSucceededToAlmostCloseToStart_undoButtonDisappears() { - controllerSender.displayView(transferToReceiverSucceeded()) - controllerSender.displayView(almostCloseToStartCast()) - - assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE) - } - - @Test - fun changeFromTransferTriggeredToTransferFailed_failureIconAppears() { - controllerSender.displayView(transferToReceiverTriggered()) - controllerSender.displayView(transferToReceiverFailed()) - - assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE) - } - - @Test - fun transferToReceiverTriggeredThenRemoveView_viewStillDisplayed() { - controllerSender.displayView(transferToReceiverTriggered()) - fakeClock.advanceTime(1000L) - - controllerSender.removeView("fakeRemovalReason") - fakeExecutor.runAllReady() - - verify(windowManager, never()).removeView(any()) - verify(logger).logRemovalBypass(any(), any()) - } - - @Test - fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayed() { - controllerSender.displayView(transferToReceiverTriggered()) - - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, - routeInfo, - null - ) - fakeExecutor.runAllReady() - - verify(windowManager, never()).removeView(any()) - verify(logger).logRemovalBypass(any(), any()) - } - - @Test - fun transferToReceiverTriggeredThenRemoveView_eventuallyTimesOut() { - controllerSender.displayView(transferToReceiverTriggered()) - - controllerSender.removeView("fakeRemovalReason") - fakeClock.advanceTime(TIMEOUT + 1L) - - verify(windowManager).removeView(any()) - } - - @Test - fun transferToThisDeviceTriggeredThenRemoveView_viewStillDisplayed() { - controllerSender.displayView(transferToThisDeviceTriggered()) - fakeClock.advanceTime(1000L) - - controllerSender.removeView("fakeRemovalReason") - fakeExecutor.runAllReady() - - verify(windowManager, never()).removeView(any()) - verify(logger).logRemovalBypass(any(), any()) - } - - @Test - fun transferToThisDeviceTriggeredThenRemoveView_eventuallyTimesOut() { - controllerSender.displayView(transferToThisDeviceTriggered()) - - controllerSender.removeView("fakeRemovalReason") - fakeClock.advanceTime(TIMEOUT + 1L) - - verify(windowManager).removeView(any()) - } - - @Test - fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayed() { - controllerSender.displayView(transferToThisDeviceTriggered()) - - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, - routeInfo, - null - ) - fakeExecutor.runAllReady() - - verify(windowManager, never()).removeView(any()) - verify(logger).logRemovalBypass(any(), any()) - } - - @Test - fun transferToReceiverSucceededThenRemoveView_viewStillDisplayed() { - controllerSender.displayView(transferToReceiverSucceeded()) - - controllerSender.removeView("fakeRemovalReason") - fakeExecutor.runAllReady() - - verify(windowManager, never()).removeView(any()) - verify(logger).logRemovalBypass(any(), any()) - } - - @Test - fun transferToReceiverSucceededThenRemoveView_eventuallyTimesOut() { - controllerSender.displayView(transferToReceiverSucceeded()) - - controllerSender.removeView("fakeRemovalReason") - fakeClock.advanceTime(TIMEOUT + 1L) - - verify(windowManager).removeView(any()) - } - - @Test - fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayed() { - controllerSender.displayView(transferToReceiverSucceeded()) - - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, - routeInfo, - null - ) - fakeExecutor.runAllReady() - - verify(windowManager, never()).removeView(any()) - verify(logger).logRemovalBypass(any(), any()) - } - - @Test - fun transferToThisDeviceSucceededThenRemoveView_viewStillDisplayed() { - controllerSender.displayView(transferToThisDeviceSucceeded()) - - controllerSender.removeView("fakeRemovalReason") - fakeExecutor.runAllReady() - - verify(windowManager, never()).removeView(any()) - verify(logger).logRemovalBypass(any(), any()) - } - - @Test - fun transferToThisDeviceSucceededThenRemoveView_eventuallyTimesOut() { - controllerSender.displayView(transferToThisDeviceSucceeded()) - - controllerSender.removeView("fakeRemovalReason") - fakeClock.advanceTime(TIMEOUT + 1L) - - verify(windowManager).removeView(any()) - } - - @Test - fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayed() { - controllerSender.displayView(transferToThisDeviceSucceeded()) - - commandQueueCallback.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, - routeInfo, - null - ) - fakeExecutor.runAllReady() - - verify(windowManager, never()).removeView(any()) - verify(logger).logRemovalBypass(any(), any()) - } - - private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) - - private fun ViewGroup.getChipText(): String = - (this.requireViewById<TextView>(R.id.text)).text as String - - private fun ViewGroup.getLoadingIconVisibility(): Int = - this.requireViewById<View>(R.id.loading).visibility - - private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.undo) - - private fun ViewGroup.getFailureIcon(): View = this.requireViewById(R.id.failure_icon) - - private fun getChipView(): ViewGroup { - val viewCaptor = ArgumentCaptor.forClass(View::class.java) - verify(windowManager).addView(viewCaptor.capture(), any()) - return viewCaptor.value as ViewGroup - } - - /** Helper method providing default parameters to not clutter up the tests. */ - private fun almostCloseToStartCast() = - ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST, routeInfo) - - /** Helper method providing default parameters to not clutter up the tests. */ - private fun almostCloseToEndCast() = - ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST, routeInfo) - - /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferToReceiverTriggered() = - ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo) - - /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferToThisDeviceTriggered() = - ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo) - - /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) = - ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, undoCallback) - - /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) = - ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, undoCallback) - - /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferToReceiverFailed() = - ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo) - - /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferToThisDeviceFailed() = - ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo) - - private class TestMediaTttChipControllerSender( - commandQueue: CommandQueue, - context: Context, - @MediaTttReceiverLogger logger: MediaTttLogger, - windowManager: WindowManager, - mainExecutor: DelayableExecutor, - accessibilityManager: AccessibilityManager, - configurationController: ConfigurationController, - powerManager: PowerManager, - uiEventLogger: MediaTttSenderUiEventLogger, - falsingManager: FalsingManager, - falsingCollector: FalsingCollector, - viewUtil: ViewUtil, - ) : MediaTttChipControllerSender( - commandQueue, - context, - logger, - windowManager, - mainExecutor, - accessibilityManager, - configurationController, - powerManager, - uiEventLogger, - falsingManager, - falsingCollector, - viewUtil, - ) { - override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { - // Just bypass the animation in tests - onAnimationEnd.run() - } - } -} - -private const val APP_NAME = "Fake app name" -private const val OTHER_DEVICE_NAME = "My Tablet" -private const val PACKAGE_NAME = "com.android.systemui" -private const val TIMEOUT = 10000 - -private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME) - .addFeature("feature") - .setClientPackageName(PACKAGE_NAME) - .build() 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 new file mode 100644 index 000000000000..f977f55db483 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -0,0 +1,688 @@ +/* + * 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 android.app.StatusBarManager +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.media.MediaRoute2Info +import android.os.PowerManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.view.accessibility.AccessibilityManager +import android.widget.ImageView +import android.widget.TextView +import androidx.test.filters.SmallTest +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.internal.statusbar.IUndoMediaTransferCallback +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.common.shared.model.Text.Companion.loadText +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.policy.ConfigurationController +import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator +import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.util.view.ViewUtil +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.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class MediaTttSenderCoordinatorTest : SysuiTestCase() { + + // Note: This tests are a bit like integration tests because they use a real instance of + // [ChipbarCoordinator] and verify that the coordinator displays the correct view, based on + // the inputs from [MediaTttSenderCoordinator]. + + private lateinit var underTest: MediaTttSenderCoordinator + + @Mock private lateinit var accessibilityManager: AccessibilityManager + @Mock private lateinit var applicationInfo: ApplicationInfo + @Mock private lateinit var commandQueue: CommandQueue + @Mock private lateinit var configurationController: ConfigurationController + @Mock private lateinit var falsingManager: FalsingManager + @Mock private lateinit var falsingCollector: FalsingCollector + @Mock private lateinit var logger: MediaTttLogger + @Mock private lateinit var mediaTttFlags: MediaTttFlags + @Mock private lateinit var packageManager: PackageManager + + @Mock private lateinit var powerManager: PowerManager + @Mock private lateinit var viewUtil: ViewUtil + @Mock private lateinit var windowManager: WindowManager + private lateinit var chipbarCoordinator: ChipbarCoordinator + private lateinit var commandQueueCallback: CommandQueue.Callbacks + private lateinit var fakeAppIconDrawable: Drawable + private lateinit var fakeClock: FakeSystemClock + private lateinit var fakeExecutor: FakeExecutor + private lateinit var uiEventLoggerFake: UiEventLoggerFake + private lateinit var uiEventLogger: MediaTttSenderUiEventLogger + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true) + whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT) + + fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!! + whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) + whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable) + whenever( + packageManager.getApplicationInfo( + eq(PACKAGE_NAME), + any<PackageManager.ApplicationInfoFlags>() + ) + ) + .thenReturn(applicationInfo) + context.setMockPackageManager(packageManager) + + fakeClock = FakeSystemClock() + fakeExecutor = FakeExecutor(fakeClock) + + uiEventLoggerFake = UiEventLoggerFake() + uiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake) + + chipbarCoordinator = + FakeChipbarCoordinator( + context, + logger, + windowManager, + fakeExecutor, + accessibilityManager, + configurationController, + powerManager, + falsingManager, + falsingCollector, + viewUtil, + ) + chipbarCoordinator.start() + + underTest = + MediaTttSenderCoordinator( + chipbarCoordinator, + commandQueue, + context, + logger, + mediaTttFlags, + uiEventLogger, + ) + underTest.start() + + val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) + verify(commandQueue).addCallback(callbackCaptor.capture()) + commandQueueCallback = callbackCaptor.value!! + } + + @Test + fun commandQueueCallback_flagOff_noCallbackAdded() { + reset(commandQueue) + whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false) + underTest = + MediaTttSenderCoordinator( + chipbarCoordinator, + commandQueue, + context, + logger, + mediaTttFlags, + uiEventLogger, + ) + underTest.start() + + verify(commandQueue, never()).addCallback(any()) + } + + @Test + fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + null + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME) + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) + + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id) + } + + @Test + fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + routeInfo, + null + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME) + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) + + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id) + } + + @Test + fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfo, + null + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME) + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) + + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id) + } + + @Test + fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + routeInfo, + null + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME) + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) + + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id) + } + + @Test + fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + null + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME) + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) + + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id) + } + + @Test + fun transferToReceiverSucceeded_nullUndoCallback_noUndo() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + /* undoCallback= */ null + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) + } + + @Test + fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() {} + }, + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE) + assertThat(chipbarView.getUndoButton().hasOnClickListeners()).isTrue() + } + + @Test + fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() { + var undoCallbackCalled = false + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() { + undoCallbackCalled = true + } + }, + ) + + getChipbarView().getUndoButton().performClick() + + // Event index 1 since initially displaying the succeeded chip would also log an event + assertThat(uiEventLoggerFake.eventId(1)) + .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id) + assertThat(undoCallbackCalled).isTrue() + assertThat(getChipbarView().getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText()) + } + + @Test + fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + null + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME) + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) + + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id) + } + + @Test + fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + /* undoCallback= */ null + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) + } + + @Test + fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() {} + }, + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE) + assertThat(chipbarView.getUndoButton().hasOnClickListeners()).isTrue() + } + + @Test + fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() { + var undoCallbackCalled = false + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() { + undoCallbackCalled = true + } + }, + ) + + getChipbarView().getUndoButton().performClick() + + // Event index 1 since initially displaying the succeeded chip would also log an event + assertThat(uiEventLoggerFake.eventId(1)) + .isEqualTo( + MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id + ) + assertThat(undoCallbackCalled).isTrue() + assertThat(getChipbarView().getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText()) + } + + @Test + fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, + routeInfo, + null + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME) + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE) + + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id) + } + + @Test + fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, + routeInfo, + null + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME) + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE) + + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id) + } + + @Test + fun commandQueueCallback_farFromReceiver_noChipShown() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + + verify(windowManager, never()).addView(any(), any()) + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER.id) + } + + @Test + fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + null + ) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + + val viewCaptor = ArgumentCaptor.forClass(View::class.java) + verify(windowManager).addView(viewCaptor.capture(), any()) + verify(windowManager).removeView(viewCaptor.value) + } + + @Test + fun commandQueueCallback_invalidStateParam_noChipShown() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay(100, routeInfo, null) + + verify(windowManager, never()).addView(any(), any()) + } + + @Test + fun receivesNewStateFromCommandQueue_isLogged() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + null + ) + + verify(logger).logStateChange(any(), any(), any()) + } + + @Test + fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayedButStillTimesOut() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfo, + null + ) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + routeInfo, + null + ) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + null + ) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + null + ) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() {} + }, + ) + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText()) + + // Because [MediaTttSenderCoordinator] internally creates the undo callback, we should + // verify that the new state it triggers operates just like any other state. + getChipbarView().getUndoButton().performClick() + fakeExecutor.runAllReady() + + // Verify that the click updated us to the triggered state + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + // Verify that we didn't remove the chipbar because it's in the triggered state + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + + fakeClock.advanceTime(TIMEOUT + 1L) + + // Verify we eventually remove the chipbar + verify(windowManager).removeView(any()) + } + + @Test + fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() {} + }, + ) + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText()) + + // Because [MediaTttSenderCoordinator] internally creates the undo callback, we should + // verify that the new state it triggers operates just like any other state. + getChipbarView().getUndoButton().performClick() + fakeExecutor.runAllReady() + + // Verify that the click updated us to the triggered state + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + // Verify that we didn't remove the chipbar because it's in the triggered state + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + + fakeClock.advanceTime(TIMEOUT + 1L) + + // Verify we eventually remove the chipbar + verify(windowManager).removeView(any()) + } + + private fun getChipbarView(): ViewGroup { + val viewCaptor = ArgumentCaptor.forClass(View::class.java) + verify(windowManager).addView(viewCaptor.capture(), any()) + return viewCaptor.value as ViewGroup + } + + private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.start_icon) + + private fun ViewGroup.getChipText(): String = + (this.requireViewById<TextView>(R.id.text)).text as String + + private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading) + + private fun ViewGroup.getErrorIcon(): View = this.requireViewById(R.id.error) + + 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 const val APP_NAME = "Fake app name" +private const val OTHER_DEVICE_NAME = "My Tablet" +private const val PACKAGE_NAME = "com.android.systemui" +private const val TIMEOUT = 10000 + +private val routeInfo = + MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME) + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java index 0badd861787d..1bc4719c70b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java @@ -147,6 +147,18 @@ public class ColorSchemeTest extends SysuiTestCase { } @Test + public void testMonochromatic() { + int colorInt = 0xffB3588A; // H350 C50 T50 + ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */, + Style.MONOCHROMATIC /* style */); + int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2); + Assert.assertTrue( + Color.red(neutralMid) == Color.green(neutralMid) + && Color.green(neutralMid) == Color.blue(neutralMid) + ); + } + + @Test @SuppressWarnings("ResultOfMethodCallIgnored") public void testToString() { new ColorScheme(Color.TRANSPARENT, false /* darkTheme */).toString(); 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 0e9d2799dddb..6adce7a827b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -80,6 +80,7 @@ import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.buttons.ButtonDispatcher; import com.android.systemui.navigationbar.buttons.DeadZone; @@ -197,6 +198,8 @@ public class NavigationBarTest extends SysuiTestCase { @Mock private UserContextProvider mUserContextProvider; @Mock + private WakefulnessLifecycle mWakefulnessLifecycle; + @Mock private Resources mResources; private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake(); @@ -474,7 +477,8 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBarTransitions, mEdgeBackGestureHandler, Optional.of(mock(BackAnimation.class)), - mUserContextProvider)); + mUserContextProvider, + mWakefulnessLifecycle)); } private void processAllMessages() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index 4e9b2325b899..c377c374148f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -46,6 +46,7 @@ import com.android.settingslib.fuelgauge.Estimate; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.power.PowerUI.WarningsUI; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -84,6 +85,7 @@ public class PowerUITest extends SysuiTestCase { private PowerUI mPowerUI; @Mock private EnhancedEstimates mEnhancedEstimates; @Mock private PowerManager mPowerManager; + @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private IThermalService mThermalServiceMock; private IThermalEventListener mUsbThermalEventListener; private IThermalEventListener mSkinThermalEventListener; @@ -680,7 +682,7 @@ public class PowerUITest extends SysuiTestCase { private void createPowerUi() { mPowerUI = new PowerUI( mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy, - mMockWarnings, mEnhancedEstimates, mPowerManager); + mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager); mPowerUI.mThermalService = mThermalServiceMock; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt index e2c6ff996199..d6db62aa2f72 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt @@ -20,7 +20,7 @@ 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.log.LogcatEchoTracker +import com.android.systemui.plugins.log.LogcatEchoTracker import com.android.systemui.statusbar.DisableFlagsLogger import com.google.common.truth.Truth.assertThat import org.junit.Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 2a4996f259dc..760bb9bec559 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -192,16 +192,6 @@ class FooterActionsViewModelTest : SysuiTestCase() { // UserManager change. assertThat(iconTint()).isNull() - // Trigger a user info change: there should now be a tint. - userInfoController.updateInfo { userAccount = "doe" } - assertThat(iconTint()) - .isEqualTo( - Utils.getColorAttrDefaultColor( - context, - android.R.attr.colorForeground, - ) - ) - // Make sure we don't tint the icon if it is a user image (and not the default image), even // in guest mode. userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt index da52a9b1a3c2..bc27bbc13f81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.statusbar.policy.UserSwitcherController import com.android.systemui.user.data.source.UserRecord import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -139,6 +140,11 @@ class UserDetailViewAdapterTest : SysuiTestCase() { clickableTest(false, false, mUserDetailItemView, true) } + @Test + fun testManageUsersIsNotAvailable() { + assertNull(adapter.users.find { it.isManageUsers }) + } + private fun createUserRecord(current: Boolean, guest: Boolean) = UserRecord( UserInfo(0 /* id */, "name", 0 /* flags */), diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt new file mode 100644 index 000000000000..b6a595b0077a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt @@ -0,0 +1,103 @@ +/* + * 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.content.ComponentName +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +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.mockito.Mockito.`when` as whenever + +@SmallTest +class ActionIntentCreatorTest : SysuiTestCase() { + + @Test + fun testCreateShareIntent() { + val uri = Uri.parse("content://fake") + val subject = "Example subject" + + val output = ActionIntentCreator.createShareIntent(uri, subject) + + 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)).isEqualTo(subject) + assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) + .isEqualTo(uri) + } + + @Test + fun testCreateShareIntent_noSubject() { + val uri = Uri.parse("content://fake") + val output = ActionIntentCreator.createShareIntent(uri, null) + val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull() + } + + @Test + fun testCreateEditIntent() { + val uri = Uri.parse("content://fake") + val context = mock<Context>() + + val output = ActionIntentCreator.createEditIntent(uri, context) + + assertThat(output.action).isEqualTo(Intent.ACTION_EDIT) + assertThat(output.data).isEqualTo(uri) + assertThat(output.type).isEqualTo("image/png") + assertThat(output.component).isNull() + val expectedFlags = + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK + assertFlagsSet(expectedFlags, output.flags) + } + + @Test + fun testCreateEditIntent_withEditor() { + val uri = Uri.parse("content://fake") + val context = mock<Context>() + var component = ComponentName("com.android.foo", "com.android.foo.Something") + + whenever(context.getString(eq(R.string.config_screenshotEditor))) + .thenReturn(component.flattenToString()) + + val output = ActionIntentCreator.createEditIntent(uri, context) + + assertThat(output.component).isEqualTo(component) + } + + private fun assertFlagsSet(expected: Int, observed: Int) { + assertThat(observed and expected).isEqualTo(expected) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java new file mode 100644 index 000000000000..c6ce51a28dd3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java @@ -0,0 +1,81 @@ +/* + * 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.MotionEvent; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +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) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class DraggableConstraintLayoutTest extends SysuiTestCase { + + @Mock + DraggableConstraintLayout.SwipeDismissCallbacks mCallbacks; + + private DraggableConstraintLayout mDraggableConstraintLayout; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mDraggableConstraintLayout = new DraggableConstraintLayout(mContext, null, 0); + } + + @Test + public void test_dismissDoesNotCallSwipeInitiated() { + mDraggableConstraintLayout.setCallbacks(mCallbacks); + + mDraggableConstraintLayout.dismiss(); + + verify(mCallbacks, never()).onSwipeDismissInitiated(any()); + } + + @Test + public void test_onTouchCallsOnInteraction() { + mDraggableConstraintLayout.setCallbacks(mCallbacks); + + mDraggableConstraintLayout.onInterceptTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); + + verify(mCallbacks).onInteraction(); + } + + @Test + public void test_callbacksNotSet() { + // just test that it doesn't throw an NPE + mDraggableConstraintLayout.onInterceptTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); + mDraggableConstraintLayout.onInterceptHoverEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0)); + mDraggableConstraintLayout.dismiss(); + } +} 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 c76d9e7a2b20..14a3bc147808 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -645,6 +645,65 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { verify(animator).start() } + @Test + fun privacyChipParentVisibleFromStart() { + verify(privacyIconsController).onParentVisible() + } + + @Test + fun privacyChipParentVisibleAlways() { + controller.largeScreenActive = true + controller.largeScreenActive = false + controller.largeScreenActive = true + + verify(privacyIconsController, never()).onParentInvisible() + } + + @Test + fun clockPivotYInCenter() { + val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java) + verify(clock).addOnLayoutChangeListener(capture(captor)) + var height = 100 + val width = 50 + + clock.executeLayoutChange(0, 0, width, height, captor.value) + verify(clock).pivotY = height.toFloat() / 2 + + height = 150 + clock.executeLayoutChange(0, 0, width, height, captor.value) + verify(clock).pivotY = height.toFloat() / 2 + } + + private fun View.executeLayoutChange( + left: Int, + top: Int, + right: Int, + bottom: Int, + listener: View.OnLayoutChangeListener + ) { + val oldLeft = this.left + val oldTop = this.top + val oldRight = this.right + val oldBottom = this.bottom + whenever(this.left).thenReturn(left) + whenever(this.top).thenReturn(top) + whenever(this.right).thenReturn(right) + whenever(this.bottom).thenReturn(bottom) + whenever(this.height).thenReturn(bottom - top) + whenever(this.width).thenReturn(right - left) + listener.onLayoutChange( + this, + oldLeft, + oldTop, + oldRight, + oldBottom, + left, + top, + right, + bottom + ) + } + private fun createWindowInsets( topCutout: Rect? = Rect() ): WindowInsets { 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 37be3439fdc8..d095add1c660 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -33,11 +33,13 @@ 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.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -76,6 +78,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.internal.util.CollectionUtils; import com.android.internal.util.LatencyTracker; +import com.android.keyguard.FaceAuthApiRequestReason; import com.android.keyguard.KeyguardClockSwitch; import com.android.keyguard.KeyguardClockSwitchController; import com.android.keyguard.KeyguardStatusView; @@ -93,7 +96,6 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.camera.CameraGestureHelper; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; -import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; @@ -109,7 +111,7 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; -import com.android.systemui.qrcodescanner.controller.QRCodeScannerController; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSFragment; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.transition.ShadeTransitionController; @@ -165,7 +167,6 @@ import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.SystemClock; -import com.android.systemui.wallet.controller.QuickAccessWalletController; import com.android.wm.shell.animation.FlingAnimationUtils; import org.junit.After; @@ -173,6 +174,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; @@ -257,11 +260,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private KeyguardIndicationController mKeyguardIndicationController; @Mock private FragmentService mFragmentService; @Mock private FragmentHostManager mFragmentHostManager; - @Mock private QuickAccessWalletController mQuickAccessWalletController; - @Mock private QRCodeScannerController mQrCodeScannerController; @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager; @Mock private RecordingController mRecordingController; - @Mock private ControlsComponent mControlsComponent; @Mock private LockscreenGestureLogger mLockscreenGestureLogger; @Mock private DumpManager mDumpManager; @Mock private InteractionJankMonitor mInteractionJankMonitor; @@ -282,6 +282,10 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private ViewTreeObserver mViewTreeObserver; @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; + @Mock private MotionEvent mDownMotionEvent; + @Captor + private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener> + mEmptySpaceClickListenerCaptor; private NotificationPanelViewController.TouchHandler mTouchHandler; private ConfigurationController mConfigurationController; @@ -425,6 +429,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver); when(mView.getParent()).thenReturn(mViewParent); when(mQs.getHeader()).thenReturn(mQsHeader); + when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN); mMainHandler = new Handler(Looper.getMainLooper()); NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter = @@ -512,6 +517,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .addCallback(mNotificationPanelViewController.mStatusBarStateListener); mNotificationPanelViewController .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); + verify(mNotificationStackScrollLayoutController) + .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture()); } @After @@ -716,6 +723,40 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test + public void test_pulsing_onTouchEvent_noTracking() { + // GIVEN device is pulsing + mNotificationPanelViewController.setPulsing(true); + + // WHEN touch DOWN & MOVE events received + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, + 0 /* metaState */)); + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */, + 0 /* metaState */)); + + // THEN touch is NOT tracked (since the device is pulsing) + assertThat(mNotificationPanelViewController.isTracking()).isFalse(); + } + + @Test + public void test_onTouchEvent_startTracking() { + // GIVEN device is NOT pulsing + mNotificationPanelViewController.setPulsing(false); + + // WHEN touch DOWN & MOVE events received + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, + 0 /* metaState */)); + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */, + 0 /* metaState */)); + + // THEN touch is tracked + assertThat(mNotificationPanelViewController.isTracking()).isTrue(); + } + + @Test public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { when(mCommandQueue.panelsEnabled()).thenReturn(false); @@ -1506,6 +1547,103 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { ); } + @Test + public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() { + StatusBarStateController.StateListener statusBarStateListener = + mNotificationPanelViewController.mStatusBarStateListener; + statusBarStateListener.onStateChanged(KEYGUARD); + mNotificationPanelViewController.setDozing(false, false); + + // This sets the dozing state that is read when onMiddleClicked is eventually invoked. + mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); + mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + + verify(mUpdateMonitor).requestFaceAuth(true, + FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); + } + + @Test + public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() { + StatusBarStateController.StateListener statusBarStateListener = + mNotificationPanelViewController.mStatusBarStateListener; + statusBarStateListener.onStateChanged(KEYGUARD); + mNotificationPanelViewController.setDozing(false, false); + when(mUpdateMonitor.isFaceDetectionRunning()).thenReturn(false); + + // This sets the dozing state that is read when onMiddleClicked is eventually invoked. + mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); + mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + + verify(mNotificationStackScrollLayoutController).setUnlockHintRunning(true); + } + + @Test + public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() { + StatusBarStateController.StateListener statusBarStateListener = + mNotificationPanelViewController.mStatusBarStateListener; + statusBarStateListener.onStateChanged(KEYGUARD); + mNotificationPanelViewController.setDozing(false, false); + when(mUpdateMonitor.isFaceDetectionRunning()).thenReturn(true); + + // This sets the dozing state that is read when onMiddleClicked is eventually invoked. + mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); + mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + + verify(mNotificationStackScrollLayoutController, never()).setUnlockHintRunning(true); + } + + @Test + public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() { + StatusBarStateController.StateListener statusBarStateListener = + mNotificationPanelViewController.mStatusBarStateListener; + statusBarStateListener.onStateChanged(KEYGUARD); + mNotificationPanelViewController.setDozing(true, false); + + // This sets the dozing state that is read when onMiddleClicked is eventually invoked. + mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); + mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + + verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString()); + } + + @Test + public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() { + StatusBarStateController.StateListener statusBarStateListener = + mNotificationPanelViewController.mStatusBarStateListener; + statusBarStateListener.onStateChanged(SHADE_LOCKED); + + mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + + verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString()); + + } + + /** + * When shade is flinging to close and this fling is not intercepted, + * {@link AmbientState#setIsClosing(boolean)} should be called before + * {@link NotificationStackScrollLayoutController#onExpansionStopped()} + * to ensure scrollY can be correctly set to be 0 + */ + @Test + public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() { + // Given: Shade is expanded + mNotificationPanelViewController.notifyExpandingFinished(); + mNotificationPanelViewController.setIsClosing(false); + + // When: Shade flings to close not canceled + mNotificationPanelViewController.notifyExpandingStarted(); + mNotificationPanelViewController.setIsClosing(true); + mNotificationPanelViewController.onFlingEnd(false); + + // Then: AmbientState's mIsClosing should be set to false + // before mNotificationStackScrollLayoutController.onExpansionStopped() is called + // to ensure NotificationStackScrollLayout.resetScrollPosition() -> resetScrollPosition + // -> setOwnScrollY(0) can set scrollY to 0 when shade is closed + InOrder inOrder = inOrder(mAmbientState, mNotificationStackScrollLayoutController); + inOrder.verify(mAmbientState).setIsClosing(false); + inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped(); + } + private static MotionEvent createMotionEvent(int x, int y, int action) { return MotionEvent.obtain( /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt index 5b34a95d4fb0..b761647e24e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt @@ -17,58 +17,58 @@ import org.mockito.MockitoAnnotations @SmallTest class UncaughtExceptionPreHandlerTest : SysuiTestCase() { - private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager + private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager - @Mock private lateinit var mockHandler: UncaughtExceptionHandler + @Mock private lateinit var mockHandler: UncaughtExceptionHandler - @Mock private lateinit var mockHandler2: UncaughtExceptionHandler + @Mock private lateinit var mockHandler2: UncaughtExceptionHandler - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - Thread.setUncaughtExceptionPreHandler(null) - preHandlerManager = UncaughtExceptionPreHandlerManager() - } + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + Thread.setUncaughtExceptionPreHandler(null) + preHandlerManager = UncaughtExceptionPreHandlerManager() + } - @Test - fun registerHandler_registersOnceOnly() { - preHandlerManager.registerHandler(mockHandler) - preHandlerManager.registerHandler(mockHandler) - preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) - verify(mockHandler, only()).uncaughtException(any(), any()) - } + @Test + fun registerHandler_registersOnceOnly() { + preHandlerManager.registerHandler(mockHandler) + preHandlerManager.registerHandler(mockHandler) + preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) + verify(mockHandler, only()).uncaughtException(any(), any()) + } - @Test - fun registerHandler_setsUncaughtExceptionPreHandler() { - Thread.setUncaughtExceptionPreHandler(null) - preHandlerManager.registerHandler(mockHandler) - assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull() - } + @Test + fun registerHandler_setsUncaughtExceptionPreHandler() { + Thread.setUncaughtExceptionPreHandler(null) + preHandlerManager.registerHandler(mockHandler) + assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull() + } - @Test - fun registerHandler_preservesOriginalHandler() { - Thread.setUncaughtExceptionPreHandler(mockHandler) - preHandlerManager.registerHandler(mockHandler2) - preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) - verify(mockHandler, only()).uncaughtException(any(), any()) - } + @Test + fun registerHandler_preservesOriginalHandler() { + Thread.setUncaughtExceptionPreHandler(mockHandler) + preHandlerManager.registerHandler(mockHandler2) + preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) + verify(mockHandler, only()).uncaughtException(any(), any()) + } - @Test - @Ignore - fun registerHandler_toleratesHandlersThatThrow() { - `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException()) - preHandlerManager.registerHandler(mockHandler2) - preHandlerManager.registerHandler(mockHandler) - preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) - verify(mockHandler2, only()).uncaughtException(any(), any()) - verify(mockHandler, only()).uncaughtException(any(), any()) - } + @Test + @Ignore + fun registerHandler_toleratesHandlersThatThrow() { + `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException()) + preHandlerManager.registerHandler(mockHandler2) + preHandlerManager.registerHandler(mockHandler) + preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception()) + verify(mockHandler2, only()).uncaughtException(any(), any()) + verify(mockHandler, only()).uncaughtException(any(), any()) + } - @Test - fun registerHandler_doesNotSetUpTwice() { - UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2) - assertThrows(IllegalStateException::class.java) { - preHandlerManager.registerHandler(mockHandler) + @Test + fun registerHandler_doesNotSetUpTwice() { + UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2) + assertThrows(IllegalStateException::class.java) { + preHandlerManager.registerHandler(mockHandler) + } } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt index 8cb530c355bd..5fc0ffe42f55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt @@ -4,7 +4,7 @@ import android.testing.AndroidTestingRunner import android.util.DisplayMetrics import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.phone.LSShadeTransitionLogger import com.android.systemui.statusbar.phone.LockscreenGestureLogger diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index f8a0d2fc415c..9c65fac1af45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -70,7 +70,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.telephony.TelephonyListenerManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java index ed8a3e16cdd1..4bed4a19b3d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java @@ -38,7 +38,7 @@ import android.testing.TestableLooper.RunWithLooper; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.DataUsageController; import com.android.systemui.dump.DumpManager; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.CarrierConfigTracker; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java index a76676e01c15..d5f5105036d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java @@ -43,7 +43,7 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.DataUsageController; import com.android.systemui.R; import com.android.systemui.dump.DumpManager; -import com.android.systemui.log.LogBuffer; +import com.android.systemui.plugins.log.LogBuffer; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.CarrierConfigTracker; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 82e32b2fdc64..09f8a10f88c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -34,10 +34,12 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -135,6 +137,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); allowTestableLooperAsMainThread(); + when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true); mListBuilder = new ShadeListBuilder( mDumpManager, @@ -1995,22 +1998,89 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + public void testActiveOrdering_withLegacyStability() { + when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false); + assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change + assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X + assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change + assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X + assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap + } + + @Test + public void testStableOrdering_withLegacyStability() { + when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false); + mStabilityManager.setAllowEntryReordering(false); + assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change + assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG", false); // X + assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false); // no change + assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG", false); // Z and X + assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG", false); // Z and X + gap + } + + @Test public void testStableOrdering() { + when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true); mStabilityManager.setAllowEntryReordering(false); - assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG"); // X - assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG"); // no change - assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG"); // Z and X - assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG"); // Z and X + gap - verify(mStabilityManager, times(4)).onEntryReorderSuppressed(); + // No input or output + assertOrder("", "", "", true); + // Remove everything + assertOrder("ABCDEFG", "", "", true); + // Literally no changes + assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); + + // No stable order + assertOrder("", "ABCDEFG", "ABCDEFG", true); + + // F moved after A, and... + assertOrder("ABCDEFG", "AFBCDEG", "ABCDEFG", false); // No other changes + assertOrder("ABCDEFG", "AXFBCDEG", "AXBCDEFG", false); // Insert X before F + assertOrder("ABCDEFG", "AFXBCDEG", "AXBCDEFG", false); // Insert X after F + assertOrder("ABCDEFG", "AFBCDEXG", "ABCDEFXG", false); // Insert X where F was + + // B moved after F, and... + assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false); // No other changes + assertOrder("ABCDEFG", "ACDEFXBG", "ABCDEFXG", false); // Insert X before B + assertOrder("ABCDEFG", "ACDEFBXG", "ABCDEFXG", false); // Insert X after B + assertOrder("ABCDEFG", "AXCDEFBG", "AXBCDEFG", false); // Insert X where B was + + // Swap F and B, and... + assertOrder("ABCDEFG", "AFCDEBG", "ABCDEFG", false); // No other changes + assertOrder("ABCDEFG", "AXFCDEBG", "AXBCDEFG", false); // Insert X before F + assertOrder("ABCDEFG", "AFXCDEBG", "AXBCDEFG", false); // Insert X after F + assertOrder("ABCDEFG", "AFCXDEBG", "AXBCDEFG", false); // Insert X between CD (or: ABCXDEFG) + assertOrder("ABCDEFG", "AFCDXEBG", "ABCDXEFG", false); // Insert X between DE (or: ABCDEFXG) + assertOrder("ABCDEFG", "AFCDEXBG", "ABCDEFXG", false); // Insert X before B + assertOrder("ABCDEFG", "AFCDEBXG", "ABCDEFXG", false); // Insert X after B + + // Remove a bunch of entries at once + assertOrder("ABCDEFGHIJKL", "ACEGHI", "ACEGHI", true); + + // Remove a bunch of entries and scramble + assertOrder("ABCDEFGHIJKL", "GCEHAI", "ACEGHI", false); + + // Add a bunch of entries at once + assertOrder("ABCDEFG", "AVBWCXDYZEFG", "AVBWCXDYZEFG", true); + + // Add a bunch of entries and reverse originals + // NOTE: Some of these don't have obviously correct answers + assertOrder("ABCDEFG", "GFEBCDAVWXYZ", "ABCDEFGVWXYZ", false); // appended + assertOrder("ABCDEFG", "VWXYZGFEBCDA", "VWXYZABCDEFG", false); // prepended + assertOrder("ABCDEFG", "GFEBVWXYZCDA", "ABCDEFGVWXYZ", false); // closer to back: append + assertOrder("ABCDEFG", "GFEVWXYZBCDA", "VWXYZABCDEFG", false); // closer to front: prepend + assertOrder("ABCDEFG", "GFEVWBXYZCDA", "VWABCDEFGXYZ", false); // split new entries + + // Swap 2 pairs ("*BC*NO*"->"*NO*CB*"), remove EG, add UVWXYZ throughout + assertOrder("ABCDEFGHIJKLMNOP", "AUNOVDFHWXIJKLMYCBZP", "AUVBCDFHWXIJKLMNOYZP", false); } @Test public void testActiveOrdering() { - assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG"); // X - assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG"); // no change - assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG"); // Z and X - assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG"); // Z and X + gap - verify(mStabilityManager, never()).onEntryReorderSuppressed(); + when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true); + assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X + assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change + assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X + assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap } @Test @@ -2062,6 +2132,52 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + public void stableOrderingDisregardedWithSectionChange() { + when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true); + // GIVEN the first sectioner's packages can be changed from run-to-run + List<String> mutableSectionerPackages = new ArrayList<>(); + mutableSectionerPackages.add(PACKAGE_1); + mListBuilder.setSectioners(asList( + new PackageSectioner(mutableSectionerPackages, null), + new PackageSectioner(List.of(PACKAGE_1, PACKAGE_2, PACKAGE_3), null))); + mStabilityManager.setAllowEntryReordering(false); + + // WHEN the list is originally built with reordering disabled (and section changes allowed) + addNotif(0, PACKAGE_1).setRank(4); + addNotif(1, PACKAGE_1).setRank(5); + addNotif(2, PACKAGE_2).setRank(1); + addNotif(3, PACKAGE_2).setRank(2); + addNotif(4, PACKAGE_3).setRank(3); + dispatchBuild(); + + // VERIFY the order and that entry reordering has not been suppressed + verifyBuiltList( + notif(0), + notif(1), + notif(2), + notif(3), + notif(4) + ); + verify(mStabilityManager, never()).onEntryReorderSuppressed(); + + // WHEN the first section now claims PACKAGE_3 notifications + mutableSectionerPackages.add(PACKAGE_3); + dispatchBuild(); + + // VERIFY the re-sectioned notification is inserted at #1 of the first section, which + // is the correct position based on its rank, rather than #3 in the new section simply + // because it was #3 in its previous section. + verifyBuiltList( + notif(4), + notif(0), + notif(1), + notif(2), + notif(3) + ); + verify(mStabilityManager, never()).onEntryReorderSuppressed(); + } + + @Test public void testStableChildOrdering() { // WHEN the list is originally built with reordering disabled mStabilityManager.setAllowEntryReordering(false); @@ -2112,6 +2228,85 @@ public class ShadeListBuilderTest extends SysuiTestCase { ); } + @Test + public void groupRevertingToSummaryDoesNotRetainStablePositionWithLegacyIndexLogic() { + when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(false); + + // GIVEN a notification group is on screen + mStabilityManager.setAllowEntryReordering(false); + + // WHEN the list is originally built with reordering disabled (and section changes allowed) + addNotif(0, PACKAGE_1).setRank(2); + addNotif(1, PACKAGE_1).setRank(3); + addGroupSummary(2, PACKAGE_1, "group").setRank(4); + addGroupChild(3, PACKAGE_1, "group").setRank(5); + addGroupChild(4, PACKAGE_1, "group").setRank(6); + dispatchBuild(); + + verifyBuiltList( + notif(0), + notif(1), + group( + summary(2), + child(3), + child(4) + ) + ); + + // WHEN the notification summary rank increases and children removed + setNewRank(notif(2).entry, 1); + mEntrySet.remove(4); + mEntrySet.remove(3); + dispatchBuild(); + + // VERIFY the summary (incorrectly) moves to the top of the section where it is ranked, + // despite visual stability being active + verifyBuiltList( + notif(2), + notif(0), + notif(1) + ); + } + + @Test + public void groupRevertingToSummaryRetainsStablePosition() { + when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true); + + // GIVEN a notification group is on screen + mStabilityManager.setAllowEntryReordering(false); + + // WHEN the list is originally built with reordering disabled (and section changes allowed) + addNotif(0, PACKAGE_1).setRank(2); + addNotif(1, PACKAGE_1).setRank(3); + addGroupSummary(2, PACKAGE_1, "group").setRank(4); + addGroupChild(3, PACKAGE_1, "group").setRank(5); + addGroupChild(4, PACKAGE_1, "group").setRank(6); + dispatchBuild(); + + verifyBuiltList( + notif(0), + notif(1), + group( + summary(2), + child(3), + child(4) + ) + ); + + // WHEN the notification summary rank increases and children removed + setNewRank(notif(2).entry, 1); + mEntrySet.remove(4); + mEntrySet.remove(3); + dispatchBuild(); + + // VERIFY the summary stays in the same location on rebuild + verifyBuiltList( + notif(0), + notif(1), + notif(2) + ); + } + private static void setNewRank(NotificationEntry entry, int rank) { entry.setRanking(new RankingBuilder(entry.getRanking()).setRank(rank).build()); } @@ -2255,26 +2450,35 @@ public class ShadeListBuilderTest extends SysuiTestCase { return addGroupChildWithTag(index, packageId, groupId, null); } - private void assertOrder(String visible, String active, String expected) { + private void assertOrder(String visible, String active, String expected, + boolean isOrderedCorrectly) { StringBuilder differenceSb = new StringBuilder(); + NotifSection section = new NotifSection(mock(NotifSectioner.class), 0); for (char c : active.toCharArray()) { if (visible.indexOf(c) < 0) differenceSb.append(c); } String difference = differenceSb.toString(); + int globalIndex = 0; for (int i = 0; i < visible.length(); i++) { - addNotif(i, String.valueOf(visible.charAt(i))) - .setRank(active.indexOf(visible.charAt(i))) + final char c = visible.charAt(i); + // Skip notifications which aren't active anymore + if (!active.contains(String.valueOf(c))) continue; + addNotif(globalIndex++, String.valueOf(c)) + .setRank(active.indexOf(c)) + .setSection(section) .setStableIndex(i); - } - for (int i = 0; i < difference.length(); i++) { - addNotif(i + visible.length(), String.valueOf(difference.charAt(i))) - .setRank(active.indexOf(difference.charAt(i))) + for (char c : difference.toCharArray()) { + addNotif(globalIndex++, String.valueOf(c)) + .setRank(active.indexOf(c)) + .setSection(section) .setStableIndex(-1); } + clearInvocations(mStabilityManager); + dispatchBuild(); StringBuilder resultSb = new StringBuilder(); for (int i = 0; i < expected.length(); i++) { @@ -2284,6 +2488,9 @@ public class ShadeListBuilderTest extends SysuiTestCase { assertEquals("visible [" + visible + "] active [" + active + "]", expected, resultSb.toString()); mEntrySet.clear(); + + verify(mStabilityManager, isOrderedCorrectly ? never() : times(1)) + .onEntryReorderSuppressed(); } private int nextId(String packageName) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt new file mode 100644 index 000000000000..1cdd023dd01c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.listbuilder + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.util.Log +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class SemiStableSortTest : SysuiTestCase() { + + var shuffleInput: Boolean = false + var testStabilizeTo: Boolean = false + var sorter: SemiStableSort? = null + + @Before + fun setUp() { + shuffleInput = false + sorter = null + } + + private fun stringStabilizeTo( + stableOrder: String, + activeOrder: String, + ): Pair<String, Boolean> { + val actives = activeOrder.toMutableList() + val result = mutableListOf<Char>() + return (sorter ?: SemiStableSort()) + .stabilizeTo( + actives, + { ch -> stableOrder.indexOf(ch).takeIf { it >= 0 } }, + result, + ) + .let { ordered -> result.joinToString("") to ordered } + } + + private fun stringSort( + stableOrder: String, + activeOrder: String, + ): Pair<String, Boolean> { + val actives = activeOrder.toMutableList() + if (shuffleInput) { + actives.shuffle() + } + return (sorter ?: SemiStableSort()) + .sort( + actives, + { ch -> stableOrder.indexOf(ch).takeIf { it >= 0 } }, + compareBy { activeOrder.indexOf(it) }, + ) + .let { ordered -> actives.joinToString("") to ordered } + } + + private fun testCase( + stableOrder: String, + activeOrder: String, + expected: String, + expectOrdered: Boolean, + ) { + val (mergeResult, ordered) = + if (testStabilizeTo) stringStabilizeTo(stableOrder, activeOrder) + else stringSort(stableOrder, activeOrder) + val resultPass = expected == mergeResult + val orderedPass = ordered == expectOrdered + val pass = resultPass && orderedPass + val resultSuffix = + if (resultPass) "result=$expected" else "expected=$expected got=$mergeResult" + val orderedSuffix = + if (orderedPass) "ordered=$ordered" else "expected ordered to be $expectOrdered" + val readableResult = "stable=$stableOrder active=$activeOrder $resultSuffix $orderedSuffix" + Log.d("SemiStableSortTest", "${if (pass) "PASS" else "FAIL"}: $readableResult") + if (!pass) { + throw AssertionError("Test case failed: $readableResult") + } + } + + private fun runAllTestCases() { + // No input or output + testCase("", "", "", true) + // Remove everything + testCase("ABCDEFG", "", "", true) + // Literally no changes + testCase("ABCDEFG", "ABCDEFG", "ABCDEFG", true) + + // No stable order + testCase("", "ABCDEFG", "ABCDEFG", true) + + // F moved after A, and... + testCase("ABCDEFG", "AFBCDEG", "ABCDEFG", false) // No other changes + testCase("ABCDEFG", "AXFBCDEG", "AXBCDEFG", false) // Insert X before F + testCase("ABCDEFG", "AFXBCDEG", "AXBCDEFG", false) // Insert X after F + testCase("ABCDEFG", "AFBCDEXG", "ABCDEFXG", false) // Insert X where F was + + // B moved after F, and... + testCase("ABCDEFG", "ACDEFBG", "ABCDEFG", false) // No other changes + testCase("ABCDEFG", "ACDEFXBG", "ABCDEFXG", false) // Insert X before B + testCase("ABCDEFG", "ACDEFBXG", "ABCDEFXG", false) // Insert X after B + testCase("ABCDEFG", "AXCDEFBG", "AXBCDEFG", false) // Insert X where B was + + // Swap F and B, and... + testCase("ABCDEFG", "AFCDEBG", "ABCDEFG", false) // No other changes + testCase("ABCDEFG", "AXFCDEBG", "AXBCDEFG", false) // Insert X before F + testCase("ABCDEFG", "AFXCDEBG", "AXBCDEFG", false) // Insert X after F + testCase("ABCDEFG", "AFCXDEBG", "AXBCDEFG", false) // Insert X between CD (Alt: ABCXDEFG) + testCase("ABCDEFG", "AFCDXEBG", "ABCDXEFG", false) // Insert X between DE (Alt: ABCDEFXG) + testCase("ABCDEFG", "AFCDEXBG", "ABCDEFXG", false) // Insert X before B + testCase("ABCDEFG", "AFCDEBXG", "ABCDEFXG", false) // Insert X after B + + // Remove a bunch of entries at once + testCase("ABCDEFGHIJKL", "ACEGHI", "ACEGHI", true) + + // Remove a bunch of entries and scramble + testCase("ABCDEFGHIJKL", "GCEHAI", "ACEGHI", false) + + // Add a bunch of entries at once + testCase("ABCDEFG", "AVBWCXDYZEFG", "AVBWCXDYZEFG", true) + + // Add a bunch of entries and reverse originals + // NOTE: Some of these don't have obviously correct answers + testCase("ABCDEFG", "GFEBCDAVWXYZ", "ABCDEFGVWXYZ", false) // appended + testCase("ABCDEFG", "VWXYZGFEBCDA", "VWXYZABCDEFG", false) // prepended + testCase("ABCDEFG", "GFEBVWXYZCDA", "ABCDEFGVWXYZ", false) // closer to back: append + testCase("ABCDEFG", "GFEVWXYZBCDA", "VWXYZABCDEFG", false) // closer to front: prepend + testCase("ABCDEFG", "GFEVWBXYZCDA", "VWABCDEFGXYZ", false) // split new entries + + // Swap 2 pairs ("*BC*NO*"->"*NO*CB*"), remove EG, add UVWXYZ throughout + testCase("ABCDEFGHIJKLMNOP", "AUNOVDFHWXIJKLMYCBZP", "AUVBCDFHWXIJKLMNOYZP", false) + } + + @Test + fun testSort() { + testStabilizeTo = false + shuffleInput = false + sorter = null + runAllTestCases() + } + + @Test + fun testSortWithSingleInstance() { + testStabilizeTo = false + shuffleInput = false + sorter = SemiStableSort() + runAllTestCases() + } + + @Test + fun testSortWithShuffledInput() { + testStabilizeTo = false + shuffleInput = true + sorter = null + runAllTestCases() + } + + @Test + fun testStabilizeTo() { + testStabilizeTo = true + sorter = null + runAllTestCases() + } + + @Test + fun testStabilizeToWithSingleInstance() { + testStabilizeTo = true + sorter = SemiStableSort() + runAllTestCases() + } + + @Test + fun testIsSorted() { + val intCmp = Comparator<Int> { x, y -> Integer.compare(x, y) } + SemiStableSort.apply { + assertTrue(emptyList<Int>().isSorted(intCmp)) + assertTrue(listOf(1).isSorted(intCmp)) + assertTrue(listOf(1, 2).isSorted(intCmp)) + assertTrue(listOf(1, 2, 3).isSorted(intCmp)) + assertTrue(listOf(1, 2, 3, 4).isSorted(intCmp)) + assertTrue(listOf(1, 2, 3, 4, 5).isSorted(intCmp)) + assertTrue(listOf(1, 1, 1, 1, 1).isSorted(intCmp)) + assertTrue(listOf(1, 1, 2, 2, 3, 3).isSorted(intCmp)) + assertFalse(listOf(2, 1).isSorted(intCmp)) + assertFalse(listOf(2, 1, 2).isSorted(intCmp)) + assertFalse(listOf(1, 2, 1).isSorted(intCmp)) + assertFalse(listOf(1, 2, 3, 2, 5).isSorted(intCmp)) + assertFalse(listOf(5, 2, 3, 4, 5).isSorted(intCmp)) + assertFalse(listOf(1, 2, 3, 4, 1).isSorted(intCmp)) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt new file mode 100644 index 000000000000..20369546d68a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.listbuilder + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper.getContiguousSubLists +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class ShadeListBuilderHelperTest : SysuiTestCase() { + + @Test + fun testGetContiguousSubLists() { + assertThat(getContiguousSubLists("AAAAAA".toList()) { it }) + .containsExactly( + listOf('A', 'A', 'A', 'A', 'A', 'A'), + ) + .inOrder() + assertThat(getContiguousSubLists("AAABBB".toList()) { it }) + .containsExactly( + listOf('A', 'A', 'A'), + listOf('B', 'B', 'B'), + ) + .inOrder() + assertThat(getContiguousSubLists("AAABAA".toList()) { it }) + .containsExactly( + listOf('A', 'A', 'A'), + listOf('B'), + listOf('A', 'A'), + ) + .inOrder() + assertThat(getContiguousSubLists("AAABAA".toList(), minLength = 2) { it }) + .containsExactly( + listOf('A', 'A', 'A'), + listOf('A', 'A'), + ) + .inOrder() + assertThat(getContiguousSubLists("AAABBBBCCDEEE".toList()) { it }) + .containsExactly( + listOf('A', 'A', 'A'), + listOf('B', 'B', 'B', 'B'), + listOf('C', 'C'), + listOf('D'), + listOf('E', 'E', 'E'), + ) + .inOrder() + assertThat(getContiguousSubLists("AAABBBBCCDEEE".toList(), minLength = 2) { it }) + .containsExactly( + listOf('A', 'A', 'A'), + listOf('B', 'B', 'B', 'B'), + listOf('C', 'C'), + listOf('E', 'E', 'E'), + ) + .inOrder() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java index 3f641df376ed..ca6598726a85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java @@ -91,6 +91,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); + when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(new RowContentBindParams()); + mViewBinder.unbindHeadsUpView(mEntry); verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry)); verifyNoMoreInteractions(mLogger); @@ -139,6 +141,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); + when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(new RowContentBindParams()); + mViewBinder.unbindHeadsUpView(mEntry); verify(mLogger).currentOngoingBindingAborted(eq(mEntry)); verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry)); @@ -150,4 +154,30 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); } + + @Test + public void testLoggingForLateUnbindFlow() { + AtomicReference<NotifBindPipeline.BindCallback> callback = new AtomicReference<>(); + when(mBindStage.requestRebind(any(), any())).then(i -> { + callback.set(i.getArgument(1)); + return new CancellationSignal(); + }); + + mViewBinder.bindHeadsUpView(mEntry, null); + verify(mLogger).startBindingHun(eq(mEntry)); + verifyNoMoreInteractions(mLogger); + clearInvocations(mLogger); + + callback.get().onBindFinished(mEntry); + verify(mLogger).entryBoundSuccessfully(eq(mEntry)); + verifyNoMoreInteractions(mLogger); + clearInvocations(mLogger); + + when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(null); + + mViewBinder.unbindHeadsUpView(mEntry); + verify(mLogger).entryBindStageParamsNullOnUnbind(eq(mEntry)); + verifyNoMoreInteractions(mLogger); + clearInvocations(mLogger); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index d59cc54dfe98..8b7b4dea155f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -29,6 +29,7 @@ import static com.android.systemui.statusbar.notification.collection.EntryUtilKt import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -305,15 +306,59 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { } @Test - public void hideSilentNotificationsPerUserSetting() { - when(mKeyguardStateController.isShowing()).thenReturn(true); + public void hideSilentOnLockscreenSetting() { + // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen + setupUnfilteredState(mEntry); mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + + // WHEN the show silent notifs on lockscreen setting is false mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + + // WHEN the notification is not high priority and not ambient + mEntry = new NotificationEntryBuilder() + .setImportance(IMPORTANCE_LOW) + .build(); + when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void showSilentOnLockscreenSetting() { + // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen + setupUnfilteredState(mEntry); + mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + + // WHEN the show silent notifs on lockscreen setting is true + mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true); + + // WHEN the notification is not high priority and not ambient + mEntry = new NotificationEntryBuilder() + .setImportance(IMPORTANCE_LOW) + .build(); + when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); + + // THEN do not filter out the entry + assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void defaultSilentOnLockscreenSettingIsHide() { + // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen + setupUnfilteredState(mEntry); + mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + + // WHEN the notification is not high priority and not ambient mEntry = new NotificationEntryBuilder() .setUser(new UserHandle(NOTIF_USER_ID)) .setImportance(IMPORTANCE_LOW) .build(); when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false); + + // WhHEN the show silent notifs on lockscreen setting is unset + assertNull(mFakeSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @@ -431,25 +476,6 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { } @Test - public void showSilentOnLockscreenSetting() { - // GIVEN an 'unfiltered-keyguard-showing' state - setupUnfilteredState(mEntry); - - // WHEN the notification is not high priority and not ambient - mEntry.setRanking(new RankingBuilder() - .setKey(mEntry.getKey()) - .setImportance(IMPORTANCE_LOW) - .build()); - when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - - // WHEN the show silent notifs on lockscreen setting is true - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true); - - // THEN do not filter out the entry - assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); - } - - @Test public void notificationVisibilityPublic() { // GIVEN a VISIBILITY_PUBLIC notification NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index ad3bd711c23f..7c99568ee75f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -21,6 +21,10 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNotSame; +import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -31,6 +35,7 @@ import static org.mockito.Mockito.verify; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Log; import androidx.test.filters.SmallTest; @@ -100,6 +105,67 @@ public class RowContentBindStageTest extends SysuiTestCase { verify(mBinder).unbindContent(eq(mEntry), any(), eq(flags)); } + class CountingWtfHandler implements Log.TerribleFailureHandler { + private Log.TerribleFailureHandler mOldHandler = null; + private int mWtfCount = 0; + + public void register() { + mOldHandler = Log.setWtfHandler(this); + } + + public void unregister() { + Log.setWtfHandler(mOldHandler); + mOldHandler = null; + } + + @Override + public void onTerribleFailure(String tag, Log.TerribleFailure what, boolean system) { + mWtfCount++; + } + + public int getWtfCount() { + return mWtfCount; + } + } + + @Test + public void testGetStageParamsAfterCleanUp() { + // GIVEN an entry whose params have already been deleted. + RowContentBindParams originalParams = mRowContentBindStage.getStageParams(mEntry); + mRowContentBindStage.deleteStageParams(mEntry); + + // WHEN a caller calls getStageParams. + CountingWtfHandler countingWtfHandler = new CountingWtfHandler(); + countingWtfHandler.register(); + + RowContentBindParams blankParams = mRowContentBindStage.getStageParams(mEntry); + + countingWtfHandler.unregister(); + + // THEN getStageParams logs a WTF and returns blank params created to avoid a crash. + assertEquals(1, countingWtfHandler.getWtfCount()); + assertNotNull(blankParams); + assertNotSame(originalParams, blankParams); + } + + @Test + public void testTryGetStageParamsAfterCleanUp() { + // GIVEN an entry whose params have already been deleted. + mRowContentBindStage.deleteStageParams(mEntry); + + // WHEN a caller calls getStageParams. + CountingWtfHandler countingWtfHandler = new CountingWtfHandler(); + countingWtfHandler.register(); + + RowContentBindParams nullParams = mRowContentBindStage.tryGetStageParams(mEntry); + + countingWtfHandler.unregister(); + + // THEN getStageParams does NOT log a WTF and returns null to indicate missing params. + assertEquals(0, countingWtfHandler.getWtfCount()); + assertNull(nullParams); + } + @Test public void testRebindAllContentViews() { // GIVEN a view with content bound. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt index 11798a7a4f96..87f4c323b7cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt @@ -361,6 +361,22 @@ class AmbientStateTest : SysuiTestCase() { assertThat(sut.isOnKeyguard).isFalse() } // endregion + + // region mIsClosing + @Test + fun isClosing_whenShadeClosing_shouldReturnTrue() { + sut.setIsClosing(true) + + assertThat(sut.isClosing).isTrue() + } + + @Test + fun isClosing_whenShadeFinishClosing_shouldReturnFalse() { + sut.setIsClosing(false) + + assertThat(sut.isClosing).isFalse() + } + // endregion } // region Arrange helper methods. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 43530365360b..35c8b61b6383 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -728,6 +728,57 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat()); } + @Test + public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() { + // Given: shade is not closing, scrollY is 0 + mAmbientState.setScrollY(0); + assertEquals(0, mAmbientState.getScrollY()); + mAmbientState.setIsClosing(false); + + // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1 + mStackScroller.setOwnScrollY(1); + + // Then: scrollY should be set to 1 + assertEquals(1, mAmbientState.getScrollY()); + + // Reset scrollY back to 0 to avoid interfering with other tests + mStackScroller.setOwnScrollY(0); + assertEquals(0, mAmbientState.getScrollY()); + } + + @Test + public void testSetOwnScrollY_shadeClosing_scrollYDoesNotChange() { + // Given: shade is closing, scrollY is 0 + mAmbientState.setScrollY(0); + assertEquals(0, mAmbientState.getScrollY()); + mAmbientState.setIsClosing(true); + + // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1 + mStackScroller.setOwnScrollY(1); + + // Then: scrollY should not change, it should still be 0 + assertEquals(0, mAmbientState.getScrollY()); + + // Reset scrollY and mAmbientState.mIsClosing to avoid interfering with other tests + mAmbientState.setIsClosing(false); + mStackScroller.setOwnScrollY(0); + assertEquals(0, mAmbientState.getScrollY()); + } + + @Test + public void onShadeFlingClosingEnd_scrollYShouldBeSetToZero() { + // Given: mAmbientState.mIsClosing is set to be true + // mIsExpanded is set to be false + mAmbientState.setIsClosing(true); + mStackScroller.setIsExpanded(false); + + // When: onExpansionStopped is called + mStackScroller.onExpansionStopped(); + + // Then: mAmbientState.scrollY should be set to be 0 + assertEquals(mAmbientState.getScrollY(), 0); + } + private void setBarStateForTest(int state) { // Can't inject this through the listener or we end up on the actual implementation // rather than the mock because the spy just coppied the anonymous inner /shruggie. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt index 1ee8875eada8..78a4db1224cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt @@ -20,7 +20,7 @@ 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.log.LogcatEchoTracker +import com.android.systemui.plugins.log.LogcatEchoTracker import com.android.systemui.statusbar.DisableFlagsLogger import com.google.common.truth.Truth.assertThat import org.junit.Test 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 63467e7039d4..438271c489e6 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 @@ -49,9 +49,9 @@ import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogcatEchoTracker; import com.android.systemui.plugins.DarkIconDispatcher; +import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.plugins.log.LogcatEchoTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeExpansionStateManager; 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 0e75c74ef6f5..b32058fca109 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 @@ -22,7 +22,7 @@ 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.log.LogcatEchoTracker +import com.android.systemui.plugins.log.LogcatEchoTracker import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import com.google.common.truth.Truth.assertThat diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt index a3ad028519bb..e56623f9fea2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt @@ -22,7 +22,7 @@ import androidx.test.filters.SmallTest import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION 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.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK @@ -125,19 +125,12 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase } else { testCase.expected.contentDescription.invoke(context) } - assertThat(iconFlow.value?.contentDescription?.getAsString()) + assertThat(iconFlow.value?.contentDescription?.loadContentDescription(context)) .isEqualTo(expectedContentDescription) job.cancel() } - private fun ContentDescription.getAsString(): String? { - return when (this) { - is ContentDescription.Loaded -> this.description - is ContentDescription.Resource -> context.getString(this.res) - } - } - internal data class Expected( /** The resource that should be used for the icon. */ @DrawableRes val iconResource: Int, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt index 76ecc1c7f36d..169f4fb2715b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt @@ -57,14 +57,18 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.telephony.TelephonyListenerManager import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper +import com.android.systemui.user.shared.model.UserActionModel 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.kotlinArgumentCaptor import com.android.systemui.util.mockito.nullable import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -123,7 +127,7 @@ class UserSwitcherControllerOldImplTest : SysuiTestCase() { private val ownerId = UserHandle.USER_SYSTEM private val ownerInfo = UserInfo(ownerId, "Owner", null, UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or - UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM, + UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN, UserManager.USER_TYPE_FULL_SYSTEM) private val guestId = 1234 private val guestInfo = UserInfo(guestId, "Guest", null, @@ -597,6 +601,76 @@ class UserSwitcherControllerOldImplTest : SysuiTestCase() { } @Test + fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() { + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.USER_SWITCHER_ENABLED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(1) + + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.ADD_USERS_WHEN_LOCKED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(1) + setupController() + assertTrue(userSwitcherController.canManageUsers()) + } + + @Test + fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() { + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.USER_SWITCHER_ENABLED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(0) + + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.ADD_USERS_WHEN_LOCKED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(1) + setupController() + assertFalse(userSwitcherController.canManageUsers()) + } + + @Test + fun testCanManageUser_userSwitcherEnabled_isAdmin() { + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.USER_SWITCHER_ENABLED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(1) + + setupController() + assertTrue(userSwitcherController.canManageUsers()) + } + + @Test + fun testCanManageUser_userSwitcherDisabled_isAdmin() { + `when`( + globalSettings.getIntForUser( + eq(Settings.Global.USER_SWITCHER_ENABLED), + anyInt(), + eq(UserHandle.USER_SYSTEM) + ) + ).thenReturn(0) + + setupController() + assertFalse(userSwitcherController.canManageUsers()) + } + + @Test fun addUserSwitchCallback() { val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>() verify(broadcastDispatcher).registerReceiver( @@ -632,4 +706,22 @@ class UserSwitcherControllerOldImplTest : SysuiTestCase() { bgExecutor.runAllReady() verify(userManager).createGuest(context) } + + @Test + fun onUserItemClicked_manageUsers() { + val manageUserRecord = LegacyUserDataHelper.createRecord( + mContext, + ownerId, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + isRestricted = false, + isSwitchToEnabled = true + ) + + userSwitcherController.onUserListItemClicked(manageUserRecord, null) + val intentCaptor = kotlinArgumentCaptor<Intent>() + verify(activityStarter).startActivity(intentCaptor.capture(), + eq(true) + ) + Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS) + } } 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 c4abedd0eed4..b68eb88d46db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -63,8 +63,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { @Mock private lateinit var powerManager: PowerManager - private var shouldIgnoreViewRemoval: Boolean = false - @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -209,26 +207,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { verify(windowManager, never()).removeView(any()) } - @Test - fun removeView_shouldIgnoreRemovalFalse_viewRemoved() { - shouldIgnoreViewRemoval = false - underTest.displayView(getState()) - - underTest.removeView("reason") - - verify(windowManager).removeView(any()) - } - - @Test - fun removeView_shouldIgnoreRemovalTrue_viewNotRemoved() { - shouldIgnoreViewRemoval = true - underTest.displayView(getState()) - - underTest.removeView("reason") - - verify(windowManager, never()).removeView(any()) - } - private fun getState(name: String = "name") = ViewInfo(name) private fun getConfigurationListener(): ConfigurationListener { @@ -253,7 +231,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { accessibilityManager, configurationController, powerManager, - R.layout.media_ttt_chip, + R.layout.chipbar, "Window Title", "WAKE_REASON", ) { @@ -267,10 +245,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { mostRecentViewInfo = newInfo } - override fun shouldIgnoreViewRemoval(info: ViewInfo, removalReason: String): Boolean { - return shouldIgnoreViewRemoval - } - override fun getTouchableRegion(view: View, outRect: Rect) { outRect.setEmpty() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt index c9f2b4db81ef..13e9f608158e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.temporarydisplay import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager -import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory -import com.android.systemui.log.LogcatEchoTracker +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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt new file mode 100644 index 000000000000..fa78b3832158 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt @@ -0,0 +1,334 @@ +/* + * 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.temporarydisplay.chipbar + +import android.os.PowerManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.view.accessibility.AccessibilityManager +import android.widget.ImageView +import android.widget.TextView +import androidx.test.filters.SmallTest +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +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.common.shared.model.Text +import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.util.view.ViewUtil +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.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class ChipbarCoordinatorTest : SysuiTestCase() { + private lateinit var underTest: FakeChipbarCoordinator + + @Mock private lateinit var logger: MediaTttLogger + @Mock private lateinit var accessibilityManager: AccessibilityManager + @Mock private lateinit var configurationController: ConfigurationController + @Mock private lateinit var powerManager: PowerManager + @Mock private lateinit var windowManager: WindowManager + @Mock private lateinit var falsingManager: FalsingManager + @Mock private lateinit var falsingCollector: FalsingCollector + @Mock private lateinit var viewUtil: ViewUtil + private lateinit var fakeClock: FakeSystemClock + private lateinit var fakeExecutor: FakeExecutor + private lateinit var uiEventLoggerFake: UiEventLoggerFake + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT) + + fakeClock = FakeSystemClock() + fakeExecutor = FakeExecutor(fakeClock) + + uiEventLoggerFake = UiEventLoggerFake() + + underTest = + FakeChipbarCoordinator( + context, + logger, + windowManager, + fakeExecutor, + accessibilityManager, + configurationController, + powerManager, + falsingManager, + falsingCollector, + viewUtil, + ) + underTest.start() + } + + @Test + fun displayView_loadedIcon_correctlyRendered() { + val drawable = context.getDrawable(R.drawable.ic_celebration)!! + + underTest.displayView( + ChipbarInfo( + Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")), + Text.Loaded("text"), + endItem = null, + ) + ) + + val iconView = getChipbarView().getStartIconView() + assertThat(iconView.drawable).isEqualTo(drawable) + assertThat(iconView.contentDescription).isEqualTo("loadedCD") + } + + @Test + fun displayView_resourceIcon_correctlyRendered() { + val contentDescription = ContentDescription.Resource(R.string.controls_error_timeout) + underTest.displayView( + ChipbarInfo( + Icon.Resource(R.drawable.ic_cake, contentDescription), + Text.Loaded("text"), + endItem = null, + ) + ) + + val iconView = getChipbarView().getStartIconView() + assertThat(iconView.contentDescription) + .isEqualTo(contentDescription.loadContentDescription(context)) + } + + @Test + fun displayView_loadedText_correctlyRendered() { + underTest.displayView( + ChipbarInfo( + Icon.Resource(R.id.check_box, null), + Text.Loaded("display view text here"), + endItem = null, + ) + ) + + assertThat(getChipbarView().getChipText()).isEqualTo("display view text here") + } + + @Test + fun displayView_resourceText_correctlyRendered() { + underTest.displayView( + ChipbarInfo( + Icon.Resource(R.id.check_box, null), + Text.Resource(R.string.screenrecord_start_error), + endItem = null, + ) + ) + + assertThat(getChipbarView().getChipText()) + .isEqualTo(context.getString(R.string.screenrecord_start_error)) + } + + @Test + fun displayView_endItemNull_correctlyRendered() { + underTest.displayView( + ChipbarInfo( + Icon.Resource(R.id.check_box, null), + Text.Loaded("text"), + endItem = null, + ) + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE) + } + + @Test + fun displayView_endItemLoading_correctlyRendered() { + underTest.displayView( + ChipbarInfo( + Icon.Resource(R.id.check_box, null), + Text.Loaded("text"), + endItem = ChipbarEndItem.Loading, + ) + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE) + } + + @Test + fun displayView_endItemError_correctlyRendered() { + underTest.displayView( + ChipbarInfo( + Icon.Resource(R.id.check_box, null), + Text.Loaded("text"), + endItem = ChipbarEndItem.Error, + ) + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE) + assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE) + } + + @Test + fun displayView_endItemButton_correctlyRendered() { + underTest.displayView( + ChipbarInfo( + Icon.Resource(R.id.check_box, null), + Text.Loaded("text"), + endItem = + ChipbarEndItem.Button( + Text.Loaded("button text"), + onClickListener = {}, + ), + ) + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.VISIBLE) + assertThat(chipbarView.getEndButton().text).isEqualTo("button text") + assertThat(chipbarView.getEndButton().hasOnClickListeners()).isTrue() + } + + @Test + fun displayView_endItemButtonClicked_falseTap_listenerNotRun() { + whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true) + var isClicked = false + val buttonClickListener = View.OnClickListener { isClicked = true } + + underTest.displayView( + ChipbarInfo( + Icon.Resource(R.id.check_box, null), + Text.Loaded("text"), + endItem = + ChipbarEndItem.Button( + Text.Loaded("button text"), + buttonClickListener, + ), + ) + ) + + getChipbarView().getEndButton().performClick() + + assertThat(isClicked).isFalse() + } + + @Test + fun displayView_endItemButtonClicked_notFalseTap_listenerRun() { + whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false) + var isClicked = false + val buttonClickListener = View.OnClickListener { isClicked = true } + + underTest.displayView( + ChipbarInfo( + Icon.Resource(R.id.check_box, null), + Text.Loaded("text"), + endItem = + ChipbarEndItem.Button( + Text.Loaded("button text"), + buttonClickListener, + ), + ) + ) + + getChipbarView().getEndButton().performClick() + + assertThat(isClicked).isTrue() + } + + @Test + fun updateView_viewUpdated() { + // First, display a view + val drawable = context.getDrawable(R.drawable.ic_celebration)!! + + underTest.displayView( + ChipbarInfo( + Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")), + Text.Loaded("title text"), + endItem = ChipbarEndItem.Loading, + ) + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getStartIconView().drawable).isEqualTo(drawable) + assertThat(chipbarView.getStartIconView().contentDescription).isEqualTo("loadedCD") + assertThat(chipbarView.getChipText()).isEqualTo("title text") + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE) + + // WHEN the view is updated + val newDrawable = context.getDrawable(R.drawable.ic_cake)!! + underTest.updateView( + ChipbarInfo( + Icon.Loaded(newDrawable, ContentDescription.Loaded("new CD")), + Text.Loaded("new title text"), + endItem = ChipbarEndItem.Error, + ), + chipbarView + ) + + // THEN we display the new view + assertThat(chipbarView.getStartIconView().drawable).isEqualTo(newDrawable) + assertThat(chipbarView.getStartIconView().contentDescription).isEqualTo("new CD") + assertThat(chipbarView.getChipText()).isEqualTo("new title text") + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE) + assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE) + } + + private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon) + + private fun ViewGroup.getChipText(): String = + (this.requireViewById<TextView>(R.id.text)).text as String + + private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading) + + private fun ViewGroup.getEndButton(): TextView = this.requireViewById(R.id.end_button) + + private fun ViewGroup.getErrorIcon(): View = this.requireViewById(R.id.error) + + private fun getChipbarView(): ViewGroup { + val viewCaptor = ArgumentCaptor.forClass(View::class.java) + verify(windowManager).addView(viewCaptor.capture(), any()) + return viewCaptor.value as ViewGroup + } +} + +private const val TIMEOUT = 10000 diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt new file mode 100644 index 000000000000..8f32e0fd1de5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt @@ -0,0 +1,61 @@ +/* + * 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.temporarydisplay.chipbar + +import android.content.Context +import android.os.PowerManager +import android.view.ViewGroup +import android.view.WindowManager +import android.view.accessibility.AccessibilityManager +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.view.ViewUtil + +/** A fake implementation of [ChipbarCoordinator] for testing. */ +class FakeChipbarCoordinator( + context: Context, + @MediaTttReceiverLogger logger: MediaTttLogger, + windowManager: WindowManager, + mainExecutor: DelayableExecutor, + accessibilityManager: AccessibilityManager, + configurationController: ConfigurationController, + powerManager: PowerManager, + falsingManager: FalsingManager, + falsingCollector: FalsingCollector, + viewUtil: ViewUtil, +) : + ChipbarCoordinator( + context, + logger, + windowManager, + mainExecutor, + accessibilityManager, + configurationController, + powerManager, + falsingManager, + falsingCollector, + viewUtil, + ) { + override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { + // Just bypass the animation in tests + onAnimationEnd.run() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 7e0704007700..e18dd3a3c846 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -25,16 +25,21 @@ import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider import com.android.systemui.unfold.updates.FoldProvider.FoldCallback +import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener import com.android.systemui.util.mockito.any +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.Captor import org.mockito.Mock +import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -48,6 +53,12 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Mock private lateinit var handler: Handler + @Mock + private lateinit var rotationChangeProvider: RotationChangeProvider + + @Captor + private lateinit var rotationListener: ArgumentCaptor<RotationListener> + private val foldProvider = TestFoldProvider() private val screenOnStatusProvider = TestScreenOnStatusProvider() private val testHingeAngleProvider = TestHingeAngleProvider() @@ -76,6 +87,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { screenOnStatusProvider, foldProvider, activityTypeProvider, + rotationChangeProvider, context.mainExecutor, handler ) @@ -92,6 +104,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { }) foldStateProvider.start() + verify(rotationChangeProvider).addCallback(capture(rotationListener)) + whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock -> scheduledRunnable = invocationOnMock.getArgument<Runnable>(0) scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1) @@ -372,6 +386,27 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { assertThat(testHingeAngleProvider.isStarted).isFalse() } + @Test + fun onRotationChanged_whileInProgress_cancelled() { + setFoldState(folded = false) + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING) + + rotationListener.value.onRotationChanged(1) + + assertThat(foldUpdates).containsExactly( + FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN) + } + + @Test + fun onRotationChanged_whileNotInProgress_noUpdates() { + setFoldState(folded = true) + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED) + + rotationListener.value.onRotationChanged(1) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED) + } + private fun setupForegroundActivityType(isHomeActivity: Boolean?) { whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt new file mode 100644 index 000000000000..85cfef727954 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt @@ -0,0 +1,84 @@ +/* + * 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.unfold.updates + +import android.testing.AndroidTestingRunner +import android.view.IRotationWatcher +import android.view.IWindowManager +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener +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.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class RotationChangeProviderTest : SysuiTestCase() { + + private lateinit var rotationChangeProvider: RotationChangeProvider + + @Mock lateinit var windowManagerInterface: IWindowManager + @Mock lateinit var listener: RotationListener + @Captor lateinit var rotationWatcher: ArgumentCaptor<IRotationWatcher> + private val fakeExecutor = FakeExecutor(FakeSystemClock()) + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + rotationChangeProvider = + RotationChangeProvider(windowManagerInterface, context, fakeExecutor) + rotationChangeProvider.addCallback(listener) + fakeExecutor.runAllReady() + verify(windowManagerInterface).watchRotation(rotationWatcher.capture(), anyInt()) + } + + @Test + fun onRotationChanged_rotationUpdated_listenerReceivesIt() { + sendRotationUpdate(42) + + verify(listener).onRotationChanged(42) + } + + @Test + fun onRotationChanged_subscribersRemoved_noRotationChangeReceived() { + sendRotationUpdate(42) + verify(listener).onRotationChanged(42) + + rotationChangeProvider.removeCallback(listener) + fakeExecutor.runAllReady() + sendRotationUpdate(43) + + verify(windowManagerInterface).removeRotationWatcher(any()) + verifyNoMoreInteractions(listener) + } + + private fun sendRotationUpdate(newRotation: Int) { + rotationWatcher.value.onRotationChanged(newRotation) + fakeExecutor.runAllReady() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt index b2cedbf8d606..a25469bfc09b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt @@ -16,18 +16,19 @@ package com.android.systemui.unfold.util import android.testing.AndroidTestingRunner -import android.view.IRotationWatcher -import android.view.IWindowManager import android.view.Surface import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.TestUnfoldTransitionProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener -import com.android.systemui.util.mockito.any +import com.android.systemui.unfold.updates.RotationChangeProvider +import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener +import com.android.systemui.util.mockito.capture 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.clearInvocations import org.mockito.Mockito.never @@ -38,32 +39,26 @@ import org.mockito.MockitoAnnotations @SmallTest class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() { - @Mock - lateinit var windowManager: IWindowManager + @Mock lateinit var rotationChangeProvider: RotationChangeProvider private val sourceProvider = TestUnfoldTransitionProvider() - @Mock - lateinit var transitionListener: TransitionProgressListener + @Mock lateinit var transitionListener: TransitionProgressListener - lateinit var progressProvider: NaturalRotationUnfoldProgressProvider + @Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener> - private val rotationWatcherCaptor = - ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java) + lateinit var progressProvider: NaturalRotationUnfoldProgressProvider @Before fun setUp() { MockitoAnnotations.initMocks(this) - progressProvider = NaturalRotationUnfoldProgressProvider( - context, - windowManager, - sourceProvider - ) + progressProvider = + NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, sourceProvider) progressProvider.init() - verify(windowManager).watchRotation(rotationWatcherCaptor.capture(), any()) + verify(rotationChangeProvider).addCallback(capture(rotationListenerCaptor)) progressProvider.addCallback(transitionListener) } @@ -127,6 +122,6 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() { } private fun onRotationChanged(rotation: Int) { - rotationWatcherCaptor.value.onRotationChanged(rotation) + rotationListenerCaptor.value.onRotationChanged(rotation) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt deleted file mode 100644 index 3968bb798bb7..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt +++ /dev/null @@ -1,152 +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.user - -import android.app.Application -import android.os.UserManager -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper.RunWithLooper -import android.view.LayoutInflater -import android.view.View -import android.view.Window -import android.window.OnBackInvokedCallback -import android.window.OnBackInvokedDispatcher -import androidx.test.filters.SmallTest -import com.android.systemui.R -import com.android.systemui.SysuiTestCase -import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.policy.UserSwitcherController -import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.time.FakeSystemClock -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.Captor -import org.mockito.Mock -import org.mockito.Mockito.`when` -import org.mockito.Mockito.any -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.doNothing -import org.mockito.Mockito.eq -import org.mockito.Mockito.mock -import org.mockito.Mockito.spy -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import java.util.concurrent.Executor - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@RunWithLooper(setAsMainLooper = true) -class UserSwitcherActivityTest : SysuiTestCase() { - @Mock - private lateinit var activity: UserSwitcherActivity - @Mock - private lateinit var userSwitcherController: UserSwitcherController - @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock - private lateinit var layoutInflater: LayoutInflater - @Mock - private lateinit var falsingCollector: FalsingCollector - @Mock - private lateinit var falsingManager: FalsingManager - @Mock - private lateinit var userManager: UserManager - @Mock - private lateinit var userTracker: UserTracker - @Mock - private lateinit var flags: FeatureFlags - @Mock - private lateinit var viewModelFactoryLazy: dagger.Lazy<UserSwitcherViewModel.Factory> - @Mock - private lateinit var onBackDispatcher: OnBackInvokedDispatcher - @Mock - private lateinit var decorView: View - @Mock - private lateinit var window: Window - @Mock - private lateinit var userSwitcherRootView: UserSwitcherRootView - @Captor - private lateinit var onBackInvokedCallback: ArgumentCaptor<OnBackInvokedCallback> - var isFinished = false - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - activity = spy(object : UserSwitcherActivity( - userSwitcherController, - broadcastDispatcher, - falsingCollector, - falsingManager, - userManager, - userTracker, - flags, - viewModelFactoryLazy, - ) { - override fun getOnBackInvokedDispatcher() = onBackDispatcher - override fun getMainExecutor(): Executor = FakeExecutor(FakeSystemClock()) - override fun finish() { - isFinished = true - } - }) - `when`(activity.window).thenReturn(window) - `when`(window.decorView).thenReturn(decorView) - `when`(activity.findViewById<UserSwitcherRootView>(R.id.user_switcher_root)) - .thenReturn(userSwitcherRootView) - `when`(activity.findViewById<View>(R.id.cancel)).thenReturn(mock(View::class.java)) - `when`(activity.findViewById<View>(R.id.add)).thenReturn(mock(View::class.java)) - `when`(activity.application).thenReturn(mock(Application::class.java)) - doNothing().`when`(activity).setContentView(anyInt()) - } - - @Test - fun testMaxColumns() { - assertThat(activity.getMaxColumns(3)).isEqualTo(4) - assertThat(activity.getMaxColumns(4)).isEqualTo(4) - assertThat(activity.getMaxColumns(5)).isEqualTo(3) - assertThat(activity.getMaxColumns(6)).isEqualTo(3) - assertThat(activity.getMaxColumns(7)).isEqualTo(4) - assertThat(activity.getMaxColumns(9)).isEqualTo(5) - } - - @Test - fun onCreate_callbackRegistration() { - activity.createActivity() - verify(onBackDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any()) - - activity.destroyActivity() - verify(onBackDispatcher).unregisterOnBackInvokedCallback(any()) - } - - @Test - fun onBackInvokedCallback_finishesActivity() { - activity.createActivity() - verify(onBackDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), onBackInvokedCallback.capture()) - - onBackInvokedCallback.value.onBackInvoked() - assertThat(isFinished).isTrue() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt index d4b41c18e123..a363a037c499 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt @@ -97,6 +97,7 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() { createUserRecord(2), createActionRecord(UserActionModel.ADD_SUPERVISED_USER), createActionRecord(UserActionModel.ENTER_GUEST_MODE), + createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT), ) ) var models: List<UserModel>? = null @@ -176,15 +177,17 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() { createUserRecord(2), createActionRecord(UserActionModel.ADD_SUPERVISED_USER), createActionRecord(UserActionModel.ENTER_GUEST_MODE), + createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT), ) ) var models: List<UserActionModel>? = null val job = underTest.actions.onEach { models = it }.launchIn(this) - assertThat(models).hasSize(3) + assertThat(models).hasSize(4) assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER) assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER) assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE) + assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) job.cancel() } @@ -200,6 +203,7 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() { isAddUser = action == UserActionModel.ADD_USER, isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER, isGuest = action == UserActionModel.ENTER_GUEST_MODE, + isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt index 37c378c9a530..1540f8552002 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt @@ -202,6 +202,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { fun `actions - device unlocked`() = runBlocking(IMMEDIATE) { val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) @@ -215,6 +216,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { UserActionModel.ENTER_GUEST_MODE, UserActionModel.ADD_USER, UserActionModel.ADD_SUPERVISED_USER, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) ) @@ -276,6 +278,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { UserActionModel.ENTER_GUEST_MODE, UserActionModel.ADD_USER, UserActionModel.ADD_SUPERVISED_USER, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) ) @@ -283,7 +286,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { } @Test - fun `actions - device locked - only guest action is shown`() = + fun `actions - device locked - only guest action and manage user is shown`() = runBlocking(IMMEDIATE) { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) @@ -293,7 +296,13 @@ class UserInteractorRefactoredTest : UserInteractorTest() { var value: List<UserActionModel>? = null val job = underTest.actions.onEach { value = it }.launchIn(this) - assertThat(value).isEqualTo(listOf(UserActionModel.ENTER_GUEST_MODE)) + assertThat(value) + .isEqualTo( + listOf( + UserActionModel.ENTER_GUEST_MODE, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT + ) + ) job.cancel() } @@ -330,7 +339,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER) val intentCaptor = kotlinArgumentCaptor<Intent>() - verify(activityStarter).startActivity(intentCaptor.capture(), eq(false)) + verify(activityStarter).startActivity(intentCaptor.capture(), eq(true)) assertThat(intentCaptor.value.action) .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER) assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE) @@ -342,7 +351,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) val intentCaptor = kotlinArgumentCaptor<Intent>() - verify(activityStarter).startActivity(intentCaptor.capture(), eq(false)) + verify(activityStarter).startActivity(intentCaptor.capture(), eq(true)) assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS) } @@ -561,6 +570,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { UserActionModel.ENTER_GUEST_MODE, UserActionModel.ADD_USER, UserActionModel.ADD_SUPERVISED_USER, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ), ) } @@ -705,7 +715,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() { name, /* iconPath= */ "", /* flags= */ if (isPrimary) { - UserInfo.FLAG_PRIMARY + UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN } else { 0 }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt index c3a9705bf6ba..6a17c8ddc63d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt @@ -64,13 +64,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() { @Test fun `actions - not actionable when locked and not locked`() = runBlocking(IMMEDIATE) { - userRepository.setActions( - listOf( - UserActionModel.ENTER_GUEST_MODE, - UserActionModel.ADD_USER, - UserActionModel.ADD_SUPERVISED_USER, - ) - ) + setActions() userRepository.setActionableWhenLocked(false) keyguardRepository.setKeyguardShowing(false) @@ -92,13 +86,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() { @Test fun `actions - actionable when locked and not locked`() = runBlocking(IMMEDIATE) { - userRepository.setActions( - listOf( - UserActionModel.ENTER_GUEST_MODE, - UserActionModel.ADD_USER, - UserActionModel.ADD_SUPERVISED_USER, - ) - ) + setActions() userRepository.setActionableWhenLocked(true) keyguardRepository.setKeyguardShowing(false) @@ -120,13 +108,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() { @Test fun `actions - actionable when locked and locked`() = runBlocking(IMMEDIATE) { - userRepository.setActions( - listOf( - UserActionModel.ENTER_GUEST_MODE, - UserActionModel.ADD_USER, - UserActionModel.ADD_SUPERVISED_USER, - ) - ) + setActions() userRepository.setActionableWhenLocked(true) keyguardRepository.setKeyguardShowing(true) @@ -182,6 +164,10 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() { verify(activityStarter).startActivity(any(), anyBoolean()) } + private fun setActions() { + userRepository.setActions(UserActionModel.values().toList()) + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 0344e3f991e2..c12a868dbaed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -268,6 +268,26 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test + fun `menu actions`() = + runBlocking(IMMEDIATE) { + userRepository.setActions(UserActionModel.values().toList()) + var actions: List<UserActionViewModel>? = null + val job = underTest.menu.onEach { actions = it }.launchIn(this) + + assertThat(actions?.map { it.viewKey }) + .isEqualTo( + listOf( + UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(), + UserActionModel.ADD_USER.ordinal.toLong(), + UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(), + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(), + ) + ) + + job.cancel() + } + + @Test fun `isFinishRequested - finishes when user is switched`() = runBlocking(IMMEDIATE) { setUsers(count = 2) diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt deleted file mode 100644 index 5e09b81da4e8..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt +++ /dev/null @@ -1,131 +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.util.collection - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertSame -import org.junit.Assert.assertThrows -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class RingBufferTest : SysuiTestCase() { - - private val buffer = RingBuffer(5) { TestElement() } - - private val history = mutableListOf<TestElement>() - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - } - - @Test - fun testBarelyFillBuffer() { - fillBuffer(5) - - assertEquals(0, buffer[0].id) - assertEquals(1, buffer[1].id) - assertEquals(2, buffer[2].id) - assertEquals(3, buffer[3].id) - assertEquals(4, buffer[4].id) - } - - @Test - fun testPartiallyFillBuffer() { - fillBuffer(3) - - assertEquals(3, buffer.size) - - assertEquals(0, buffer[0].id) - assertEquals(1, buffer[1].id) - assertEquals(2, buffer[2].id) - - assertThrows(IndexOutOfBoundsException::class.java) { buffer[3] } - assertThrows(IndexOutOfBoundsException::class.java) { buffer[4] } - } - - @Test - fun testSpinBuffer() { - fillBuffer(277) - - assertEquals(272, buffer[0].id) - assertEquals(273, buffer[1].id) - assertEquals(274, buffer[2].id) - assertEquals(275, buffer[3].id) - assertEquals(276, buffer[4].id) - assertThrows(IndexOutOfBoundsException::class.java) { buffer[5] } - - assertEquals(5, buffer.size) - } - - @Test - fun testElementsAreRecycled() { - fillBuffer(23) - - assertSame(history[4], buffer[1]) - assertSame(history[9], buffer[1]) - assertSame(history[14], buffer[1]) - assertSame(history[19], buffer[1]) - } - - @Test - fun testIterator() { - fillBuffer(13) - - val iterator = buffer.iterator() - - for (i in 0 until 5) { - assertEquals(history[8 + i], iterator.next()) - } - assertFalse(iterator.hasNext()) - assertThrows(NoSuchElementException::class.java) { iterator.next() } - } - - @Test - fun testForEach() { - fillBuffer(13) - var i = 8 - - buffer.forEach { - assertEquals(history[i], it) - i++ - } - assertEquals(13, i) - } - - private fun fillBuffer(count: Int) { - for (i in 0 until count) { - val elem = buffer.advance() - elem.id = history.size - history.add(elem) - } - } -} - -private class TestElement(var id: Int = 0) { - override fun toString(): String { - return "{TestElement $id}" - } -}
\ No newline at end of file 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 725b1f41372c..0c126805fb78 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import com.android.systemui.common.shared.model.Position +import com.android.systemui.keyguard.shared.model.StatusBarState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -44,6 +45,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _dozeAmount = MutableStateFlow(0f) override val dozeAmount: Flow<Float> = _dozeAmount + private val _statusBarState = MutableStateFlow(StatusBarState.SHADE) + override val statusBarState: Flow<StatusBarState> = _statusBarState + override fun isKeyguardShowing(): Boolean { return _isKeyguardShowing.value } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt index a5ec0a454412..5a868a4df354 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt @@ -20,10 +20,12 @@ import android.content.ContentResolver import android.content.Context import android.hardware.SensorManager import android.os.Handler +import android.view.IWindowManager import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.dagger.UnfoldBackground import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.updates.FoldProvider +import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.util.CurrentActivityTypeProvider import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix @@ -39,11 +41,11 @@ import javax.inject.Singleton * * This component is meant to be used for places that don't use dagger. By providing those * parameters to the factory, all dagger objects are correctly instantiated. See - * [createUnfoldTransitionProgressProvider] for an example. + * [createUnfoldSharedComponent] for an example. */ @Singleton @Component(modules = [UnfoldSharedModule::class]) -internal interface UnfoldSharedComponent { +interface UnfoldSharedComponent { @Component.Factory interface Factory { @@ -58,9 +60,11 @@ internal interface UnfoldSharedComponent { @BindsInstance @UnfoldMain executor: Executor, @BindsInstance @UnfoldBackground backgroundExecutor: Executor, @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String, + @BindsInstance windowManager: IWindowManager, @BindsInstance contentResolver: ContentResolver = context.contentResolver ): UnfoldSharedComponent } val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider> + val rotationChangeProvider: RotationChangeProvider } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index 402dd8474bc4..a1ed17844e8e 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -20,6 +20,7 @@ package com.android.systemui.unfold import android.content.Context import android.hardware.SensorManager import android.os.Handler +import android.view.IWindowManager import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.updates.FoldProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider @@ -27,14 +28,15 @@ import com.android.systemui.unfold.util.CurrentActivityTypeProvider import java.util.concurrent.Executor /** - * Factory for [UnfoldTransitionProgressProvider]. + * Factory for [UnfoldSharedComponent]. * - * This is needed as Launcher has to create the object manually. If dagger is available, this object - * is provided in [UnfoldSharedModule]. + * This wraps the autogenerated factory (for discoverability), and is needed as Launcher has to + * create the object manually. If dagger is available, this object is provided in + * [UnfoldSharedModule]. * * This should **never** be called from sysui, as the object is already provided in that process. */ -fun createUnfoldTransitionProgressProvider( +fun createUnfoldSharedComponent( context: Context, config: UnfoldTransitionConfig, screenStatusProvider: ScreenStatusProvider, @@ -44,8 +46,9 @@ fun createUnfoldTransitionProgressProvider( mainHandler: Handler, mainExecutor: Executor, backgroundExecutor: Executor, - tracingTagPrefix: String -): UnfoldTransitionProgressProvider = + tracingTagPrefix: String, + windowManager: IWindowManager, +): UnfoldSharedComponent = DaggerUnfoldSharedComponent.factory() .create( context, @@ -57,9 +60,6 @@ fun createUnfoldTransitionProgressProvider( mainHandler, mainExecutor, backgroundExecutor, - tracingTagPrefix) - .unfoldTransitionProvider - .orElse(null) - ?: throw IllegalStateException( - "Trying to create " + - "UnfoldTransitionProgressProvider when the transition is disabled") + tracingTagPrefix, + windowManager, + ) diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt index d54481c72bfd..7117aafba54a 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt @@ -26,7 +26,8 @@ import com.android.systemui.unfold.util.CallbackController * * onTransitionProgress callback could be called on each frame. * - * Use [createUnfoldTransitionProgressProvider] to create instances of this interface + * Use [createUnfoldSharedComponent] to create instances of this interface when dagger is not + * available. */ interface UnfoldTransitionProgressProvider : CallbackController<TransitionProgressListener> { diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 19cfc805d17b..07473b30dd58 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -24,6 +24,7 @@ import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener +import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES import com.android.systemui.unfold.updates.hinge.HingeAngleProvider @@ -40,22 +41,24 @@ constructor( private val screenStatusProvider: ScreenStatusProvider, private val foldProvider: FoldProvider, private val activityTypeProvider: CurrentActivityTypeProvider, + private val rotationChangeProvider: RotationChangeProvider, @UnfoldMain private val mainExecutor: Executor, @UnfoldMain private val handler: Handler ) : FoldStateProvider { private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf() - @FoldUpdate - private var lastFoldUpdate: Int? = null + @FoldUpdate private var lastFoldUpdate: Int? = null - @FloatRange(from = 0.0, to = 180.0) - private var lastHingeAngle: Float = 0f + @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f private val hingeAngleListener = HingeAngleListener() private val screenListener = ScreenStatusListener() private val foldStateListener = FoldStateListener() - private val timeoutRunnable = TimeoutRunnable() + private val timeoutRunnable = Runnable { cancelAnimation() } + private val rotationListener = RotationListener { + if (isTransitionInProgress) cancelAnimation() + } /** * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a @@ -72,6 +75,7 @@ constructor( foldProvider.registerCallback(foldStateListener, mainExecutor) screenStatusProvider.addCallback(screenListener) hingeAngleProvider.addCallback(hingeAngleListener) + rotationChangeProvider.addCallback(rotationListener) } override fun stop() { @@ -79,6 +83,7 @@ constructor( foldProvider.unregisterCallback(foldStateListener) hingeAngleProvider.removeCallback(hingeAngleListener) hingeAngleProvider.stop() + rotationChangeProvider.removeCallback(rotationListener) } override fun addCallback(listener: FoldUpdatesListener) { @@ -90,14 +95,15 @@ constructor( } override val isFinishedOpening: Boolean - get() = !isFolded && + get() = + !isFolded && (lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN || - lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN) + lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN) private val isTransitionInProgress: Boolean get() = lastFoldUpdate == FOLD_UPDATE_START_OPENING || - lastFoldUpdate == FOLD_UPDATE_START_CLOSING + lastFoldUpdate == FOLD_UPDATE_START_CLOSING private fun onHingeAngle(angle: Float) { if (DEBUG) { @@ -168,7 +174,7 @@ constructor( private fun notifyFoldUpdate(@FoldUpdate update: Int) { if (DEBUG) { - Log.d(TAG, stateToString(update)) + Log.d(TAG, update.name()) } outputListeners.forEach { it.onFoldUpdate(update) } lastFoldUpdate = update @@ -185,6 +191,8 @@ constructor( handler.removeCallbacks(timeoutRunnable) } + private fun cancelAnimation(): Unit = notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) + private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener { override fun onScreenTurnedOn() { @@ -225,16 +233,10 @@ constructor( onHingeAngle(angle) } } - - private inner class TimeoutRunnable : Runnable { - override fun run() { - notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) - } - } } -private fun stateToString(@FoldUpdate update: Int): String { - return when (update) { +fun @receiver:FoldUpdate Int.name() = + when (this) { FOLD_UPDATE_START_OPENING -> "START_OPENING" FOLD_UPDATE_START_CLOSING -> "START_CLOSING" FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE" @@ -243,15 +245,12 @@ private fun stateToString(@FoldUpdate update: Int): String { FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED" else -> "UNKNOWN" } -} private const val TAG = "DeviceFoldProvider" private const val DEBUG = false /** Threshold after which we consider the device fully unfolded. */ -@VisibleForTesting -const val FULLY_OPEN_THRESHOLD_DEGREES = 15f +@VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f /** Fold animation on top of apps only when the angle exceeds this threshold. */ -@VisibleForTesting -const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60 +@VisibleForTesting const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60 diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt new file mode 100644 index 000000000000..0cf8224d3a3f --- /dev/null +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.unfold.updates + +import android.content.Context +import android.os.RemoteException +import android.view.IRotationWatcher +import android.view.IWindowManager +import android.view.Surface.Rotation +import com.android.systemui.unfold.dagger.UnfoldMain +import com.android.systemui.unfold.util.CallbackController +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * Allows to subscribe to rotation changes. + * + * This is needed as rotation updates from [IWindowManager] are received in a binder thread, while + * most of the times we want them in the main one. Updates are provided for the display associated + * to [context]. + */ +class RotationChangeProvider +@Inject +constructor( + private val windowManagerInterface: IWindowManager, + private val context: Context, + @UnfoldMain private val mainExecutor: Executor, +) : CallbackController<RotationChangeProvider.RotationListener> { + + private val listeners = mutableListOf<RotationListener>() + + private val rotationWatcher = RotationWatcher() + + override fun addCallback(listener: RotationListener) { + mainExecutor.execute { + if (listeners.isEmpty()) { + subscribeToRotation() + } + listeners += listener + } + } + + override fun removeCallback(listener: RotationListener) { + mainExecutor.execute { + listeners -= listener + if (listeners.isEmpty()) { + unsubscribeToRotation() + } + } + } + + private fun subscribeToRotation() { + try { + windowManagerInterface.watchRotation(rotationWatcher, context.displayId) + } catch (e: RemoteException) { + throw e.rethrowFromSystemServer() + } + } + + private fun unsubscribeToRotation() { + try { + windowManagerInterface.removeRotationWatcher(rotationWatcher) + } catch (e: RemoteException) { + throw e.rethrowFromSystemServer() + } + } + + /** Gets notified of rotation changes. */ + fun interface RotationListener { + /** Called once rotation changes. */ + fun onRotationChanged(@Rotation newRotation: Int) + } + + private inner class RotationWatcher : IRotationWatcher.Stub() { + override fun onRotationChanged(rotation: Int) { + mainExecutor.execute { listeners.forEach { it.onRotationChanged(rotation) } } + } + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 6417db0eb050..29194c58bd0c 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -657,25 +657,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.mBindingServices.removeIf(filter); userState.mCrashedServices.removeIf(filter); final Iterator<ComponentName> it = userState.mEnabledServices.iterator(); + boolean anyServiceRemoved = false; while (it.hasNext()) { final ComponentName comp = it.next(); final String compPkg = comp.getPackageName(); if (compPkg.equals(packageName)) { it.remove(); - // Update the enabled services setting. - persistComponentNamesToSettingLocked( - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - userState.mEnabledServices, userId); - // Update the touch exploration granted services setting. userState.mTouchExplorationGrantedServices.remove(comp); - persistComponentNamesToSettingLocked( - Settings.Secure. - TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, - userState.mTouchExplorationGrantedServices, userId); - onUserStateChangedLocked(userState); - return; + anyServiceRemoved = true; } } + if (anyServiceRemoved) { + // Update the enabled services setting. + persistComponentNamesToSettingLocked( + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userState.mEnabledServices, userId); + // Update the touch exploration granted services setting. + persistComponentNamesToSettingLocked( + Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, + userState.mTouchExplorationGrantedServices, userId); + onUserStateChangedLocked(userState); + } } } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index ca7fe0c571d1..14cfce7cc679 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -3722,21 +3722,34 @@ public class UserBackupManagerService { Slog.w(TAG, "agentDisconnected: the backup agent for " + packageName + " died: cancel current operations"); - // handleCancel() causes the PerformFullTransportBackupTask to go on to - // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so - // that the package being backed up doesn't get stuck in restricted mode until the - // backup time-out elapses. - for (int token : mOperationStorage.operationTokensForPackage(packageName)) { - if (MORE_DEBUG) { - Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:" - + Integer.toHexString(token)); + // Offload operation cancellation off the main thread as the cancellation callbacks + // might call out to BackupTransport. Other operations started on the same package + // before the cancellation callback has executed will also be cancelled by the callback. + Runnable cancellationRunnable = () -> { + // handleCancel() causes the PerformFullTransportBackupTask to go on to + // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so + // that the package being backed up doesn't get stuck in restricted mode until the + // backup time-out elapses. + for (int token : mOperationStorage.operationTokensForPackage(packageName)) { + if (MORE_DEBUG) { + Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:" + + Integer.toHexString(token)); + } + handleCancel(token, true /* cancelAll */); } - handleCancel(token, true /* cancelAll */); - } + }; + getThreadForAsyncOperation(/* operationName */ "agent-disconnected", + cancellationRunnable).start(); + mAgentConnectLock.notifyAll(); } } + @VisibleForTesting + Thread getThreadForAsyncOperation(String operationName, Runnable operation) { + return new Thread(operation, operationName); + } + /** * An application being installed will need a restore pass, then the {@link PackageManager} will * need to be told when the restore is finished. diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 8d878af4b788..fcc1ef2a3882 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -152,6 +152,8 @@ final class UiModeManagerService extends SystemService { // flag set by resource, whether to start dream immediately upon docking even if unlocked. private boolean mStartDreamImmediatelyOnDock = true; + // flag set by resource, whether to disable dreams when ambient mode suppression is enabled. + private boolean mDreamsDisabledByAmbientModeSuppression = false; // flag set by resource, whether to enable Car dock launch when starting car mode. private boolean mEnableCarDockLaunch = true; // flag set by resource, whether to lock UI mode to the default one or not. @@ -364,6 +366,11 @@ final class UiModeManagerService extends SystemService { mStartDreamImmediatelyOnDock = startDreamImmediatelyOnDock; } + @VisibleForTesting + void setDreamsDisabledByAmbientModeSuppression(boolean disabledByAmbientModeSuppression) { + mDreamsDisabledByAmbientModeSuppression = disabledByAmbientModeSuppression; + } + @Override public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { mCurrentUser = to.getUserIdentifier(); @@ -424,6 +431,8 @@ final class UiModeManagerService extends SystemService { final Resources res = context.getResources(); mStartDreamImmediatelyOnDock = res.getBoolean( com.android.internal.R.bool.config_startDreamImmediatelyOnDock); + mDreamsDisabledByAmbientModeSuppression = res.getBoolean( + com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig); mNightMode = res.getInteger( com.android.internal.R.integer.config_defaultNightMode); mDefaultUiModeType = res.getInteger( @@ -1827,10 +1836,14 @@ final class UiModeManagerService extends SystemService { // Send the new configuration. applyConfigurationExternallyLocked(); + final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppression + && mLocalPowerManager.isAmbientDisplaySuppressed(); + // If we did not start a dock app, then start dreaming if appropriate. - if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock - || mWindowManager.isKeyguardShowingAndNotOccluded() - || !mPowerManager.isInteractive())) { + if (category != null && !dockAppStarted && !dreamsSuppressed && ( + mStartDreamImmediatelyOnDock + || mWindowManager.isKeyguardShowingAndNotOccluded() + || !mPowerManager.isInteractive())) { mInjector.startDreamWhenDockedIfAppropriate(getContext()); } } diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java index 62f94ed05e0a..1a5f31c8ac90 100644 --- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java +++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java @@ -30,7 +30,10 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.sensors.BaseClientMonitor; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** Probe for ambient light. */ final class ALSProbe implements Probe { @@ -47,12 +50,18 @@ final class ALSProbe implements Probe { private boolean mEnabled = false; private boolean mDestroyed = false; + private boolean mDestroyRequested = false; + private boolean mDisableRequested = false; + private volatile NextConsumer mNextConsumer = null; private volatile float mLastAmbientLux = -1; private final SensorEventListener mLightSensorListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { mLastAmbientLux = event.values[0]; + if (mNextConsumer != null) { + completeNextConsumer(mLastAmbientLux); + } } @Override @@ -102,29 +111,84 @@ final class ALSProbe implements Probe { @Override public synchronized void enable() { - if (!mDestroyed) { + if (!mDestroyed && !mDestroyRequested) { + mDisableRequested = false; enableLightSensorLoggingLocked(); } } @Override public synchronized void disable() { - if (!mDestroyed) { + mDisableRequested = true; + + // if a final consumer is set it will call destroy/disable on the next value if requested + if (!mDestroyed && mNextConsumer == null) { disableLightSensorLoggingLocked(); } } @Override public synchronized void destroy() { - disable(); - mDestroyed = true; + mDestroyRequested = true; + + // if a final consumer is set it will call destroy/disable on the next value if requested + if (!mDestroyed && mNextConsumer == null) { + disable(); + mDestroyed = true; + } } /** The most recent lux reading. */ - public float getCurrentLux() { + public float getMostRecentLux() { return mLastAmbientLux; } + /** + * Register a listener for the next available ALS reading, which will be reported to the given + * consumer even if this probe is {@link #disable()}'ed or {@link #destroy()}'ed before a value + * is available. + * + * This method is intended to be used for event logs that occur when the screen may be + * off and sampling may have been {@link #disable()}'ed. In these cases, this method will turn + * on the sensor (if needed), fetch & report the first value, and then destroy or disable this + * probe (if needed). + * + * @param consumer consumer to notify when the data is available + * @param handler handler for notifying the consumer, or null + */ + public synchronized void awaitNextLux(@NonNull Consumer<Float> consumer, + @Nullable Handler handler) { + final NextConsumer nextConsumer = new NextConsumer(consumer, handler); + final float current = mLastAmbientLux; + if (current > 0) { + nextConsumer.consume(current); + } else if (mDestroyed) { + nextConsumer.consume(-1f); + } else if (mNextConsumer != null) { + mNextConsumer.add(nextConsumer); + } else { + mNextConsumer = nextConsumer; + enableLightSensorLoggingLocked(); + } + } + + private synchronized void completeNextConsumer(float value) { + Slog.v(TAG, "Finishing next consumer"); + + final NextConsumer consumer = mNextConsumer; + mNextConsumer = null; + + if (mDestroyRequested) { + destroy(); + } else if (mDisableRequested) { + disable(); + } + + if (consumer != null) { + consumer.consume(value); + } + } + private void enableLightSensorLoggingLocked() { if (!mEnabled) { mEnabled = true; @@ -160,4 +224,30 @@ final class ALSProbe implements Probe { + mLightSensorListener.hashCode()); disable(); } + + private static class NextConsumer { + @NonNull private final Consumer<Float> mConsumer; + @Nullable private final Handler mHandler; + @NonNull private final List<NextConsumer> mOthers = new ArrayList<>(); + + private NextConsumer(@NonNull Consumer<Float> consumer, @Nullable Handler handler) { + mConsumer = consumer; + mHandler = handler; + } + + public void consume(float value) { + if (mHandler != null) { + mHandler.post(() -> mConsumer.accept(value)); + } else { + mConsumer.accept(value); + } + for (NextConsumer c : mOthers) { + c.consume(value); + } + } + + public void add(NextConsumer consumer) { + mOthers.add(consumer); + } + } } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java index d6ca8a68145e..27a70c51f667 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java @@ -62,8 +62,7 @@ public class BiometricFrameworkStatsLogger { /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ public void authenticate(OperationContext operationContext, int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, - int authState, boolean requireConfirmation, - int targetUserId, float ambientLightLux) { + int authState, boolean requireConfirmation, int targetUserId, float ambientLightLux) { FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED, statsModality, targetUserId, @@ -80,6 +79,16 @@ public class BiometricFrameworkStatsLogger { operationContext.isAod); } + /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ + public void authenticate(OperationContext operationContext, + int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, + int authState, boolean requireConfirmation, int targetUserId, ALSProbe alsProbe) { + alsProbe.awaitNextLux((ambientLightLux) -> { + authenticate(operationContext, statsModality, statsAction, statsClient, isDebug, + latency, authState, requireConfirmation, targetUserId, ambientLightLux); + }, null /* handler */); + } + /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */ public void enroll(int statsModality, int statsAction, int statsClient, int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) { diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java index 02b350e97ef8..55fe854e1404 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java @@ -220,7 +220,7 @@ public class BiometricLogger { + ", RequireConfirmation: " + requireConfirmation + ", State: " + authState + ", Latency: " + latency - + ", Lux: " + mALSProbe.getCurrentLux()); + + ", Lux: " + mALSProbe.getMostRecentLux()); } else { Slog.v(TAG, "Authentication latency: " + latency); } @@ -231,7 +231,7 @@ public class BiometricLogger { mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient, Utils.isDebugEnabled(context, targetUserId), - latency, authState, requireConfirmation, targetUserId, mALSProbe.getCurrentLux()); + latency, authState, requireConfirmation, targetUserId, mALSProbe); } /** Log enrollment outcome. */ @@ -245,7 +245,7 @@ public class BiometricLogger { + ", User: " + targetUserId + ", Client: " + mStatsClient + ", Latency: " + latency - + ", Lux: " + mALSProbe.getCurrentLux() + + ", Lux: " + mALSProbe.getMostRecentLux() + ", Success: " + enrollSuccessful); } else { Slog.v(TAG, "Enroll latency: " + latency); @@ -256,7 +256,7 @@ public class BiometricLogger { } mSink.enroll(mStatsModality, mStatsAction, mStatsClient, - targetUserId, latency, enrollSuccessful, mALSProbe.getCurrentLux()); + targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux()); } /** Report unexpected enrollment reported by the HAL. */ diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index b3f42be41cd7..a778b573f225 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -16,6 +16,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR; import android.annotation.NonNull; @@ -57,6 +58,7 @@ import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; import com.android.server.biometrics.sensors.fingerprint.Udfps; +import java.time.Clock; import java.util.ArrayList; import java.util.function.Supplier; @@ -88,7 +90,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> private long mWaitForAuthKeyguard; private long mWaitForAuthBp; private long mIgnoreAuthFor; + private long mSideFpsLastAcquireStartTime; private Runnable mAuthSuccessRunnable; + private final Clock mClock; FingerprintAuthenticationClient( @NonNull Context context, @@ -112,7 +116,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps, - @NonNull Handler handler) { + @NonNull Handler handler, + @NonNull Clock clock) { super( context, lazyDaemon, @@ -154,6 +159,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mSkipWaitForPowerVendorAcquireMessage = context.getResources().getInteger( R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage); + mSideFpsLastAcquireStartTime = -1; + mClock = clock; if (mSensorProps.isAnySidefpsType()) { if (Build.isDebuggable()) { @@ -235,8 +242,14 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> return; } delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp; - Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps waiting for power for: " - + delay + "ms"); + + if (mSideFpsLastAcquireStartTime != -1) { + delay = Math.max(0, + delay - (mClock.millis() - mSideFpsLastAcquireStartTime)); + } + + Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps " + + "waiting for power until: " + delay + "ms"); } if (mHandler.hasMessages(MESSAGE_FINGER_UP)) { @@ -260,13 +273,15 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo)); super.onAcquired(acquiredInfo, vendorCode); if (mSensorProps.isAnySidefpsType()) { + if (acquiredInfo == FINGERPRINT_ACQUIRED_START) { + mSideFpsLastAcquireStartTime = mClock.millis(); + } final boolean shouldLookForVendor = mSkipWaitForPowerAcquireMessage == FINGERPRINT_ACQUIRED_VENDOR; final boolean acquireMessageMatch = acquiredInfo == mSkipWaitForPowerAcquireMessage; final boolean vendorMessageMatch = vendorCode == mSkipWaitForPowerVendorAcquireMessage; final boolean ignorePowerPress = - (acquireMessageMatch && !shouldLookForVendor) || (shouldLookForVendor - && acquireMessageMatch && vendorMessageMatch); + acquireMessageMatch && (!shouldLookForVendor || vendorMessageMatch); if (ignorePowerPress) { Slog.d(TAG, "(sideFPS) onFingerUp"); @@ -333,6 +348,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mALSProbeCallback.getProbe().disable(); } }); + if (getBiometricContext().isAwake()) { + mALSProbeCallback.getProbe().enable(); + } if (session.hasContextMethods()) { return session.getSession().authenticateWithContext(mOperationId, opContext); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 6f6c09b91a66..dabb2cf5819a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -47,6 +47,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemClock; import android.os.UserManager; import android.util.Slog; import android.util.SparseArray; @@ -447,7 +448,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mBiometricContext, isStrongBiometric, mTaskStackListener, mSensors.get(sensorId).getLockoutCache(), mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, - mSensors.get(sensorId).getSensorProperties(), mHandler); + mSensors.get(sensorId).getSensorProperties(), mHandler, + SystemClock.elapsedRealtimeClock()); scheduleForSensor(sensorId, client, mBiometricStateCallback); }); } diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index 17215e5ae4ad..8f59ffd30bba 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -220,6 +220,11 @@ public class BrightnessTracker { } private void backgroundStart(float initialBrightness) { + synchronized (mDataCollectionLock) { + if (mStarted) { + return; + } + } if (DEBUG) { Slog.d(TAG, "Background start"); } @@ -250,6 +255,11 @@ public class BrightnessTracker { /** Stop listening for events */ void stop() { + synchronized (mDataCollectionLock) { + if (!mStarted) { + return; + } + } if (DEBUG) { Slog.d(TAG, "Stop"); } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 1ae37bb5d66b..687d03d4f774 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -150,7 +150,7 @@ import javax.xml.datatype.DatatypeConfigurationException; * <quirk>canSetBrightnessViaHwc</quirk> * </quirks> * - * <autoBrightness> + * <autoBrightness enable="true"> * <brighteningLightDebounceMillis> * 2000 * </brighteningLightDebounceMillis> @@ -507,6 +507,11 @@ public class DisplayDeviceConfig { private long mAutoBrightnessDarkeningLightDebounce = INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE; + // This setting allows non-default displays to have autobrightness enabled. + private boolean mAutoBrightnessAvailable = false; + // This stores the raw value loaded from the config file - true if not written. + private boolean mDdcAutoBrightnessAvailable = true; + // Brightness Throttling data may be updated via the DeviceConfig. Here we store the original // data, which comes from the ddc, and the current one, which may be the DeviceConfig // overwritten value. @@ -1119,6 +1124,10 @@ public class DisplayDeviceConfig { return mProximitySensor; } + boolean isAutoBrightnessAvailable() { + return mAutoBrightnessAvailable; + } + /** * @param quirkValue The quirk to test. * @return {@code true} if the specified quirk is present in this configuration, {@code false} @@ -1184,6 +1193,60 @@ public class DisplayDeviceConfig { return mBrightnessLevelsNits; } + /** + * @return Default peak refresh rate of the associated display + */ + public int getDefaultPeakRefreshRate() { + return mContext.getResources().getInteger(R.integer.config_defaultPeakRefreshRate); + } + + /** + * @return Default refresh rate of the associated display + */ + public int getDefaultRefreshRate() { + return mContext.getResources().getInteger(R.integer.config_defaultRefreshRate); + } + + /** + * @return An array of lower display brightness thresholds. This, in combination with lower + * ambient brightness thresholds help define buckets in which the refresh rate switching is not + * allowed + */ + public int[] getLowDisplayBrightnessThresholds() { + return mContext.getResources().getIntArray( + R.array.config_brightnessThresholdsOfPeakRefreshRate); + } + + /** + * @return An array of lower ambient brightness thresholds. This, in combination with lower + * display brightness thresholds help define buckets in which the refresh rate switching is not + * allowed + */ + public int[] getLowAmbientBrightnessThresholds() { + return mContext.getResources().getIntArray( + R.array.config_ambientThresholdsOfPeakRefreshRate); + } + + /** + * @return An array of high display brightness thresholds. This, in combination with high + * ambient brightness thresholds help define buckets in which the refresh rate switching is not + * allowed + */ + public int[] getHighDisplayBrightnessThresholds() { + return mContext.getResources().getIntArray( + R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate); + } + + /** + * @return An array of high ambient brightness thresholds. This, in combination with high + * display brightness thresholds help define buckets in which the refresh rate switching is not + * allowed + */ + public int[] getHighAmbientBrightnessThresholds() { + return mContext.getResources().getIntArray( + R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate); + } + @Override public String toString() { return "DisplayDeviceConfig{" @@ -1271,6 +1334,8 @@ public class DisplayDeviceConfig { + mAutoBrightnessDarkeningLightDebounce + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux) + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits) + + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable + + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable + "}"; } @@ -1349,6 +1414,7 @@ public class DisplayDeviceConfig { loadBrightnessChangeThresholdsFromXml(); setProxSensorUnspecified(); loadAutoBrightnessConfigsFromConfigXml(); + loadAutoBrightnessAvailableFromConfigXml(); mLoadedFrom = "<config.xml>"; } @@ -1367,6 +1433,7 @@ public class DisplayDeviceConfig { setSimpleMappingStrategyValues(); loadAmbientLightSensorFromConfigXml(); setProxSensorUnspecified(); + loadAutoBrightnessAvailableFromConfigXml(); } private void copyUninitializedValuesFromSecondaryConfig(DisplayConfiguration defaultConfig) { @@ -1559,9 +1626,11 @@ public class DisplayDeviceConfig { } private void loadAutoBrightnessConfigValues(DisplayConfiguration config) { - loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness()); - loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness()); - loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness()); + final AutoBrightness autoBrightness = config.getAutoBrightness(); + loadAutoBrightnessBrighteningLightDebounce(autoBrightness); + loadAutoBrightnessDarkeningLightDebounce(autoBrightness); + loadAutoBrightnessDisplayBrightnessMapping(autoBrightness); + loadEnableAutoBrightness(autoBrightness); } /** @@ -1623,6 +1692,11 @@ public class DisplayDeviceConfig { } } + private void loadAutoBrightnessAvailableFromConfigXml() { + mAutoBrightnessAvailable = mContext.getResources().getBoolean( + R.bool.config_automatic_brightness_available); + } + private void loadBrightnessMapFromConfigXml() { // Use the config.xml mapping final Resources res = mContext.getResources(); @@ -2263,6 +2337,20 @@ public class DisplayDeviceConfig { return levels; } + private void loadEnableAutoBrightness(AutoBrightness autobrightness) { + // mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the + // config.xml values if the autobrightness tag is not defined in the ddc file. + // Autobrightness can still be turned off globally via config_automatic_brightness_available + mDdcAutoBrightnessAvailable = true; + if (autobrightness != null) { + mDdcAutoBrightnessAvailable = autobrightness.getEnabled(); + } + + mAutoBrightnessAvailable = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available) + && mDdcAutoBrightnessAvailable; + } + static class SensorData { public String type; public String name; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b153c1ba98e0..b8ff6ed93666 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1519,6 +1519,7 @@ public final class DisplayManagerService extends SystemService { display.setUserDisabledHdrTypes(mUserDisabledHdrTypes); } if (isDefault) { + notifyDefaultDisplayDeviceUpdated(display); recordStableDisplayStatsIfNeededLocked(display); recordTopInsetLocked(display); } @@ -1612,6 +1613,11 @@ public final class DisplayManagerService extends SystemService { mHandler.post(work); } final int displayId = display.getDisplayIdLocked(); + + if (displayId == Display.DEFAULT_DISPLAY) { + notifyDefaultDisplayDeviceUpdated(display); + } + DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { dpc.onDisplayChanged(); @@ -1621,6 +1627,11 @@ public final class DisplayManagerService extends SystemService { handleLogicalDisplayChangedLocked(display); } + private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) { + mDisplayModeDirector.defaultDisplayDeviceUpdated(display.getPrimaryDisplayDeviceLocked() + .mDisplayDeviceConfig); + } + private void handleLogicalDisplayDeviceStateTransitionLocked(@NonNull LogicalDisplay display) { final int displayId = display.getDisplayIdLocked(); final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); @@ -1759,9 +1770,13 @@ public final class DisplayManagerService extends SystemService { if (displayDevice == null) { return; } - mPersistentDataStore.setUserPreferredResolution( - displayDevice, resolutionWidth, resolutionHeight); - mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate); + try { + mPersistentDataStore.setUserPreferredResolution( + displayDevice, resolutionWidth, resolutionHeight); + mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate); + } finally { + mPersistentDataStore.saveIfNeeded(); + } } private void setUserPreferredModeForDisplayLocked(int displayId, Display.Mode mode) { diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index ac72b1725432..912b1b2e8da2 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -79,6 +79,7 @@ import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.concurrent.Callable; /** * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically @@ -153,8 +154,10 @@ public class DisplayModeDirector { mSupportedModesByDisplay = new SparseArray<>(); mDefaultModeByDisplay = new SparseArray<>(); mAppRequestObserver = new AppRequestObserver(); - mSettingsObserver = new SettingsObserver(context, handler); mDisplayObserver = new DisplayObserver(context, handler); + mDeviceConfig = injector.getDeviceConfig(); + mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); + mSettingsObserver = new SettingsObserver(context, handler); mBrightnessObserver = new BrightnessObserver(context, handler, injector); mUdfpsObserver = new UdfpsObserver(); final BallotBox ballotBox = (displayId, priority, vote) -> { @@ -164,10 +167,8 @@ public class DisplayModeDirector { }; mSensorObserver = new SensorObserver(context, ballotBox, injector); mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox); - mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(), mDeviceConfigDisplaySettings); - mDeviceConfig = injector.getDeviceConfig(); mAlwaysRespectAppRequest = false; } @@ -518,6 +519,15 @@ public class DisplayModeDirector { } /** + * A utility to make this class aware of the new display configs whenever the default display is + * changed + */ + public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) { + mSettingsObserver.setRefreshRates(displayDeviceConfig); + mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig); + } + + /** * When enabled the app requested display mode is always selected and all * other votes will be ignored. This is used for testing purposes. */ @@ -1132,10 +1142,19 @@ public class DisplayModeDirector { SettingsObserver(@NonNull Context context, @NonNull Handler handler) { super(handler); mContext = context; - mDefaultPeakRefreshRate = (float) context.getResources().getInteger( - R.integer.config_defaultPeakRefreshRate); + setRefreshRates(/* displayDeviceConfig= */ null); + } + + /** + * This is used to update the refresh rate configs from the DeviceConfig, which + * if missing from DisplayDeviceConfig, and finally fallback to config.xml. + */ + public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig) { + setDefaultPeakRefreshRate(displayDeviceConfig); mDefaultRefreshRate = - (float) context.getResources().getInteger(R.integer.config_defaultRefreshRate); + (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( + R.integer.config_defaultRefreshRate) + : (float) displayDeviceConfig.getDefaultRefreshRate(); } public void observe() { @@ -1196,6 +1215,23 @@ public class DisplayModeDirector { } } + private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig) { + Float defaultPeakRefreshRate = null; + try { + defaultPeakRefreshRate = + mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate(); + } catch (Exception exception) { + // Do nothing + } + if (defaultPeakRefreshRate == null) { + defaultPeakRefreshRate = + (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( + R.integer.config_defaultPeakRefreshRate) + : (float) displayDeviceConfig.getDefaultPeakRefreshRate(); + } + mDefaultPeakRefreshRate = defaultPeakRefreshRate; + } + private void updateLowPowerModeSettingLocked() { boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0; @@ -1508,12 +1544,31 @@ public class DisplayModeDirector { mContext = context; mHandler = handler; mInjector = injector; + updateBlockingZoneThresholds(/* displayDeviceConfig= */ null); + mRefreshRateInHighZone = context.getResources().getInteger( + R.integer.config_fixedRefreshRateInHighZone); + } - mLowDisplayBrightnessThresholds = context.getResources().getIntArray( - R.array.config_brightnessThresholdsOfPeakRefreshRate); - mLowAmbientBrightnessThresholds = context.getResources().getIntArray( - R.array.config_ambientThresholdsOfPeakRefreshRate); - + /** + * This is used to update the blocking zone thresholds from the DeviceConfig, which + * if missing from DisplayDeviceConfig, and finally fallback to config.xml. + */ + public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig) { + loadLowBrightnessThresholds(displayDeviceConfig); + loadHighBrightnessThresholds(displayDeviceConfig); + } + + private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) { + mLowDisplayBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(), + () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), + R.array.config_brightnessThresholdsOfPeakRefreshRate, + displayDeviceConfig); + mLowAmbientBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(), + () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), + R.array.config_ambientThresholdsOfPeakRefreshRate, + displayDeviceConfig); if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) { throw new RuntimeException("display low brightness threshold array and ambient " + "brightness threshold array have different length: " @@ -1522,11 +1577,19 @@ public class DisplayModeDirector { + ", ambientBrightnessThresholds=" + Arrays.toString(mLowAmbientBrightnessThresholds)); } + } - mHighDisplayBrightnessThresholds = context.getResources().getIntArray( - R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate); - mHighAmbientBrightnessThresholds = context.getResources().getIntArray( - R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate); + private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) { + mHighDisplayBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(), + () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), + R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, + displayDeviceConfig); + mHighAmbientBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(), + () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), + R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, + displayDeviceConfig); if (mHighDisplayBrightnessThresholds.length != mHighAmbientBrightnessThresholds.length) { throw new RuntimeException("display high brightness threshold array and ambient " @@ -1536,8 +1599,32 @@ public class DisplayModeDirector { + ", ambientBrightnessThresholds=" + Arrays.toString(mHighAmbientBrightnessThresholds)); } - mRefreshRateInHighZone = context.getResources().getInteger( - R.integer.config_fixedRefreshRateInHighZone); + } + + private int[] loadBrightnessThresholds( + Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable, + Callable<int[]> loadFromDisplayDeviceConfigCallable, + int brightnessThresholdOfFixedRefreshRateKey, + DisplayDeviceConfig displayDeviceConfig) { + int[] brightnessThresholds = null; + try { + brightnessThresholds = + loadFromDeviceConfigDisplaySettingsCallable.call(); + } catch (Exception exception) { + // Do nothing + } + if (brightnessThresholds == null) { + try { + brightnessThresholds = + (displayDeviceConfig == null) ? mContext.getResources().getIntArray( + brightnessThresholdOfFixedRefreshRateKey) + : loadFromDisplayDeviceConfigCallable.call(); + } catch (Exception e) { + Slog.e(TAG, "Unexpectedly failed to load display brightness threshold"); + e.printStackTrace(); + } + } + return brightnessThresholds; } /** @@ -1590,7 +1677,6 @@ public class DisplayModeDirector { mLowAmbientBrightnessThresholds = lowAmbientBrightnessThresholds; } - int[] highDisplayBrightnessThresholds = mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(); int[] highAmbientBrightnessThresholds = diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 458cf1a31b97..95dc23fc3120 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -558,13 +558,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness( pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR)); - // Check the setting, but also verify that it is the default display. Only the default - // display has an automatic brightness controller running. - // TODO: b/179021925 - Fix to work with multiple displays - mUseSoftwareAutoBrightnessConfig = resources.getBoolean( - com.android.internal.R.bool.config_automatic_brightness_available) - && mDisplayId == Display.DEFAULT_DISPLAY; - mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean( com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing); @@ -938,6 +931,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } private void setUpAutoBrightness(Resources resources, Handler handler) { + mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable(); + if (!mUseSoftwareAutoBrightnessConfig) { return; } diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java index b9a0738d15c4..a11f1721a4b9 100644 --- a/services/core/java/com/android/server/display/PersistentDataStore.java +++ b/services/core/java/com/android/server/display/PersistentDataStore.java @@ -75,6 +75,11 @@ import java.util.Objects; * </brightness-curve> * </brightness-configuration> * </brightness-configurations> + * <display-mode>0< + * <resolution-width>1080</resolution-width> + * <resolution-height>1920</resolution-height> + * <refresh-rate>60</refresh-rate> + * </display-mode> * </display> * </display-states> * <stable-device-values> @@ -121,6 +126,10 @@ final class PersistentDataStore { private static final String ATTR_PACKAGE_NAME = "package-name"; private static final String ATTR_TIME_STAMP = "timestamp"; + private static final String TAG_RESOLUTION_WIDTH = "resolution-width"; + private static final String TAG_RESOLUTION_HEIGHT = "resolution-height"; + private static final String TAG_REFRESH_RATE = "refresh-rate"; + // Remembered Wifi display devices. private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>(); @@ -696,6 +705,18 @@ final class PersistentDataStore { case TAG_BRIGHTNESS_CONFIGURATIONS: mDisplayBrightnessConfigurations.loadFromXml(parser); break; + case TAG_RESOLUTION_WIDTH: + String width = parser.nextText(); + mWidth = Integer.parseInt(width); + break; + case TAG_RESOLUTION_HEIGHT: + String height = parser.nextText(); + mHeight = Integer.parseInt(height); + break; + case TAG_REFRESH_RATE: + String refreshRate = parser.nextText(); + mRefreshRate = Float.parseFloat(refreshRate); + break; } } } @@ -712,6 +733,18 @@ final class PersistentDataStore { serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); mDisplayBrightnessConfigurations.saveToXml(serializer); serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); + + serializer.startTag(null, TAG_RESOLUTION_WIDTH); + serializer.text(Integer.toString(mWidth)); + serializer.endTag(null, TAG_RESOLUTION_WIDTH); + + serializer.startTag(null, TAG_RESOLUTION_HEIGHT); + serializer.text(Integer.toString(mHeight)); + serializer.endTag(null, TAG_RESOLUTION_HEIGHT); + + serializer.startTag(null, TAG_REFRESH_RATE); + serializer.text(Float.toString(mRefreshRate)); + serializer.endTag(null, TAG_REFRESH_RATE); } public void dump(final PrintWriter pw, final String prefix) { @@ -719,6 +752,8 @@ final class PersistentDataStore { pw.println(prefix + "BrightnessValue=" + mBrightness); pw.println(prefix + "DisplayBrightnessConfigurations: "); mDisplayBrightnessConfigurations.dump(pw, prefix); + pw.println(prefix + "Resolution=" + mWidth + " " + mHeight); + pw.println(prefix + "RefreshRate=" + mRefreshRate); } } diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 7964fd550266..c9557d60c8b7 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -246,8 +246,8 @@ public final class DreamManagerService extends SystemService { // Because napping could cause the screen to turn off immediately if the dream // cannot be started, we keep one eye open and gently poke user activity. long time = SystemClock.uptimeMillis(); - mPowerManager.userActivity(time, true /*noChangeLights*/); - mPowerManager.nap(time); + mPowerManager.userActivity(time, /* noChangeLights= */ true); + mPowerManagerInternal.nap(time, /* allowWake= */ true); } private void requestAwakenInternal(String reason) { @@ -637,7 +637,7 @@ public final class DreamManagerService extends SystemService { @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) throws RemoteException { - new DreamShellCommand(DreamManagerService.this, mPowerManager) + new DreamShellCommand(DreamManagerService.this) .exec(this, in, out, err, args, callback, resultReceiver); } diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java index eae7e80a89f1..ab84ae4c08a2 100644 --- a/services/core/java/com/android/server/dreams/DreamShellCommand.java +++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java @@ -18,10 +18,8 @@ package com.android.server.dreams; import android.annotation.NonNull; import android.os.Binder; -import android.os.PowerManager; import android.os.Process; import android.os.ShellCommand; -import android.os.SystemClock; import android.text.TextUtils; import android.util.Slog; @@ -34,11 +32,9 @@ public class DreamShellCommand extends ShellCommand { private static final boolean DEBUG = true; private static final String TAG = "DreamShellCommand"; private final @NonNull DreamManagerService mService; - private final @NonNull PowerManager mPowerManager; - DreamShellCommand(@NonNull DreamManagerService service, @NonNull PowerManager powerManager) { + DreamShellCommand(@NonNull DreamManagerService service) { mService = service; - mPowerManager = powerManager; } @Override @@ -67,8 +63,6 @@ public class DreamShellCommand extends ShellCommand { } private int startDreaming() { - mPowerManager.wakeUp(SystemClock.uptimeMillis(), - PowerManager.WAKE_REASON_PLUGGED_IN, "shell:cmd:android.service.dreams:DREAM"); mService.requestStartDreamFromShell(); return 0; } diff --git a/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java b/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java index aad7b1457b3c..7440fc75bf51 100644 --- a/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java +++ b/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java @@ -40,13 +40,24 @@ import java.util.Set; public class AmbientDisplaySuppressionController { private static final String TAG = "AmbientDisplaySuppressionController"; - private final Context mContext; private final Set<Pair<String, Integer>> mSuppressionTokens; + private final AmbientDisplaySuppressionChangedCallback mCallback; private IStatusBarService mStatusBarService; - AmbientDisplaySuppressionController(Context context) { - mContext = requireNonNull(context); + /** Interface to get a list of available logical devices. */ + interface AmbientDisplaySuppressionChangedCallback { + /** + * Called when the suppression state changes. + * + * @param isSuppressed Whether ambient is suppressed. + */ + void onSuppressionChanged(boolean isSuppressed); + } + + AmbientDisplaySuppressionController( + @NonNull AmbientDisplaySuppressionChangedCallback callback) { mSuppressionTokens = Collections.synchronizedSet(new ArraySet<>()); + mCallback = requireNonNull(callback); } /** @@ -58,6 +69,7 @@ public class AmbientDisplaySuppressionController { */ public void suppress(@NonNull String token, int callingUid, boolean suppress) { Pair<String, Integer> suppressionToken = Pair.create(requireNonNull(token), callingUid); + final boolean wasSuppressed = isSuppressed(); if (suppress) { mSuppressionTokens.add(suppressionToken); @@ -65,9 +77,14 @@ public class AmbientDisplaySuppressionController { mSuppressionTokens.remove(suppressionToken); } + final boolean isSuppressed = isSuppressed(); + if (isSuppressed != wasSuppressed) { + mCallback.onSuppressionChanged(isSuppressed); + } + try { synchronized (mSuppressionTokens) { - getStatusBar().suppressAmbientDisplay(isSuppressed()); + getStatusBar().suppressAmbientDisplay(isSuppressed); } } catch (RemoteException e) { Slog.e(TAG, "Failed to suppress ambient display", e); diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java index 9fe53fbfaf25..431cf3861804 100644 --- a/services/core/java/com/android/server/power/PowerGroup.java +++ b/services/core/java/com/android/server/power/PowerGroup.java @@ -229,8 +229,8 @@ public class PowerGroup { } } - boolean dreamLocked(long eventTime, int uid) { - if (eventTime < mLastWakeTime || mWakefulness != WAKEFULNESS_AWAKE) { + boolean dreamLocked(long eventTime, int uid, boolean allowWake) { + if (eventTime < mLastWakeTime || (!allowWake && mWakefulness != WAKEFULNESS_AWAKE)) { return false; } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index f200564a9007..725fb3fec616 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -127,6 +127,7 @@ import com.android.server.am.BatteryStatsService; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.power.AmbientDisplaySuppressionController.AmbientDisplaySuppressionChangedCallback; import com.android.server.power.batterysaver.BatterySaverController; import com.android.server.power.batterysaver.BatterySaverPolicy; import com.android.server.power.batterysaver.BatterySaverStateMachine; @@ -467,6 +468,9 @@ public final class PowerManagerService extends SystemService // True if the device should wake up when plugged or unplugged. private boolean mWakeUpWhenPluggedOrUnpluggedConfig; + // True if the device should keep dreaming when undocked. + private boolean mKeepDreamingWhenUndockingConfig; + // True if the device should wake up when plugged or unplugged in theater mode. private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig; @@ -504,6 +508,9 @@ public final class PowerManagerService extends SystemService // effectively and terminate the dream. Use -1 to disable this safety feature. private int mDreamsBatteryLevelDrainCutoffConfig; + // Whether dreams should be disabled when ambient mode is suppressed. + private boolean mDreamsDisabledByAmbientModeSuppressionConfig; + // True if dreams are enabled by the user. private boolean mDreamsEnabledSetting; @@ -961,8 +968,8 @@ public final class PowerManagerService extends SystemService } AmbientDisplaySuppressionController createAmbientDisplaySuppressionController( - Context context) { - return new AmbientDisplaySuppressionController(context); + @NonNull AmbientDisplaySuppressionChangedCallback callback) { + return new AmbientDisplaySuppressionController(callback); } InattentiveSleepWarningController createInattentiveSleepWarningController() { @@ -1041,7 +1048,8 @@ public final class PowerManagerService extends SystemService mConstants = new Constants(mHandler); mAmbientDisplayConfiguration = mInjector.createAmbientDisplayConfiguration(context); mAmbientDisplaySuppressionController = - mInjector.createAmbientDisplaySuppressionController(context); + mInjector.createAmbientDisplaySuppressionController( + mAmbientSuppressionChangedCallback); mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock); mFaceDownDetector = new FaceDownDetector(this::onFlip); mScreenUndimDetector = new ScreenUndimDetector(); @@ -1374,6 +1382,8 @@ public final class PowerManagerService extends SystemService com.android.internal.R.bool.config_powerDecoupleInteractiveModeFromDisplay); mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean( com.android.internal.R.bool.config_unplugTurnsOnScreen); + mKeepDreamingWhenUndockingConfig = resources.getBoolean( + com.android.internal.R.bool.config_keepDreamingWhenUndocking); mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig = resources.getBoolean( com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug); mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean( @@ -1398,6 +1408,8 @@ public final class PowerManagerService extends SystemService com.android.internal.R.integer.config_dreamsBatteryLevelMinimumWhenNotPowered); mDreamsBatteryLevelDrainCutoffConfig = resources.getInteger( com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff); + mDreamsDisabledByAmbientModeSuppressionConfig = resources.getBoolean( + com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig); mDozeAfterScreenOff = resources.getBoolean( com.android.internal.R.bool.config_dozeAfterScreenOffByDefault); mMinimumScreenOffTimeoutConfig = resources.getInteger( @@ -1919,6 +1931,13 @@ public final class PowerManagerService extends SystemService } } + private void napInternal(long eventTime, int uid, boolean allowWake) { + synchronized (mLock) { + dreamPowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP), + eventTime, uid, allowWake); + } + } + private void onUserAttention() { synchronized (mLock) { if (userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP), @@ -2034,7 +2053,8 @@ public final class PowerManagerService extends SystemService } @GuardedBy("mLock") - private boolean dreamPowerGroupLocked(PowerGroup powerGroup, long eventTime, int uid) { + private boolean dreamPowerGroupLocked(PowerGroup powerGroup, long eventTime, int uid, + boolean allowWake) { if (DEBUG_SPEW) { Slog.d(TAG, "dreamPowerGroup: groupId=" + powerGroup.getGroupId() + ", eventTime=" + eventTime + ", uid=" + uid); @@ -2042,7 +2062,7 @@ public final class PowerManagerService extends SystemService if (!mBootCompleted || !mSystemReady) { return false; } - return powerGroup.dreamLocked(eventTime, uid); + return powerGroup.dreamLocked(eventTime, uid, allowWake); } @GuardedBy("mLock") @@ -2487,6 +2507,14 @@ public final class PowerManagerService extends SystemService return false; } + // Don't wake when undocking while dreaming if configured not to. + if (mKeepDreamingWhenUndockingConfig + && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING + && wasPowered && !mIsPowered + && oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) { + return false; + } + // Don't wake when undocked from wireless charger. // See WirelessChargerDetector for justification. if (wasPowered && !mIsPowered @@ -3086,7 +3114,8 @@ public final class PowerManagerService extends SystemService changed = sleepPowerGroupLocked(powerGroup, time, PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, Process.SYSTEM_UID); } else if (shouldNapAtBedTimeLocked()) { - changed = dreamPowerGroupLocked(powerGroup, time, Process.SYSTEM_UID); + changed = dreamPowerGroupLocked(powerGroup, time, + Process.SYSTEM_UID, /* allowWake= */ false); } else { changed = dozePowerGroupLocked(powerGroup, time, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, Process.SYSTEM_UID); @@ -3307,7 +3336,7 @@ public final class PowerManagerService extends SystemService } // Doze has ended or will be stopped. Update the power state. - sleepPowerGroupLocked(powerGroup, now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, + sleepPowerGroupLocked(powerGroup, now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, Process.SYSTEM_UID); } } @@ -3318,12 +3347,32 @@ public final class PowerManagerService extends SystemService } } + @GuardedBy("mLock") + private void onDreamSuppressionChangedLocked(final boolean isSuppressed) { + if (!mDreamsDisabledByAmbientModeSuppressionConfig) { + return; + } + if (!isSuppressed && mIsPowered && mDreamsSupportedConfig && mDreamsEnabledSetting + && shouldNapAtBedTimeLocked() && isItBedTimeYetLocked( + mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP))) { + napInternal(SystemClock.uptimeMillis(), Process.SYSTEM_UID, /* allowWake= */ true); + } else if (isSuppressed) { + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + + /** * Returns true if the {@code groupId} is allowed to dream in its current state. */ @GuardedBy("mLock") private boolean canDreamLocked(final PowerGroup powerGroup) { + final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppressionConfig + && mAmbientDisplaySuppressionController.isSuppressed(); + if (!mBootCompleted + || dreamsSuppressed || getGlobalWakefulnessLocked() != WAKEFULNESS_DREAMING || !mDreamsSupportedConfig || !mDreamsEnabledSetting @@ -4408,6 +4457,8 @@ public final class PowerManagerService extends SystemService + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig); pw.println(" mTheaterModeEnabled=" + mTheaterModeEnabled); + pw.println(" mKeepDreamingWhenUndockingConfig=" + + mKeepDreamingWhenUndockingConfig); pw.println(" mSuspendWhenScreenOffDueToProximityConfig=" + mSuspendWhenScreenOffDueToProximityConfig); pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig); @@ -5013,6 +5064,16 @@ public final class PowerManagerService extends SystemService } }; + private final AmbientDisplaySuppressionChangedCallback mAmbientSuppressionChangedCallback = + new AmbientDisplaySuppressionChangedCallback() { + @Override + public void onSuppressionChanged(boolean isSuppressed) { + synchronized (mLock) { + onDreamSuppressionChangedLocked(isSuppressed); + } + } + }; + /** * Callback for asynchronous operations performed by the power manager. */ @@ -5701,10 +5762,7 @@ public final class PowerManagerService extends SystemService final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - synchronized (mLock) { - dreamPowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP), - eventTime, uid); - } + napInternal(eventTime, uid, /* allowWake= */ false); } finally { Binder.restoreCallingIdentity(ident); } @@ -6632,6 +6690,16 @@ public final class PowerManagerService extends SystemService public boolean interceptPowerKeyDown(KeyEvent event) { return interceptPowerKeyDownInternal(event); } + + @Override + public void nap(long eventTime, boolean allowWake) { + napInternal(eventTime, Process.SYSTEM_UID, allowWake); + } + + @Override + public boolean isAmbientDisplaySuppressed() { + return mAmbientDisplaySuppressionController.isSuppressed(); + } } /** diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java index 0799b955b6f1..9c455dbb5320 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java +++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java @@ -65,6 +65,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { public final DeviceVibrationEffectAdapter deviceEffectAdapter; public final VibrationThread.VibratorManagerHooks vibratorManagerHooks; + // Not guarded by lock because they're not modified by this conductor, it's used here only to + // check immutable attributes. The status and other mutable states are changed by the service or + // by the vibrator steps. private final Vibration mVibration; private final SparseArray<VibratorController> mVibrators = new SparseArray<>(); @@ -405,6 +408,16 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { } } + /** Returns true if a cancellation signal was sent via {@link #notifyCancelled}. */ + public boolean wasNotifiedToCancel() { + if (Build.IS_DEBUGGABLE) { + expectIsVibrationThread(false); + } + synchronized (mLock) { + return mSignalCancel != null; + } + } + @GuardedBy("mLock") private boolean hasPendingNotifySignalLocked() { if (Build.IS_DEBUGGABLE) { diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 97275583ac36..14693599c3ec 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -852,8 +852,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } Vibration currentVibration = mCurrentVibration.getVibration(); - if (currentVibration.hasEnded()) { - // Current vibration is finishing up, it should not block incoming vibrations. + if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) { + // Current vibration has ended or is cancelling, should not block incoming vibrations. return null; } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index f25929c36060..189b86fd0ff1 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1166,7 +1166,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false, wpdData.mWidth, wpdData.mHeight, - wpdData.mPadding, mDisplayId); + wpdData.mPadding, mDisplayId, FLAG_SYSTEM | FLAG_LOCK); } catch (RemoteException e) { Slog.w(TAG, "Failed attaching wallpaper on display", e); if (wallpaper != null && !wallpaper.wallpaperUpdating diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 20059fe97b9c..2eb2cf643c42 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7638,6 +7638,31 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); } + /** + * Returns the requested {@link Configuration.Orientation} for the current activity. + * + * <p>When The current orientation is set to {@link SCREEN_ORIENTATION_BEHIND} it returns the + * requested orientation for the activity below which is the first activity with an explicit + * (different from {@link SCREEN_ORIENTATION_UNSET}) orientation which is not {@link + * SCREEN_ORIENTATION_BEHIND}. + */ + @Configuration.Orientation + @Override + int getRequestedConfigurationOrientation(boolean forDisplay) { + if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) { + // 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 */); + if (belowCandidate != null) { + return belowCandidate.getRequestedConfigurationOrientation(forDisplay); + } + } + return super.getRequestedConfigurationOrientation(forDisplay); + } + @Override void onCancelFixedRotationTransform(int originalDisplayRotation) { if (this != mDisplayContent.getLastOrientationSource()) { diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 30454d45574e..9f502ab6ca10 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -389,9 +389,9 @@ public class ActivityStartController { SafeActivityOptions bottomOptions = null; if (options != null) { // To ensure the first N-1 activities (N == total # of activities) are also launched - // into the correct display, use a copy of the passed-in options (keeping only - // display-related info) for these activities. - bottomOptions = options.selectiveCloneDisplayOptions(); + // into the correct display and root task, use a copy of the passed-in options (keeping + // only display-related and launch-root-task information) for these activities. + bottomOptions = options.selectiveCloneLaunchOptions(); } try { intents = ArrayUtils.filterNotNull(intents, Intent[]::new); diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index e3916cbc30bd..fe691c61a96b 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2593,6 +2593,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // activity lifecycle transaction to make sure the override pending app // transition will be applied immediately. targetActivity.applyOptionsAnimation(); + if (activityOptions != null && activityOptions.getLaunchCookie() != null) { + targetActivity.mLaunchCookie = activityOptions.getLaunchCookie(); + } } finally { mActivityMetricsLogger.notifyActivityLaunched(launchingState, START_TASK_TO_FRONT, false /* newActivityCreated */, diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index e69a732b3fde..6d1b5fa6034d 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -117,13 +117,13 @@ public class SafeActivityOptions { /** * To ensure that two activities, one using this object, and the other using the - * SafeActivityOptions returned from this function, are launched into the same display through - * ActivityStartController#startActivities, all display-related information, i.e. - * displayAreaToken, launchDisplayId and callerDisplayId, are cloned. + * SafeActivityOptions returned from this function, are launched into the same display/root task + * through ActivityStartController#startActivities, all display-related information, i.e. + * displayAreaToken, launchDisplayId, callerDisplayId and the launch root task are cloned. */ - @Nullable SafeActivityOptions selectiveCloneDisplayOptions() { - final ActivityOptions options = cloneLaunchingDisplayOptions(mOriginalOptions); - final ActivityOptions callerOptions = cloneLaunchingDisplayOptions(mCallerOptions); + @Nullable SafeActivityOptions selectiveCloneLaunchOptions() { + final ActivityOptions options = cloneLaunchingOptions(mOriginalOptions); + final ActivityOptions callerOptions = cloneLaunchingOptions(mCallerOptions); if (options == null && callerOptions == null) { return null; } @@ -136,11 +136,12 @@ public class SafeActivityOptions { return safeOptions; } - private ActivityOptions cloneLaunchingDisplayOptions(ActivityOptions options) { + private ActivityOptions cloneLaunchingOptions(ActivityOptions options) { return options == null ? null : ActivityOptions.makeBasic() .setLaunchTaskDisplayArea(options.getLaunchTaskDisplayArea()) .setLaunchDisplayId(options.getLaunchDisplayId()) - .setCallerDisplayId((options.getCallerDisplayId())); + .setCallerDisplayId(options.getCallerDisplayId()) + .setLaunchRootTask(options.getLaunchRootTask()); } /** diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 4a51b831da57..f53a1cfcfb3c 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -383,6 +383,7 @@ </xs:complexType> <xs:complexType name="autoBrightness"> + <xs:attribute name="enabled" type="xs:boolean" use="optional" default="true"/> <xs:sequence> <!-- Sets the debounce for autoBrightness brightening in millis--> <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger" diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 748ef4be9cc7..d89bd7cc9aa2 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -6,9 +6,11 @@ package com.android.server.display.config { method public final java.math.BigInteger getBrighteningLightDebounceMillis(); method public final java.math.BigInteger getDarkeningLightDebounceMillis(); method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping(); + method public boolean getEnabled(); method public final void setBrighteningLightDebounceMillis(java.math.BigInteger); method public final void setDarkeningLightDebounceMillis(java.math.BigInteger); method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping); + method public void setEnabled(boolean); } public class BrightnessThresholds { diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt index 45e0d09a1753..4ef6875dd310 100644 --- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt @@ -628,6 +628,7 @@ class SystemStubMultiUserDisableUninstallTest : BaseHostJUnit4Test() { CodePath.SAME, CodePath.DIFFERENT -> throw AssertionError("secondDataPath cannot be a data path") CodePath.SYSTEM -> assertThat(codePaths[1]).isEqualTo(stubFile.parent.toString()) + else -> {} } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt index b9d6b2ccd306..994df2288a5c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -392,7 +392,7 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { whenever(rule.mocks().appsFilter.getVisibilityAllowList( any(PackageDataSnapshot::class.java), argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java), - any() as ArrayMap<String, out PackageStateInternal> + any<ArrayMap<String, out PackageStateInternal>>() )) .thenReturn(list) } diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java index bccd8a0b14b4..9ae892286e55 100644 --- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.backup; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -61,6 +62,7 @@ import java.util.function.IntConsumer; public class UserBackupManagerServiceTest { private static final String TEST_PACKAGE = "package1"; private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE }; + private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 1; @Mock Context mContext; @Mock IBackupManagerMonitor mBackupManagerMonitor; @@ -179,6 +181,7 @@ public class UserBackupManagerServiceTest { mService.agentDisconnected("com.android.foo"); + mService.waitForAsyncOperation(); verify(mOperationStorage).cancelOperation(eq(123), eq(true), any(IntConsumer.class)); verify(mOperationStorage).cancelOperation(eq(456), eq(true), any()); verify(mOperationStorage).cancelOperation(eq(789), eq(true), any()); @@ -207,6 +210,8 @@ public class UserBackupManagerServiceTest { boolean isEnabledStatePersisted = false; boolean shouldUseNewBackupEligibilityRules = false; + private volatile Thread mWorkerThread = null; + TestBackupService(Context context, PackageManager packageManager, LifecycleOperationStorage operationStorage) { super(context, packageManager, operationStorage); @@ -229,5 +234,23 @@ public class UserBackupManagerServiceTest { boolean shouldUseNewBackupEligibilityRules() { return shouldUseNewBackupEligibilityRules; } + + @Override + Thread getThreadForAsyncOperation(String operationName, Runnable operation) { + mWorkerThread = super.getThreadForAsyncOperation(operationName, operation); + return mWorkerThread; + } + + private void waitForAsyncOperation() { + if (mWorkerThread == null) { + return; + } + + try { + mWorkerThread.join(/* millis */ WORKER_THREAD_TIMEOUT_MILLISECONDS); + } catch (InterruptedException e) { + fail("Failed waiting for worker thread to complete: " + e.getMessage()); + } + } } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java index 10f0a5cbbc40..68c9ce4a9f86 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.anyInt; 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.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -50,6 +51,9 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + @Presubmit @SmallTest @RunWith(AndroidTestingRunner.class) @@ -93,7 +97,7 @@ public class ALSProbeTest { mSensorEventListenerCaptor.getValue().onSensorChanged( new SensorEvent(mLightSensor, 1, 2, new float[]{value})); - assertThat(mProbe.getCurrentLux()).isEqualTo(value); + assertThat(mProbe.getMostRecentLux()).isEqualTo(value); } @Test @@ -121,13 +125,17 @@ public class ALSProbeTest { mProbe.destroy(); mProbe.enable(); + AtomicInteger lux = new AtomicInteger(10); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + verify(mSensorManager, never()).registerListener(any(), any(), anyInt()); verifyNoMoreInteractions(mSensorManager); + assertThat(lux.get()).isLessThan(0); } @Test public void testDisabledReportsNegativeValue() { - assertThat(mProbe.getCurrentLux()).isLessThan(0f); + assertThat(mProbe.getMostRecentLux()).isLessThan(0f); mProbe.enable(); verify(mSensorManager).registerListener( @@ -136,7 +144,7 @@ public class ALSProbeTest { new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f})); mProbe.disable(); - assertThat(mProbe.getCurrentLux()).isLessThan(0f); + assertThat(mProbe.getMostRecentLux()).isLessThan(0f); } @Test @@ -150,7 +158,7 @@ public class ALSProbeTest { verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue())); verifyNoMoreInteractions(mSensorManager); - assertThat(mProbe.getCurrentLux()).isLessThan(0f); + assertThat(mProbe.getMostRecentLux()).isLessThan(0f); } @Test @@ -166,7 +174,148 @@ public class ALSProbeTest { verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); verifyNoMoreInteractions(mSensorManager); - assertThat(mProbe.getCurrentLux()).isLessThan(0f); + assertThat(mProbe.getMostRecentLux()).isLessThan(0f); + } + + @Test + public void testNextLuxWhenAlreadyEnabledAndNotAvailable() { + testNextLuxWhenAlreadyEnabled(false /* dataIsAvailable */); + } + + @Test + public void testNextLuxWhenAlreadyEnabledAndAvailable() { + testNextLuxWhenAlreadyEnabled(true /* dataIsAvailable */); + } + + private void testNextLuxWhenAlreadyEnabled(boolean dataIsAvailable) { + final List<Integer> values = List.of(1, 2, 3, 4, 6); + mProbe.enable(); + + verify(mSensorManager).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + + if (dataIsAvailable) { + for (int v : values) { + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{v})); + } + } + AtomicInteger lux = new AtomicInteger(-1); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + if (!dataIsAvailable) { + for (int v : values) { + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{v})); + } + } + + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{200f})); + + // should remain enabled + assertThat(lux.get()).isEqualTo(values.get(dataIsAvailable ? values.size() - 1 : 0)); + verify(mSensorManager, never()).unregisterListener(any(SensorEventListener.class)); + verifyNoMoreInteractions(mSensorManager); + + final int anotherValue = 12; + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{12})); + assertThat(mProbe.getMostRecentLux()).isEqualTo(anotherValue); + } + + @Test + public void testNextLuxWhenNotEnabled() { + testNextLuxWhenNotEnabled(false /* enableWhileWaiting */); + } + + @Test + public void testNextLuxWhenNotEnabledButEnabledLater() { + testNextLuxWhenNotEnabled(true /* enableWhileWaiting */); + } + + private void testNextLuxWhenNotEnabled(boolean enableWhileWaiting) { + final List<Integer> values = List.of(1, 2, 3, 4, 6); + mProbe.disable(); + + AtomicInteger lux = new AtomicInteger(-1); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + + if (enableWhileWaiting) { + mProbe.enable(); + } + + verify(mSensorManager).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + for (int v : values) { + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{v})); + } + + // should restore the disabled state + assertThat(lux.get()).isEqualTo(values.get(0)); + verify(mSensorManager, enableWhileWaiting ? never() : times(1)).unregisterListener( + any(SensorEventListener.class)); + verifyNoMoreInteractions(mSensorManager); + } + + @Test + public void testNextLuxIsNotCanceledByDisableOrDestroy() { + final int value = 7; + AtomicInteger lux = new AtomicInteger(-1); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + + verify(mSensorManager).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + + mProbe.destroy(); + mProbe.disable(); + + assertThat(lux.get()).isEqualTo(-1); + + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{value})); + + assertThat(lux.get()).isEqualTo(value); + + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{value + 1})); + + // should remain destroyed + mProbe.enable(); + + assertThat(lux.get()).isEqualTo(value); + verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); + verifyNoMoreInteractions(mSensorManager); + } + + @Test + public void testMultipleNextConsumers() { + final int value = 7; + AtomicInteger lux = new AtomicInteger(-1); + AtomicInteger lux2 = new AtomicInteger(-1); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + mProbe.awaitNextLux((v) -> lux2.set(Math.round(v)), null /* handler */); + + verify(mSensorManager).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + mSensorEventListenerCaptor.getValue().onSensorChanged( + new SensorEvent(mLightSensor, 1, 1, new float[]{value})); + + assertThat(lux.get()).isEqualTo(value); + assertThat(lux2.get()).isEqualTo(value); + } + + @Test + public void testNoNextLuxWhenDestroyed() { + mProbe.destroy(); + + AtomicInteger lux = new AtomicInteger(-20); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + + assertThat(lux.get()).isEqualTo(-1); + verify(mSensorManager, never()).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + verifyNoMoreInteractions(mSensorManager); } private void moveTimeBy(long millis) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java index 60dc2eb6081d..88a9646cac8a 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java @@ -121,7 +121,7 @@ public class BiometricLoggerTest { verify(mSink).authenticate(eq(mOpContext), eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), anyLong(), anyInt(), eq(requireConfirmation), - eq(targetUserId), anyFloat()); + eq(targetUserId), any()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index dea4d4fb7c64..606f4864643c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.same; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -40,6 +41,7 @@ import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; @@ -72,6 +74,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.time.Clock; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -127,6 +130,8 @@ public class FingerprintAuthenticationClientTest { private ICancellationSignal mCancellationSignal; @Mock private Probe mLuxProbe; + @Mock + private Clock mClock; @Captor private ArgumentCaptor<OperationContext> mOperationContextCaptor; @Captor @@ -215,7 +220,7 @@ public class FingerprintAuthenticationClientTest { @Test public void luxProbeWhenAwake() throws RemoteException { - when(mBiometricContext.isAwake()).thenReturn(false, true, false); + when(mBiometricContext.isAwake()).thenReturn(false); when(mBiometricContext.isAod()).thenReturn(false); final FingerprintAuthenticationClient client = createClient(); client.start(mCallback); @@ -228,15 +233,38 @@ public class FingerprintAuthenticationClientTest { verify(mLuxProbe, never()).enable(); reset(mLuxProbe); + when(mBiometricContext.isAwake()).thenReturn(true); + mContextInjector.getValue().accept(opContext); verify(mLuxProbe).enable(); verify(mLuxProbe, never()).disable(); + when(mBiometricContext.isAwake()).thenReturn(false); + mContextInjector.getValue().accept(opContext); verify(mLuxProbe).disable(); } @Test + public void luxProbeEnabledOnStartWhenWake() throws RemoteException { + luxProbeEnabledOnStart(true /* isAwake */); + } + + @Test + public void luxProbeNotEnabledOnStartWhenNotWake() throws RemoteException { + luxProbeEnabledOnStart(false /* isAwake */); + } + + private void luxProbeEnabledOnStart(boolean isAwake) throws RemoteException { + when(mBiometricContext.isAwake()).thenReturn(isAwake); + when(mBiometricContext.isAod()).thenReturn(false); + final FingerprintAuthenticationClient client = createClient(); + client.start(mCallback); + + verify(mLuxProbe, isAwake ? times(1) : never()).enable(); + } + + @Test public void luxProbeDisabledOnAod() throws RemoteException { when(mBiometricContext.isAwake()).thenReturn(false); when(mBiometricContext.isAod()).thenReturn(true); @@ -423,6 +451,52 @@ public class FingerprintAuthenticationClientTest { } @Test + public void sideFingerprintSkipsWindowIfVendorMessageMatch() throws Exception { + when(mSensorProps.isAnySidefpsType()).thenReturn(true); + final int vendorAcquireMessage = 1234; + + mContext.getOrCreateTestableResources().addOverride( + R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, + FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR); + mContext.getOrCreateTestableResources().addOverride( + R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage, + vendorAcquireMessage); + + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + mLooper.dispatchAll(); + client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), + true /* authenticated */, new ArrayList<>()); + client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, vendorAcquireMessage); + mLooper.dispatchAll(); + + verify(mCallback).onClientFinished(any(), eq(true)); + } + + @Test + public void sideFingerprintDoesNotSkipWindowOnVendorErrorMismatch() throws Exception { + when(mSensorProps.isAnySidefpsType()).thenReturn(true); + final int vendorAcquireMessage = 1234; + + mContext.getOrCreateTestableResources().addOverride( + R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, + FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR); + mContext.getOrCreateTestableResources().addOverride( + R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage, + vendorAcquireMessage); + + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + mLooper.dispatchAll(); + client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), + true /* authenticated */, new ArrayList<>()); + client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 1); + mLooper.dispatchAll(); + + verify(mCallback, never()).onClientFinished(any(), anyBoolean()); + } + + @Test public void sideFingerprintSendsAuthIfFingerUp() throws Exception { when(mSensorProps.isAnySidefpsType()).thenReturn(true); @@ -469,6 +543,79 @@ public class FingerprintAuthenticationClientTest { verify(mCallback).onClientFinished(any(), eq(true)); } + @Test + public void sideFingerprintPowerWindowStartsOnAcquireStart() throws Exception { + final int powerWindow = 500; + final long authStart = 300; + + when(mSensorProps.isAnySidefpsType()).thenReturn(true); + mContext.getOrCreateTestableResources().addOverride( + R.integer.config_sidefpsBpPowerPressWindow, powerWindow); + + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + + // Acquire start occurs at time = 0ms + when(mClock.millis()).thenReturn(0L); + client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */); + + // Auth occurs at time = 300 + when(mClock.millis()).thenReturn(authStart); + // At this point the delay should be 500 - (300 - 0) == 200 milliseconds. + client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), + true /* authenticated */, new ArrayList<>()); + mLooper.dispatchAll(); + verify(mCallback, never()).onClientFinished(any(), anyBoolean()); + + // After waiting 200 milliseconds, auth should succeed. + mLooper.moveTimeForward(powerWindow - authStart); + mLooper.dispatchAll(); + verify(mCallback).onClientFinished(any(), eq(true)); + } + + @Test + public void sideFingerprintPowerWindowStartsOnLastAcquireStart() throws Exception { + final int powerWindow = 500; + + when(mSensorProps.isAnySidefpsType()).thenReturn(true); + mContext.getOrCreateTestableResources().addOverride( + R.integer.config_sidefpsBpPowerPressWindow, powerWindow); + + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + // Acquire start occurs at time = 0ms + when(mClock.millis()).thenReturn(0L); + client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */); + + // Auth reject occurs at time = 300ms + when(mClock.millis()).thenReturn(300L); + client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), + false /* authenticated */, new ArrayList<>()); + mLooper.dispatchAll(); + + mLooper.moveTimeForward(300); + mLooper.dispatchAll(); + verify(mCallback, never()).onClientFinished(any(), anyBoolean()); + + when(mClock.millis()).thenReturn(1300L); + client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */); + + // If code is correct, the new acquired start timestamp should be used + // and the code should only have to wait 500 - (1500-1300)ms. + when(mClock.millis()).thenReturn(1500L); + client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), + true /* authenticated */, new ArrayList<>()); + mLooper.dispatchAll(); + + mLooper.moveTimeForward(299); + mLooper.dispatchAll(); + verify(mCallback, never()).onClientFinished(any(), anyBoolean()); + + mLooper.moveTimeForward(1); + mLooper.dispatchAll(); + verify(mCallback).onClientFinished(any(), eq(true)); + } + private FingerprintAuthenticationClient createClient() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */); } @@ -496,7 +643,7 @@ public class FingerprintAuthenticationClientTest { null /* taskStackListener */, mLockoutCache, mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps, - new Handler(mLooper.getLooper())) { + new Handler(mLooper.getLooper()), mClock) { @Override protected ActivityTaskManager getActivityTaskManager() { return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index 356600d84099..06422281ab25 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -885,6 +886,29 @@ public class BrightnessTrackerTest { assertNull(mInjector.mLightSensor); } + @Test + public void testOnlyOneReceiverRegistered() { + assertNull(mInjector.mLightSensor); + assertNull(mInjector.mSensorListener); + startTracker(mTracker, 0.3f, false); + + assertNotNull(mInjector.mLightSensor); + assertNotNull(mInjector.mSensorListener); + Sensor registeredLightSensor = mInjector.mLightSensor; + SensorEventListener registeredSensorListener = mInjector.mSensorListener; + + mTracker.start(0.3f); + assertSame(registeredLightSensor, mInjector.mLightSensor); + assertSame(registeredSensorListener, mInjector.mSensorListener); + + mTracker.stop(); + assertNull(mInjector.mLightSensor); + assertNull(mInjector.mSensorListener); + + // mInjector asserts that we aren't removing a null receiver + mTracker.stop(); + } + private InputStream getInputStream(String data) { return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)); } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java index a719f526a1d8..30024fb5c221 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -49,6 +49,13 @@ import java.nio.file.Path; @Presubmit @RunWith(AndroidJUnit4.class) public final class DisplayDeviceConfigTest { + private static final int DEFAULT_PEAK_REFRESH_RATE = 75; + private static final int DEFAULT_REFRESH_RATE = 120; + private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30}; + private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21}; + private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160}; + private static final int[] HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{30000}; + private DisplayDeviceConfig mDisplayDeviceConfig; private static final float ZERO_DELTA = 0.0f; private static final float SMALL_DELTA = 0.0001f; @@ -204,6 +211,16 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(new float[]{29, 30, 31}, mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA); + assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE); + assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE); + assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), + LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE); + assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), + LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE); + assertArrayEquals(mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), + HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE); + assertArrayEquals(mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), + HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE); // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping, // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor. } @@ -465,6 +482,21 @@ public final class DisplayDeviceConfigTest { when(mResources.getIntArray(R.array.config_screenDarkeningThresholds)) .thenReturn(new int[]{370, 380, 390}); + // Configs related to refresh rates and blocking zones + when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate)) + .thenReturn(DEFAULT_PEAK_REFRESH_RATE); + when(mResources.getInteger(com.android.internal.R.integer.config_defaultRefreshRate)) + .thenReturn(DEFAULT_REFRESH_RATE); + when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate)) + .thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE); + when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate)) + .thenReturn(LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE); + when(mResources.getIntArray( + R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate)) + .thenReturn(HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE); + when(mResources.getIntArray( + R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate)) + .thenReturn(HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE); mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true); } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 968e1d8c546b..18dd264558ac 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -1853,6 +1853,20 @@ public class DisplayModeDirectorTest { assertNull(vote); } + @Test + public void testNotifyDefaultDisplayDeviceUpdated() { + DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class); + when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{}); + when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{}); + when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{}); + when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{}); + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0); + director.defaultDisplayDeviceUpdated(displayDeviceConfig); + verify(displayDeviceConfig).getDefaultRefreshRate(); + verify(displayDeviceConfig).getDefaultPeakRefreshRate(); + } + private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) { return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status); } diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java index 9fe8609c85a1..3b0a22f80c30 100644 --- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java @@ -275,6 +275,75 @@ public class PersistentDataStoreTest { assertNull(mDataStore.getBrightnessConfiguration(userSerial)); } + @Test + public void testStoreAndRestoreResolution() { + final String uniqueDisplayId = "test:123"; + DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) { + @Override + public boolean hasStableUniqueId() { + return true; + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + return null; + } + }; + int width = 35; + int height = 45; + mDataStore.loadIfNeeded(); + mDataStore.setUserPreferredResolution(testDisplayDevice, width, height); + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + mInjector.setWriteStream(baos); + mDataStore.saveIfNeeded(); + mTestLooper.dispatchAll(); + assertTrue(mInjector.wasWriteSuccessful()); + TestInjector newInjector = new TestInjector(); + PersistentDataStore newDataStore = new PersistentDataStore(newInjector); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + newInjector.setReadStream(bais); + newDataStore.loadIfNeeded(); + assertNotNull(newDataStore.getUserPreferredResolution(testDisplayDevice)); + assertEquals(35, newDataStore.getUserPreferredResolution(testDisplayDevice).x); + assertEquals(35, mDataStore.getUserPreferredResolution(testDisplayDevice).x); + assertEquals(45, newDataStore.getUserPreferredResolution(testDisplayDevice).y); + assertEquals(45, mDataStore.getUserPreferredResolution(testDisplayDevice).y); + } + + @Test + public void testStoreAndRestoreRefreshRate() { + final String uniqueDisplayId = "test:123"; + DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) { + @Override + public boolean hasStableUniqueId() { + return true; + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + return null; + } + }; + float refreshRate = 85.3f; + mDataStore.loadIfNeeded(); + mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate); + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + mInjector.setWriteStream(baos); + mDataStore.saveIfNeeded(); + mTestLooper.dispatchAll(); + assertTrue(mInjector.wasWriteSuccessful()); + TestInjector newInjector = new TestInjector(); + PersistentDataStore newDataStore = new PersistentDataStore(newInjector); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + newInjector.setReadStream(bais); + newDataStore.loadIfNeeded(); + assertNotNull(newDataStore.getUserPreferredRefreshRate(testDisplayDevice)); + assertEquals(85.3f, mDataStore.getUserPreferredRefreshRate(testDisplayDevice), 01.f); + assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f); + } + public class TestInjector extends PersistentDataStore.Injector { private InputStream mReadStream; private OutputStream mWriteStream; diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java index d8c9c3433313..e3ca1707ae0c 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java @@ -116,7 +116,7 @@ public class PowerGroupTest { @Test public void testDreamPowerGroup() { assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); - mPowerGroup.dreamLocked(TIMESTAMP1, UID); + mPowerGroup.dreamLocked(TIMESTAMP1, UID, /* allowWake= */ false); assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); assertThat(mPowerGroup.isSandmanSummonedLocked()).isTrue(); verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID), @@ -172,7 +172,7 @@ public class PowerGroupTest { eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(), /* details= */ isNull()); assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING); - assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID)).isFalse(); + assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID, /* allowWake= */ false)).isFalse(); assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING); verify(mWakefulnessCallbackMock, never()).onWakefulnessChangedLocked( eq(GROUP_ID), /* wakefulness= */ eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP2), @@ -181,6 +181,22 @@ public class PowerGroupTest { } @Test + public void testDreamPowerGroupWhenNotAwakeShouldWake() { + mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT); + verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID), + eq(WAKEFULNESS_DOZING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_TIMEOUT), + eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(), + /* details= */ isNull()); + assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING); + assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID, /* allowWake= */ true)).isTrue(); + assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked( + eq(GROUP_ID), /* wakefulness= */ eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP2), + /* reason= */ anyInt(), eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ any(), + /* details= */ any()); + } + + @Test public void testLastWakeAndSleepTimeIsUpdated() { assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP_CREATE); assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP_CREATE); @@ -514,7 +530,7 @@ public class PowerGroupTest { .setBatterySaverEnabled(batterySaverEnabled) .setBrightnessFactor(brightnessFactor) .build(); - mPowerGroup.dreamLocked(TIMESTAMP1, UID); + mPowerGroup.dreamLocked(TIMESTAMP1, UID, /* allowWake= */ false); assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT); mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS, diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 2a6e6d876599..f5ed41a7fa8b 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -393,6 +393,12 @@ public class PowerManagerServiceTest { .thenReturn(minimumScreenOffTimeoutConfigMillis); } + private void setDreamsDisabledByAmbientModeSuppressionConfig(boolean disable) { + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig)) + .thenReturn(disable); + } + private void advanceTime(long timeMs) { mClock.fastForward(timeMs); mTestLooper.dispatchAll(); @@ -612,6 +618,31 @@ public class PowerManagerServiceTest { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); } + /** + * Tests that dreaming continues when undocking and configured to do so. + */ + @Test + public void testWakefulnessDream_shouldKeepDreamingWhenUndocked() { + createService(); + startSystem(); + + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_keepDreamingWhenUndocking)) + .thenReturn(true); + mService.readConfigurationLocked(); + + when(mBatteryManagerInternalMock.getPlugType()) + .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK); + setPluggedIn(true); + + forceAwake(); // Needs to be awake first before it can dream. + forceDream(); + when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0); + setPluggedIn(false); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + } + @Test public void testWakefulnessDoze_goToSleep() { createService(); @@ -765,6 +796,91 @@ public class PowerManagerServiceTest { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); } + @SuppressWarnings("GuardedBy") + @Test + public void testAmbientSuppression_disablesDreamingAndWakesDevice() { + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED, 1); + + setDreamsDisabledByAmbientModeSuppressionConfig(true); + setMinimumScreenOffTimeoutConfig(10000); + createService(); + startSystem(); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + setPluggedIn(true); + // Allow asynchronous sandman calls to execute. + advanceTime(10000); + + forceDream(); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + advanceTime(50); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + } + + @SuppressWarnings("GuardedBy") + @Test + public void testAmbientSuppressionDisabled_shouldNotWakeDevice() { + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED, 1); + + setDreamsDisabledByAmbientModeSuppressionConfig(false); + setMinimumScreenOffTimeoutConfig(10000); + createService(); + startSystem(); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + setPluggedIn(true); + // Allow asynchronous sandman calls to execute. + advanceTime(10000); + + forceDream(); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + advanceTime(50); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + } + + @Test + public void testAmbientSuppression_doesNotAffectDreamForcing() { + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED, 1); + + setDreamsDisabledByAmbientModeSuppressionConfig(true); + setMinimumScreenOffTimeoutConfig(10000); + createService(); + startSystem(); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + setPluggedIn(true); + // Allow asynchronous sandman calls to execute. + advanceTime(10000); + + // Verify that forcing dream still works even though ambient display is suppressed + forceDream(); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + } + @Test public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() { final String suspendBlockerName = "PowerManagerService.Display"; diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java index 235849c1cd8b..c484f457faea 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -53,7 +53,8 @@ final class FakeVibratorControllerProvider { private boolean mIsAvailable = true; private boolean mIsInfoLoadSuccessful = true; - private long mLatency; + private long mOnLatency; + private long mOffLatency; private int mOffCount; private int mCapabilities; @@ -97,7 +98,7 @@ final class FakeVibratorControllerProvider { public long on(long milliseconds, long vibrationId) { recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, /* frequencyHz= */ 0, (int) milliseconds)); - applyLatency(); + applyLatency(mOnLatency); scheduleListener(milliseconds, vibrationId); return milliseconds; } @@ -105,12 +106,13 @@ final class FakeVibratorControllerProvider { @Override public void off() { mOffCount++; + applyLatency(mOffLatency); } @Override public void setAmplitude(float amplitude) { mAmplitudes.add(amplitude); - applyLatency(); + applyLatency(mOnLatency); } @Override @@ -121,7 +123,7 @@ final class FakeVibratorControllerProvider { } recordEffectSegment(vibrationId, new PrebakedSegment((int) effect, false, (int) strength)); - applyLatency(); + applyLatency(mOnLatency); scheduleListener(EFFECT_DURATION, vibrationId); return EFFECT_DURATION; } @@ -141,7 +143,7 @@ final class FakeVibratorControllerProvider { duration += EFFECT_DURATION + primitive.getDelay(); recordEffectSegment(vibrationId, primitive); } - applyLatency(); + applyLatency(mOnLatency); scheduleListener(duration, vibrationId); return duration; } @@ -154,7 +156,7 @@ final class FakeVibratorControllerProvider { recordEffectSegment(vibrationId, primitive); } recordBraking(vibrationId, braking); - applyLatency(); + applyLatency(mOnLatency); scheduleListener(duration, vibrationId); return duration; } @@ -193,10 +195,10 @@ final class FakeVibratorControllerProvider { return mIsInfoLoadSuccessful; } - private void applyLatency() { + private void applyLatency(long latencyMillis) { try { - if (mLatency > 0) { - Thread.sleep(mLatency); + if (latencyMillis > 0) { + Thread.sleep(latencyMillis); } } catch (InterruptedException e) { } @@ -240,10 +242,15 @@ final class FakeVibratorControllerProvider { /** * Sets the latency this controller should fake for turning the vibrator hardware on or setting - * it's vibration amplitude. + * the vibration amplitude. */ - public void setLatency(long millis) { - mLatency = millis; + public void setOnLatency(long millis) { + mOnLatency = millis; + } + + /** Sets the latency this controller should fake for turning the vibrator off. */ + public void setOffLatency(long millis) { + mOffLatency = millis; } /** Set the capabilities of the fake vibrator hardware. */ diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index 0551bfc70bda..42a2c10a24d6 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -1118,7 +1118,7 @@ public class VibrationThreadTest { // 25% of the first waveform step will be spent on the native on() call. // 25% of each waveform step will be spent on the native setAmplitude() call.. - mVibratorProviders.get(VIBRATOR_ID).setLatency(stepDuration / 4); + mVibratorProviders.get(VIBRATOR_ID).setOnLatency(stepDuration / 4); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); int stepCount = totalDuration / stepDuration; @@ -1149,7 +1149,7 @@ public class VibrationThreadTest { fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); long latency = 5_000; // 5s - fakeVibrator.setLatency(latency); + fakeVibrator.setOnLatency(latency); long vibrationId = 1; VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); @@ -1163,8 +1163,7 @@ public class VibrationThreadTest { // fail at waitForCompletion(cancellingThread). Thread cancellingThread = new Thread( () -> conductor.notifyCancelled( - new Vibration.EndInfo( - Vibration.Status.CANCELLED_BY_USER), + new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER), /* immediate= */ false)); cancellingThread.start(); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index fe0a79c2d944..039e159a347b 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -817,6 +817,39 @@ public class VibratorManagerServiceTest { verify(mBatteryStatsMock).noteVibratorOn(UID, 5000); // The second vibration shouldn't have recorded that the vibrators were turned on. verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong()); + // No segment played is the prebaked CLICK from the second vibration. + assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream() + .anyMatch(PrebakedSegment.class::isInstance)); + // Clean up repeating effect. + service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service); + } + + @Test + public void vibrate_withOngoingRepeatingVibrationBeingCancelled_playsAfterPreviousIsCancelled() + throws Exception { + mockVibrators(1); + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); + fakeVibrator.setOffLatency(50); // Add latency so cancellation is slow. + fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); + VibratorManagerService service = createSystemReadyService(); + + VibrationEffect repeatingEffect = VibrationEffect.createWaveform( + new long[]{10, 10_000}, new int[]{255, 0}, 1); + vibrate(service, repeatingEffect, ALARM_ATTRS); + + // VibrationThread will start this vibration async, wait until the off waveform step. + assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS)); + + // Cancel vibration right before requesting a new one. + // This should trigger slow IVibrator.off before setting the vibration status to cancelled. + service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service); + vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), + ALARM_ATTRS); + + // Check that second vibration was played. + assertTrue(fakeVibrator.getAllEffectSegments().stream() + .anyMatch(PrebakedSegment.class::isInstance)); } @Test @@ -867,6 +900,11 @@ public class VibratorManagerServiceTest { // The second vibration shouldn't have recorded that the vibrators were turned on. verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong()); + // The second vibration shouldn't have played any prebaked segment. + assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream() + .anyMatch(PrebakedSegment.class::isInstance)); + // Clean up long effect. + service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index 91c2fe0eb262..8e81e2d8997c 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -1371,6 +1371,39 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { verify(mInjector).startDreamWhenDockedIfAppropriate(mContext); } + @Test + public void dreamWhenDocked_ambientModeSuppressed_suppressionEnabled() { + mUiManagerService.setStartDreamImmediatelyOnDock(true); + mUiManagerService.setDreamsDisabledByAmbientModeSuppression(true); + + when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(true); + triggerDockIntent(); + verifyAndSendResultBroadcast(); + verify(mInjector, never()).startDreamWhenDockedIfAppropriate(mContext); + } + + @Test + public void dreamWhenDocked_ambientModeSuppressed_suppressionDisabled() { + mUiManagerService.setStartDreamImmediatelyOnDock(true); + mUiManagerService.setDreamsDisabledByAmbientModeSuppression(false); + + when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(true); + triggerDockIntent(); + verifyAndSendResultBroadcast(); + verify(mInjector).startDreamWhenDockedIfAppropriate(mContext); + } + + @Test + public void dreamWhenDocked_ambientModeNotSuppressed_suppressionEnabled() { + mUiManagerService.setStartDreamImmediatelyOnDock(true); + mUiManagerService.setDreamsDisabledByAmbientModeSuppression(true); + + when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(false); + triggerDockIntent(); + verifyAndSendResultBroadcast(); + verify(mInjector).startDreamWhenDockedIfAppropriate(mContext); + } + private void triggerDockIntent() { final Intent dockedIntent = new Intent(Intent.ACTION_DOCK_EVENT) diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index d5447447a7b2..462957a88a6c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2319,6 +2319,22 @@ public class ActivityRecordTests extends WindowTestsBase { assertTrue(activity1.getTask().getTaskInfo().launchCookies.contains(launchCookie)); } + @Test + public void testOrientationForScreenOrientationBehind() { + final Task task = createTask(mDisplayContent); + // Activity below + new ActivityBuilder(mAtm) + .setTask(task) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + final ActivityRecord activityTop = new ActivityBuilder(mAtm) + .setTask(task) + .setScreenOrientation(SCREEN_ORIENTATION_BEHIND) + .build(); + final int topOrientation = activityTop.getRequestedConfigurationOrientation(); + assertEquals(SCREEN_ORIENTATION_PORTRAIT, topOrientation); + } + private void verifyProcessInfoUpdate(ActivityRecord activity, State state, boolean shouldUpdate, boolean activityChange) { reset(activity.app); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index d5e336b1cf2f..eed32d7d815c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -40,14 +40,18 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; +import android.app.ActivityOptions; import android.app.WaitResult; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.os.Binder; import android.os.ConditionVariable; +import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.view.Display; @@ -308,4 +312,40 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { waitHandlerIdle(mAtm.mH); verify(mRootWindowContainer, timeout(TIMEOUT_MS)).startHomeOnEmptyDisplays("userUnlocked"); } + + /** Verifies that launch from recents sets the launch cookie on the activity. */ + @Test + public void testStartActivityFromRecents_withLaunchCookie() { + final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + + IBinder launchCookie = new Binder("test_launch_cookie"); + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchCookie(launchCookie); + SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options.toBundle()); + + doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(), + anyInt(), any()); + + mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions); + + assertThat(activity.mLaunchCookie).isEqualTo(launchCookie); + verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions)); + } + + /** Verifies that launch from recents doesn't set the launch cookie on the activity. */ + @Test + public void testStartActivityFromRecents_withoutLaunchCookie() { + final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + + SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle( + ActivityOptions.makeBasic().toBundle()); + + doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(), + anyInt(), any()); + + mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions); + + assertThat(activity.mLaunchCookie).isNull(); + verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions)); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java index e57ad5d9ff8c..24e932f36f80 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java @@ -57,10 +57,20 @@ public class SafeActivityOptionsTest { .setLaunchTaskDisplayArea(token) .setLaunchDisplayId(launchDisplayId) .setCallerDisplayId(callerDisplayId)) - .selectiveCloneDisplayOptions(); + .selectiveCloneLaunchOptions(); assertSame(clone.getOriginalOptions().getLaunchTaskDisplayArea(), token); assertEquals(clone.getOriginalOptions().getLaunchDisplayId(), launchDisplayId); assertEquals(clone.getOriginalOptions().getCallerDisplayId(), callerDisplayId); } + + @Test + public void test_selectiveCloneLunchRootTask() { + final WindowContainerToken token = mock(WindowContainerToken.class); + final SafeActivityOptions clone = new SafeActivityOptions(ActivityOptions.makeBasic() + .setLaunchRootTask(token)) + .selectiveCloneLaunchOptions(); + + assertSame(clone.getOriginalOptions().getLaunchRootTask(), token); + } } diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt index b2c5f4ac767b..8e62388c860f 100644 --- a/tools/xmlpersistence/src/main/kotlin/Generator.kt +++ b/tools/xmlpersistence/src/main/kotlin/Generator.kt @@ -149,6 +149,7 @@ private val ClassFieldInfo.allClassFields: List<ClassFieldInfo> when (field) { is ClassFieldInfo -> this += field.allClassFields is ListFieldInfo -> this += field.element.allClassFields + else -> {} } } } |