diff options
359 files changed, 6503 insertions, 3990 deletions
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/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/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/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/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index 6bf2474beb17..514df59f1989 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -175,10 +175,7 @@ public class UiTranslationController implements Dumpable { */ public void onActivityDestroyed() { synchronized (mLock) { - if (DEBUG) { - Log.i(TAG, - "onActivityDestroyed(): mCurrentState is " + stateToString(mCurrentState)); - } + Log.i(TAG, "onActivityDestroyed(): mCurrentState is " + stateToString(mCurrentState)); if (mCurrentState != STATE_UI_TRANSLATION_FINISHED) { notifyTranslationFinished(/* activityDestroyed= */ true); } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 1bc8e6d7ef65..8815ab35b671 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -135,8 +135,11 @@ public final class TransitionInfo implements Parcelable { /** This change happened underneath something else. */ public static final int FLAG_IS_OCCLUDED = 1 << 15; + /** The container is a system window, excluding wallpaper and input-method. */ + public static final int FLAG_IS_SYSTEM_WINDOW = 1 << 16; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 16; + public static final int FLAG_FIRST_CUSTOM = 1 << 17; /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { @@ -157,6 +160,7 @@ public final class TransitionInfo implements Parcelable { FLAG_CROSS_PROFILE_WORK_THUMBNAIL, FLAG_IS_BEHIND_STARTING_WINDOW, FLAG_IS_OCCLUDED, + FLAG_IS_SYSTEM_WINDOW, FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} @@ -369,6 +373,9 @@ public final class TransitionInfo implements Parcelable { if ((flags & FLAG_IS_OCCLUDED) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("IS_OCCLUDED"); } + if ((flags & FLAG_IS_SYSTEM_WINDOW) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_SYSTEM_WINDOW"); + } if ((flags & FLAG_FIRST_CUSTOM) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM"); } @@ -701,14 +708,37 @@ public final class TransitionInfo implements Parcelable { @Override public String toString() { - String out = "{" + mContainer + "(" + mParent + ") leash=" + mLeash - + " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb=" - + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r=" - + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation - + " endFixedRotation=" + mEndFixedRotation; - if (mSnapshot != null) out += " snapshot=" + mSnapshot; - if (mLastParent != null) out += " lastParent=" + mLastParent; - return out + "}"; + final StringBuilder sb = new StringBuilder(); + sb.append('{'); sb.append(mContainer); + sb.append(" m="); sb.append(modeToString(mMode)); + sb.append(" f="); sb.append(flagsToString(mFlags)); + if (mParent != null) { + sb.append(" p="); sb.append(mParent); + } + if (mLeash != null) { + sb.append(" leash="); sb.append(mLeash); + } + sb.append(" sb="); sb.append(mStartAbsBounds); + sb.append(" eb="); sb.append(mEndAbsBounds); + if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) { + sb.append(" eo="); sb.append(mEndRelOffset); + } + if (mStartRotation != mEndRotation) { + sb.append(" r="); sb.append(mStartRotation); + sb.append("->"); sb.append(mEndRotation); + sb.append(':'); sb.append(mRotationAnimation); + } + if (mEndFixedRotation != ROTATION_UNDEFINED) { + sb.append(" endFixedRotation="); sb.append(mEndFixedRotation); + } + if (mSnapshot != null) { + sb.append(" snapshot="); sb.append(mSnapshot); + } + if (mLastParent != null) { + sb.append(" lastParent="); sb.append(mLastParent); + } + sb.append('}'); + return sb.toString(); } } 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/res/Android.bp b/core/res/Android.bp index c42517d8a873..179eff809c96 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -130,6 +130,10 @@ android_app { // Allow overlay to add resource "--auto-add-overlay", + + // Framework resources benefit tremendously from enabling sparse encoding, saving tens + // of MBs in size and RAM use. + "--enable-sparse-encoding", ], resource_zips: [ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5ae133bbe6e6..964fe2d57b0d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1035,25 +1035,38 @@ android:priority="900" /> <!-- Allows an application to read from external storage. - <p>Any app that declares the {@link #WRITE_EXTERNAL_STORAGE} permission is implicitly - granted this permission.</p> + <p class="note"><strong>Note: </strong>Starting in API level 33, this permission has no + effect. If your app accesses other apps' media files, request one or more of these permissions + instead: <a href="#READ_MEDIA_IMAGES"><code>READ_MEDIA_IMAGES</code></a>, + <a href="#READ_MEDIA_VIDEO"><code>READ_MEDIA_VIDEO</code></a>, + <a href="#READ_MEDIA_AUDIO"><code>READ_MEDIA_AUDIO</code></a>. Learn more about the + <a href="{@docRoot}training/data-storage/shared/media#storage-permission">storage + permissions</a> that are associated with media files.</p> + <p>This permission is enforced starting in API level 19. Before API level 19, this permission is not enforced and all apps still have access to read from external storage. You can test your app with the permission enforced by enabling <em>Protect USB - storage</em> under Developer options in the Settings app on a device running Android 4.1 or - higher.</p> + storage</em> under <b>Developer options</b> in the Settings app on a device running Android + 4.1 or higher.</p> <p>Also starting in API level 19, this permission is <em>not</em> required to - read/write files in your application-specific directories returned by + read or write files in your application-specific directories returned by {@link android.content.Context#getExternalFilesDir} and - {@link android.content.Context#getExternalCacheDir}. - <p class="note"><strong>Note:</strong> If <em>both</em> your <a + {@link android.content.Context#getExternalCacheDir}.</p> + <p>Starting in API level 29, apps don't need to request this permission to access files in + their app-specific directory on external storage, or their own files in the + <a href="{@docRoot}reference/android/provider/MediaStore"><code>MediaStore</code></a>. Apps + shouldn't request this permission unless they need to access other apps' files in the + <code>MediaStore</code>. Read more about these changes in the + <a href="{@docRoot}training/data-storage#scoped-storage">scoped storage</a> section of the + developer documentation.</p> + <p>If <em>both</em> your <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a> and <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code targetSdkVersion}</a> values are set to 3 or lower, the system implicitly grants your app this permission. If you don't need this permission, be sure your <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code - targetSdkVersion}</a> is 4 or higher. + targetSdkVersion}</a> is 4 or higher.</p> <p> This is a soft restricted permission which cannot be held by an app it its full form until the installer on record allowlists the permission. diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml index ded23feaca8f..38a71f0e17f6 100644 --- a/core/res/res/layout/miniresolver.xml +++ b/core/res/res/layout/miniresolver.xml @@ -65,8 +65,7 @@ android:paddingTop="32dp" android:paddingBottom="@dimen/resolver_button_bar_spacing" android:orientation="vertical" - android:background="?attr/colorBackground" - android:layout_ignoreOffset="true"> + android:background="?attr/colorBackground"> <RelativeLayout style="?attr/buttonBarStyle" android:layout_width="match_parent" 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..a9799f243cb7 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 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/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/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/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/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/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index d1bc7384d78c..db1f19aa87b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -322,6 +322,11 @@ public class Transitions implements RemoteCallable<Transitions> { boolean isOpening = isOpeningType(info.getType()); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); + if ((change.getFlags() & TransitionInfo.FLAG_IS_SYSTEM_WINDOW) != 0) { + // Currently system windows are controlled by WindowState, so don't change their + // surfaces. Otherwise their window tokens could be hidden unexpectedly. + continue; + } final SurfaceControl leash = change.getLeash(); final int mode = info.getChanges().get(i).getMode(); 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/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/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/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt new file mode 100644 index 000000000000..1d808ba7ee16 --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt @@ -0,0 +1,95 @@ +/* + * 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.SdkConstants.CLASS_CONTEXT +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 com.intellij.psi.PsiModifierListOwner +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UClass +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.getParentOfType + +/** + * Warns if {@code Context.bindService}, {@code Context.bindServiceAsUser}, or {@code + * Context.unbindService} is not called on a {@code WorkerThread} + */ +@Suppress("UnstableApiUsage") +class BindServiceOnMainThreadDetector : Detector(), SourceCodeScanner { + + override fun getApplicableMethodNames(): List<String> { + return listOf("bindService", "bindServiceAsUser", "unbindService") + } + + private fun hasWorkerThreadAnnotation( + context: JavaContext, + annotated: PsiModifierListOwner? + ): Boolean { + return context.evaluator.getAnnotations(annotated, inHierarchy = true).any { uAnnotation -> + uAnnotation.qualifiedName == "androidx.annotation.WorkerThread" + } + } + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { + if ( + !hasWorkerThreadAnnotation(context, node.getParentOfType(UMethod::class.java)) && + !hasWorkerThreadAnnotation(context, node.getParentOfType(UClass::class.java)) + ) { + context.report( + ISSUE, + method, + context.getLocation(node), + "This method should be annotated with `@WorkerThread` because " + + "it calls ${method.name}", + ) + } + } + } + + companion object { + @JvmField + val ISSUE: Issue = + Issue.create( + id = "BindServiceOnMainThread", + briefDescription = "Service bound or unbound on main thread", + explanation = + """ + Binding and unbinding services are synchronous calls to `ActivityManager`. \ + They usually take multiple milliseconds to complete. If called on the main \ + thread, it will likely cause missed frames. To fix it, use a `@Background \ + Executor` and annotate the calling method with `@WorkerThread`. + """, + category = Category.PERFORMANCE, + priority = 8, + severity = Severity.WARNING, + implementation = + Implementation( + BindServiceOnMainThreadDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + } +} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt index 8d48f0957be4..112992913661 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt @@ -16,6 +16,7 @@ package com.android.internal.systemui.lint +import com.android.SdkConstants.CLASS_CONTEXT import com.android.tools.lint.detector.api.Category import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Implementation @@ -48,14 +49,14 @@ class BroadcastSentViaContextDetector : Detector(), SourceCodeScanner { return } - val evaulator = context.evaluator - if (evaulator.isMemberInSubClassOf(method, "android.content.Context")) { + val evaluator = context.evaluator + if (evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { context.report( ISSUE, method, context.getNameLocation(node), - "Please don't call sendBroadcast/sendBroadcastAsUser directly on " + - "Context, use com.android.systemui.broadcast.BroadcastSender instead." + "`Context.${method.name}()` should be replaced with " + + "`BroadcastSender.${method.name}()`" ) } } @@ -65,14 +66,14 @@ class BroadcastSentViaContextDetector : Detector(), SourceCodeScanner { val ISSUE: Issue = Issue.create( id = "BroadcastSentViaContext", - briefDescription = "Broadcast sent via Context instead of BroadcastSender.", - explanation = - "Broadcast was sent via " + - "Context.sendBroadcast/Context.sendBroadcastAsUser. Please use " + - "BroadcastSender.sendBroadcast/BroadcastSender.sendBroadcastAsUser " + - "which will schedule dispatch of broadcasts on background thread. " + - "Sending broadcasts on main thread causes jank due to synchronous " + - "Binder calls.", + briefDescription = "Broadcast sent via `Context` instead of `BroadcastSender`", + // lint trims indents and converts \ to line continuations + explanation = """ + Broadcasts sent via `Context.sendBroadcast()` or \ + `Context.sendBroadcastAsUser()` will block the main thread and may cause \ + missed frames. Instead, use `BroadcastSender.sendBroadcast()` or \ + `BroadcastSender.sendBroadcastAsUser()` which will schedule and dispatch \ + broadcasts on a background worker thread.""", category = Category.PERFORMANCE, priority = 8, severity = Severity.WARNING, diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt deleted file mode 100644 index a629eeeb0102..000000000000 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt +++ /dev/null @@ -1,66 +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.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 - -@Suppress("UnstableApiUsage") -class GetMainLooperViaContextDetector : Detector(), SourceCodeScanner { - - override fun getApplicableMethodNames(): List<String> { - return listOf("getMainThreadHandler", "getMainLooper", "getMainExecutor") - } - - override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { - if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) { - context.report( - ISSUE, - method, - context.getNameLocation(node), - "Please inject a @Main Executor instead." - ) - } - } - - companion object { - @JvmField - val ISSUE: Issue = - Issue.create( - id = "GetMainLooperViaContextDetector", - briefDescription = "Please use idiomatic SystemUI executors, injecting " + - "them via Dagger.", - explanation = "Injecting the @Main Executor is preferred in order to make" + - "dependencies explicit and increase testability. It's much " + - "easier to pass a FakeExecutor on your test ctor than to " + - "deal with loopers in unit tests.", - category = Category.LINT, - priority = 8, - severity = Severity.WARNING, - implementation = Implementation(GetMainLooperViaContextDetector::class.java, - Scope.JAVA_FILE_SCOPE) - ) - } -} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt index 925fae0ebfb4..bab76ab4bce2 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt @@ -16,6 +16,7 @@ package com.android.internal.systemui.lint +import com.android.SdkConstants.CLASS_CONTEXT import com.android.tools.lint.detector.api.Category import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Implementation @@ -28,20 +29,19 @@ import com.intellij.psi.PsiMethod import org.jetbrains.uast.UCallExpression @Suppress("UnstableApiUsage") -class BindServiceViaContextDetector : Detector(), SourceCodeScanner { +class NonInjectedMainThreadDetector : Detector(), SourceCodeScanner { override fun getApplicableMethodNames(): List<String> { - return listOf("bindService", "bindServiceAsUser", "unbindService") + return listOf("getMainThreadHandler", "getMainLooper", "getMainExecutor") } override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { - if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) { + if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { context.report( - ISSUE, - method, - context.getNameLocation(node), - "Binding or unbinding services are synchronous calls, please make " + - "sure you're on a @Background Executor." + ISSUE, + method, + context.getNameLocation(node), + "Replace with injected `@Main Executor`." ) } } @@ -50,18 +50,20 @@ class BindServiceViaContextDetector : Detector(), SourceCodeScanner { @JvmField val ISSUE: Issue = Issue.create( - id = "BindServiceViaContextDetector", - briefDescription = "Service bound/unbound via Context, please make sure " + - "you're on a background thread.", + id = "NonInjectedMainThread", + briefDescription = "Main thread usage without dependency injection", explanation = - "Binding or unbinding services are synchronous calls to ActivityManager, " + - "they usually take multiple milliseconds to complete and will make" + - "the caller drop frames. Make sure you're on a @Background Executor.", - category = Category.PERFORMANCE, + """ + Main thread should be injected using the `@Main Executor` instead \ + of using the accessors in `Context`. This is to make the \ + dependencies explicit and increase testability. It's much easier \ + to pass a `FakeExecutor` on test constructors than it is to deal \ + with loopers in unit tests.""", + category = Category.LINT, priority = 8, severity = Severity.WARNING, implementation = - Implementation(BindServiceViaContextDetector::class.java, Scope.JAVA_FILE_SCOPE) + Implementation(NonInjectedMainThreadDetector::class.java, Scope.JAVA_FILE_SCOPE) ) } } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt index 4eb7c7dd0d7e..b62290025437 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt @@ -16,6 +16,7 @@ package com.android.internal.systemui.lint +import com.android.SdkConstants.CLASS_CONTEXT import com.android.tools.lint.detector.api.Category import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Implementation @@ -32,7 +33,7 @@ import org.jetbrains.uast.UCallExpression class NonInjectedServiceDetector : Detector(), SourceCodeScanner { override fun getApplicableMethodNames(): List<String> { - return listOf("getSystemService") + return listOf("getSystemService", "get") } override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { @@ -40,14 +41,25 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner { if ( !evaluator.isStatic(method) && method.name == "getSystemService" && - method.containingClass?.qualifiedName == "android.content.Context" + method.containingClass?.qualifiedName == CLASS_CONTEXT ) { context.report( ISSUE, method, context.getNameLocation(node), - "Use @Inject to get the handle to a system-level services instead of using " + - "Context.getSystemService()" + "Use `@Inject` to get system-level service handles instead of " + + "`Context.getSystemService()`" + ) + } else if ( + evaluator.isStatic(method) && + method.name == "get" && + method.containingClass?.qualifiedName == "android.accounts.AccountManager" + ) { + context.report( + ISSUE, + method, + context.getNameLocation(node), + "Replace `AccountManager.get()` with an injected instance of `AccountManager`" ) } } @@ -57,14 +69,14 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner { val ISSUE: Issue = Issue.create( id = "NonInjectedService", - briefDescription = - "System-level services should be retrieved using " + - "@Inject instead of Context.getSystemService().", + briefDescription = "System service not injected", explanation = - "Context.getSystemService() should be avoided because it makes testing " + - "difficult. Instead, use an injected service. For example, " + - "instead of calling Context.getSystemService(UserManager.class), " + - "use @Inject and add UserManager to the constructor", + """ + `Context.getSystemService()` should be avoided because it makes testing \ + difficult. Instead, use an injected service. For example, instead of calling \ + `Context.getSystemService(UserManager.class)` in a class, annotate the class' \ + constructor with `@Inject` and add `UserManager` to the parameters. + """, category = Category.CORRECTNESS, priority = 8, severity = Severity.WARNING, diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt index eb71d32b2d8b..4ba3afc7f7e2 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt @@ -16,6 +16,7 @@ package com.android.internal.systemui.lint +import com.android.SdkConstants.CLASS_CONTEXT import com.android.tools.lint.detector.api.Category import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Implementation @@ -35,12 +36,12 @@ class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner { } override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { - if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) { + if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { context.report( ISSUE, method, context.getNameLocation(node), - "BroadcastReceivers should be registered via BroadcastDispatcher." + "Register `BroadcastReceiver` using `BroadcastDispatcher` instead of `Context`" ) } } @@ -49,14 +50,16 @@ class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner { @JvmField val ISSUE: Issue = Issue.create( - id = "RegisterReceiverViaContextDetector", - briefDescription = "Broadcast registrations via Context are blocking " + - "calls. Please use BroadcastDispatcher.", - explanation = - "Context#registerReceiver is a blocking call to the system server, " + - "making it very likely that you'll drop a frame. Please use " + - "BroadcastDispatcher instead (or move this call to a " + - "@Background Executor.)", + id = "RegisterReceiverViaContext", + briefDescription = "Blocking broadcast registration", + // lint trims indents and converts \ to line continuations + explanation = """ + `Context.registerReceiver()` is a blocking call to the system server, \ + making it very likely that you'll drop a frame. Please use \ + `BroadcastDispatcher` instead, which registers the receiver on a \ + background thread. `BroadcastDispatcher` also improves our visibility \ + into ANRs.""", + moreInfo = "go/identifying-broadcast-threads", category = Category.PERFORMANCE, priority = 8, severity = Severity.WARNING, diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt index b00661575c14..7be21a512f89 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt @@ -49,8 +49,7 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner { ISSUE_SLOW_USER_ID_QUERY, method, context.getNameLocation(node), - "ActivityManager.getCurrentUser() is slow. " + - "Use UserTracker.getUserId() instead." + "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`" ) } if ( @@ -62,7 +61,7 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner { ISSUE_SLOW_USER_INFO_QUERY, method, context.getNameLocation(node), - "UserManager.getUserInfo() is slow. " + "Use UserTracker.getUserInfo() instead." + "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`" ) } } @@ -72,11 +71,13 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner { val ISSUE_SLOW_USER_ID_QUERY: Issue = Issue.create( id = "SlowUserIdQuery", - briefDescription = "User ID queried using ActivityManager instead of UserTracker.", + briefDescription = "User ID queried using ActivityManager", explanation = - "ActivityManager.getCurrentUser() makes a binder call and is slow. " + - "Instead, inject a UserTracker and call UserTracker.getUserId(). For " + - "more info, see: http://go/multi-user-in-systemui-slides", + """ + `ActivityManager.getCurrentUser()` uses a blocking binder call and is slow. \ + Instead, inject a `UserTracker` and call `UserTracker.getUserId()`. + """, + moreInfo = "http://go/multi-user-in-systemui-slides", category = Category.PERFORMANCE, priority = 8, severity = Severity.WARNING, @@ -88,11 +89,13 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner { val ISSUE_SLOW_USER_INFO_QUERY: Issue = Issue.create( id = "SlowUserInfoQuery", - briefDescription = "User info queried using UserManager instead of UserTracker.", + briefDescription = "User info queried using UserManager", explanation = - "UserManager.getUserInfo() makes a binder call and is slow. " + - "Instead, inject a UserTracker and call UserTracker.getUserInfo(). For " + - "more info, see: http://go/multi-user-in-systemui-slides", + """ + `UserManager.getUserInfo()` uses a blocking binder call and is slow. \ + Instead, inject a `UserTracker` and call `UserTracker.getUserInfo()`. + """, + moreInfo = "http://go/multi-user-in-systemui-slides", category = Category.PERFORMANCE, priority = 8, severity = Severity.WARNING, diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt index a584894fed71..4eeeb850292a 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt @@ -47,7 +47,7 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner { ISSUE, referenced, context.getNameLocation(referenced), - "Usage of Config.HARDWARE is highly encouraged." + "Replace software bitmap with `Config.HARDWARE`" ) } } @@ -56,12 +56,12 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner { @JvmField val ISSUE: Issue = Issue.create( - id = "SoftwareBitmapDetector", - briefDescription = "Software bitmap detected. Please use Config.HARDWARE instead.", - explanation = - "Software bitmaps occupy twice as much memory, when compared to Config.HARDWARE. " + - "In case you need to manipulate the pixels, please consider to either use" + - "a shader (encouraged), or a short lived software bitmap.", + id = "SoftwareBitmap", + briefDescription = "Software bitmap", + explanation = """ + Software bitmaps occupy twice as much memory as `Config.HARDWARE` bitmaps \ + do. However, hardware bitmaps are read-only. If you need to manipulate the \ + pixels, use a shader (preferably) or a short lived software bitmap.""", category = Category.PERFORMANCE, priority = 8, severity = Severity.WARNING, 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 312810ba4633..cf7c1b5e44a2 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 @@ -28,11 +28,11 @@ class SystemUIIssueRegistry : IssueRegistry() { override val issues: List<Issue> get() = listOf( - BindServiceViaContextDetector.ISSUE, + BindServiceOnMainThreadDetector.ISSUE, BroadcastSentViaContextDetector.ISSUE, SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY, SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY, - GetMainLooperViaContextDetector.ISSUE, + NonInjectedMainThreadDetector.ISSUE, RegisterReceiverViaContextDetector.ISSUE, SoftwareBitmapDetector.ISSUE, NonInjectedServiceDetector.ISSUE, 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 26bd8d0a6ff4..486af9dd5d98 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 @@ -16,16 +16,21 @@ package com.android.internal.systemui.lint +import com.android.annotations.NonNull import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java +import org.intellij.lang.annotations.Language + +@Suppress("UnstableApiUsage") +@NonNull +private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented() /* * 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. */ -@Suppress("UnstableApiUsage") internal val androidStubs = arrayOf( - java( + indentedJava( """ package android.app; @@ -34,7 +39,16 @@ public class ActivityManager { } """ ), - java( + indentedJava( + """ +package android.accounts; + +public class AccountManager { + public static AccountManager get(Context context) { return null; } +} +""" + ), + indentedJava( """ package android.os; import android.content.pm.UserInfo; @@ -45,39 +59,39 @@ public class UserManager { } """ ), - java(""" + indentedJava(""" package android.annotation; public @interface UserIdInt {} """), - java(""" + indentedJava(""" package android.content.pm; public class UserInfo {} """), - java(""" + indentedJava(""" package android.os; public class Looper {} """), - java(""" + indentedJava(""" package android.os; public class Handler {} """), - java(""" + indentedJava(""" package android.content; public class ServiceConnection {} """), - java(""" + indentedJava(""" package android.os; public enum UserHandle { ALL } """), - java( + indentedJava( """ package android.content; import android.os.UserHandle; @@ -108,7 +122,7 @@ public class Context { } """ ), - java( + indentedJava( """ package android.app; import android.content.Context; @@ -116,7 +130,7 @@ import android.content.Context; public class Activity extends Context {} """ ), - java( + indentedJava( """ package android.graphics; @@ -132,17 +146,17 @@ public class Bitmap { } """ ), - java(""" + indentedJava(""" package android.content; public class BroadcastReceiver {} """), - java(""" + indentedJava(""" package android.content; public class IntentFilter {} """), - java( + indentedJava( """ package com.android.systemui.settings; import android.content.pm.UserInfo; @@ -153,4 +167,23 @@ public interface UserTracker { } """ ), + indentedJava( + """ +package androidx.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER}) +public @interface WorkerThread { +} +""" + ), ) 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 new file mode 100644 index 000000000000..6ae8fd3f25a1 --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt @@ -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 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() { + + override fun getDetector(): Detector = BindServiceOnMainThreadDetector() + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE) + + @Test + fun testBindService() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class TestClass { + public void bind(Context context) { + Intent intent = new Intent(Intent.ACTION_VIEW); + context.bindService(intent, null, 0); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread] + context.bindService(intent, null, 0); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testBindServiceAsUser() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.os.UserHandle; + + public class TestClass { + public void bind(Context context) { + Intent intent = new Intent(Intent.ACTION_VIEW); + context.bindServiceAsUser(intent, null, 0, UserHandle.ALL); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/TestClass.java:8: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread] + context.bindServiceAsUser(intent, null, 0, UserHandle.ALL); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testUnbindService() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.content.ServiceConnection; + + public class TestClass { + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls unbindService [BindServiceOnMainThread] + context.unbindService(connection); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testWorkerMethod() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.content.ServiceConnection; + import androidx.annotation.WorkerThread; + + public class TestClass { + @WorkerThread + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + } + + public class ChildTestClass extends TestClass { + @Override + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expectClean() + } + + @Test + fun testWorkerClass() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.content.ServiceConnection; + import androidx.annotation.WorkerThread; + + @WorkerThread + public class TestClass { + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + } + + public class ChildTestClass extends TestClass { + @Override + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + + public void bind(Context context, ServiceConnection connection) { + context.bind(connection); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expectClean() + } + + private val stubs = androidStubs +} diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt deleted file mode 100644 index 564afcb773fd..000000000000 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt +++ /dev/null @@ -1,116 +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.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 BindServiceViaContextDetectorTest : LintDetectorTest() { - - override fun getDetector(): Detector = BindServiceViaContextDetector() - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) - - override fun getIssues(): List<Issue> = listOf(BindServiceViaContextDetector.ISSUE) - - private val explanation = "Binding or unbinding services are synchronous calls" - - @Test - fun testBindService() { - lint() - .files( - TestFiles.java( - """ - package test.pkg; - import android.content.Context; - - public class TestClass1 { - public void bind(Context context) { - Intent intent = new Intent(Intent.ACTION_VIEW); - context.bindService(intent, null, 0); - } - } - """ - ) - .indented(), - *stubs - ) - .issues(BindServiceViaContextDetector.ISSUE) - .run() - .expectWarningCount(1) - .expectContains(explanation) - } - - @Test - fun testBindServiceAsUser() { - lint() - .files( - TestFiles.java( - """ - package test.pkg; - import android.content.Context; - import android.os.UserHandle; - - public class TestClass1 { - public void bind(Context context) { - Intent intent = new Intent(Intent.ACTION_VIEW); - context.bindServiceAsUser(intent, null, 0, UserHandle.ALL); - } - } - """ - ) - .indented(), - *stubs - ) - .issues(BindServiceViaContextDetector.ISSUE) - .run() - .expectWarningCount(1) - .expectContains(explanation) - } - - @Test - fun testUnbindService() { - lint() - .files( - TestFiles.java( - """ - package test.pkg; - import android.content.Context; - import android.content.ServiceConnection; - - public class TestClass1 { - public void unbind(Context context, ServiceConnection connection) { - context.unbindService(connection); - } - } - """ - ) - .indented(), - *stubs - ) - .issues(BindServiceViaContextDetector.ISSUE) - .run() - .expectWarningCount(1) - .expectContains(explanation) - } - - private val stubs = androidStubs -} 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 06aee8e35898..7d422807ae08 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 @@ -41,7 +41,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { package test.pkg; import android.content.Context; - public class TestClass1 { + public class TestClass { public void send(Context context) { Intent intent = new Intent(Intent.ACTION_VIEW); context.sendBroadcast(intent); @@ -54,10 +54,13 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { ) .issues(BroadcastSentViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains( - "Please don't call sendBroadcast/sendBroadcastAsUser directly on " + - "Context, use com.android.systemui.broadcast.BroadcastSender instead." + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Context.sendBroadcast() should be replaced with BroadcastSender.sendBroadcast() [BroadcastSentViaContext] + context.sendBroadcast(intent); + ~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ ) } @@ -71,7 +74,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { import android.content.Context; import android.os.UserHandle; - public class TestClass1 { + public class TestClass { public void send(Context context) { Intent intent = new Intent(Intent.ACTION_VIEW); context.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); @@ -84,10 +87,13 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { ) .issues(BroadcastSentViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains( - "Please don't call sendBroadcast/sendBroadcastAsUser directly on " + - "Context, use com.android.systemui.broadcast.BroadcastSender instead." + .expect( + """ + src/test/pkg/TestClass.java:8: Warning: Context.sendBroadcastAsUser() should be replaced with BroadcastSender.sendBroadcastAsUser() [BroadcastSentViaContext] + context.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); + ~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ ) } @@ -101,7 +107,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { import android.app.Activity; import android.os.UserHandle; - public class TestClass1 { + public class TestClass { public void send(Activity activity) { Intent intent = new Intent(Intent.ACTION_VIEW); activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); @@ -115,11 +121,41 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { ) .issues(BroadcastSentViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains( - "Please don't call sendBroadcast/sendBroadcastAsUser directly on " + - "Context, use com.android.systemui.broadcast.BroadcastSender instead." + .expect( + """ + src/test/pkg/TestClass.java:8: Warning: Context.sendBroadcastAsUser() should be replaced with BroadcastSender.sendBroadcastAsUser() [BroadcastSentViaContext] + activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); + ~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testSendBroadcastInBroadcastSender() { + lint() + .files( + TestFiles.java( + """ + package com.android.systemui.broadcast; + import android.app.Activity; + import android.os.UserHandle; + + public class BroadcastSender { + public void send(Activity activity) { + Intent intent = new Intent(Intent.ACTION_VIEW); + activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); + } + + } + """ + ) + .indented(), + *stubs ) + .issues(BroadcastSentViaContextDetector.ISSUE) + .run() + .expectClean() } @Test @@ -131,7 +167,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() { package test.pkg; import android.content.Context; - public class TestClass1 { + public class TestClass { public void sendBroadcast() { Intent intent = new Intent(Intent.ACTION_VIEW); context.startActivity(intent); diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt index c55f3995f102..c468af8d09e0 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt @@ -24,14 +24,12 @@ import com.android.tools.lint.detector.api.Issue import org.junit.Test @Suppress("UnstableApiUsage") -class GetMainLooperViaContextDetectorTest : LintDetectorTest() { +class NonInjectedMainThreadDetectorTest : LintDetectorTest() { - override fun getDetector(): Detector = GetMainLooperViaContextDetector() + override fun getDetector(): Detector = NonInjectedMainThreadDetector() override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) - override fun getIssues(): List<Issue> = listOf(GetMainLooperViaContextDetector.ISSUE) - - private val explanation = "Please inject a @Main Executor instead." + override fun getIssues(): List<Issue> = listOf(NonInjectedMainThreadDetector.ISSUE) @Test fun testGetMainThreadHandler() { @@ -43,7 +41,7 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { import android.content.Context; import android.os.Handler; - public class TestClass1 { + public class TestClass { public void test(Context context) { Handler mainThreadHandler = context.getMainThreadHandler(); } @@ -53,10 +51,16 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { .indented(), *stubs ) - .issues(GetMainLooperViaContextDetector.ISSUE) + .issues(NonInjectedMainThreadDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Replace with injected @Main Executor. [NonInjectedMainThread] + Handler mainThreadHandler = context.getMainThreadHandler(); + ~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } @Test @@ -69,7 +73,7 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { import android.content.Context; import android.os.Looper; - public class TestClass1 { + public class TestClass { public void test(Context context) { Looper mainLooper = context.getMainLooper(); } @@ -79,10 +83,16 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { .indented(), *stubs ) - .issues(GetMainLooperViaContextDetector.ISSUE) + .issues(NonInjectedMainThreadDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Replace with injected @Main Executor. [NonInjectedMainThread] + Looper mainLooper = context.getMainLooper(); + ~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } @Test @@ -95,7 +105,7 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { import android.content.Context; import java.util.concurrent.Executor; - public class TestClass1 { + public class TestClass { public void test(Context context) { Executor mainExecutor = context.getMainExecutor(); } @@ -105,10 +115,16 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() { .indented(), *stubs ) - .issues(GetMainLooperViaContextDetector.ISSUE) + .issues(NonInjectedMainThreadDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Replace with injected @Main Executor. [NonInjectedMainThread] + Executor mainExecutor = context.getMainExecutor(); + ~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } private val stubs = androidStubs 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 6b9f88fedbdd..c83a35b46ca6 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 @@ -39,7 +39,7 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() { package test.pkg; import android.content.Context; - public class TestClass1 { + public class TestClass { public void getSystemServiceWithoutDagger(Context context) { context.getSystemService("user"); } @@ -51,8 +51,14 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() { ) .issues(NonInjectedServiceDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains("Use @Inject to get the handle") + .expect( + """ + src/test/pkg/TestClass.java:6: Warning: Use @Inject to get system-level service handles instead of Context.getSystemService() [NonInjectedService] + context.getSystemService("user"); + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } @Test @@ -65,7 +71,7 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() { import android.content.Context; import android.os.UserManager; - public class TestClass2 { + public class TestClass { public void getSystemServiceWithoutDagger(Context context) { context.getSystemService(UserManager.class); } @@ -77,8 +83,46 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() { ) .issues(NonInjectedServiceDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains("Use @Inject to get the handle") + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Use @Inject to get system-level service handles instead of Context.getSystemService() [NonInjectedService] + context.getSystemService(UserManager.class); + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testGetAccountManager() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.accounts.AccountManager; + + public class TestClass { + public void getSystemServiceWithoutDagger(Context context) { + AccountManager.get(context); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(NonInjectedServiceDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Replace AccountManager.get() with an injected instance of AccountManager [NonInjectedService] + AccountManager.get(context); + ~~~ + 0 errors, 1 warnings + """ + ) } private val stubs = androidStubs 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 802ceba4196c..ebcddebfbc28 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 @@ -31,8 +31,6 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE) - private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher." - @Test fun testRegisterReceiver() { lint() @@ -44,7 +42,7 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { import android.content.Context; import android.content.IntentFilter; - public class TestClass1 { + public class TestClass { public void bind(Context context, BroadcastReceiver receiver, IntentFilter filter) { context.registerReceiver(receiver, filter, 0); @@ -57,8 +55,14 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { ) .issues(RegisterReceiverViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:9: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext] + context.registerReceiver(receiver, filter, 0); + ~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } @Test @@ -74,7 +78,7 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { import android.os.Handler; import android.os.UserHandle; - public class TestClass1 { + public class TestClass { public void bind(Context context, BroadcastReceiver receiver, IntentFilter filter, Handler handler) { context.registerReceiverAsUser(receiver, UserHandle.ALL, filter, @@ -88,8 +92,14 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { ) .issues(RegisterReceiverViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext] + context.registerReceiverAsUser(receiver, UserHandle.ALL, filter, + ~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } @Test @@ -105,7 +115,7 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { import android.os.Handler; import android.os.UserHandle; - public class TestClass1 { + public class TestClass { public void bind(Context context, BroadcastReceiver receiver, IntentFilter filter, Handler handler) { context.registerReceiverForAllUsers(receiver, filter, "permission", @@ -119,8 +129,14 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() { ) .issues(RegisterReceiverViaContextDetector.ISSUE) .run() - .expectWarningCount(1) - .expectContains(explanation) + .expect( + """ + src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext] + context.registerReceiverForAllUsers(receiver, filter, "permission", + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) } private val stubs = androidStubs 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 e26583793e20..b03a11c4f02f 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 @@ -44,7 +44,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { package test.pkg; import android.app.ActivityManager; - public class TestClass1 { + public class TestClass { public void slewlyGetCurrentUser() { ActivityManager.getCurrentUser(); } @@ -59,10 +59,13 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY ) .run() - .expectWarningCount(1) - .expectContains( - "ActivityManager.getCurrentUser() is slow. " + - "Use UserTracker.getUserId() instead." + .expect( + """ + src/test/pkg/TestClass.java:6: Warning: Use UserTracker.getUserId() instead of ActivityManager.getCurrentUser() [SlowUserIdQuery] + ActivityManager.getCurrentUser(); + ~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ ) } @@ -75,7 +78,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { package test.pkg; import android.os.UserManager; - public class TestClass2 { + public class TestClass { public void slewlyGetUserInfo(UserManager userManager) { userManager.getUserInfo(); } @@ -90,9 +93,13 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY ) .run() - .expectWarningCount(1) - .expectContains( - "UserManager.getUserInfo() is slow. " + "Use UserTracker.getUserInfo() instead." + .expect( + """ + src/test/pkg/TestClass.java:6: Warning: Use UserTracker.getUserInfo() instead of UserManager.getUserInfo() [SlowUserInfoQuery] + userManager.getUserInfo(); + ~~~~~~~~~~~ + 0 errors, 1 warnings + """ ) } @@ -105,7 +112,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { package test.pkg; import com.android.systemui.settings.UserTracker; - public class TestClass3 { + public class TestClass { public void quicklyGetUserId(UserTracker userTracker) { userTracker.getUserId(); } @@ -132,7 +139,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() { package test.pkg; import com.android.systemui.settings.UserTracker; - public class TestClass4 { + public class TestClass { public void quicklyGetUserId(UserTracker userTracker) { userTracker.getUserInfo(); } 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 fd6ab09a2ccd..fb6537e92d15 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 @@ -31,8 +31,6 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() { override fun getIssues(): List<Issue> = listOf(SoftwareBitmapDetector.ISSUE) - private val explanation = "Usage of Config.HARDWARE is highly encouraged." - @Test fun testSoftwareBitmap() { lint() @@ -41,7 +39,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() { """ import android.graphics.Bitmap; - public class TestClass1 { + public class TestClass { public void test() { Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565); Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888); @@ -54,8 +52,17 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() { ) .issues(SoftwareBitmapDetector.ISSUE) .run() - .expectWarningCount(2) - .expectContains(explanation) + .expect( + """ + src/android/graphics/Bitmap.java:5: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap] + ARGB_8888, + ~~~~~~~~~ + src/android/graphics/Bitmap.java:6: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap] + RGB_565, + ~~~~~~~ + 0 errors, 2 warnings + """ + ) } @Test @@ -66,7 +73,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() { """ import android.graphics.Bitmap; - public class TestClass1 { + public class TestClass { public void test() { Bitmap.createBitmap(300, 300, Bitmap.Config.HARDWARE); } @@ -78,7 +85,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() { ) .issues(SoftwareBitmapDetector.ISSUE) .run() - .expectWarningCount(0) + .expectClean() } private val stubs = androidStubs diff --git a/packages/SystemUI/docs/device-entry/doze.md b/packages/SystemUI/docs/device-entry/doze.md index 6b6dce5da169..10bd3679a13b 100644 --- a/packages/SystemUI/docs/device-entry/doze.md +++ b/packages/SystemUI/docs/device-entry/doze.md @@ -1,5 +1,7 @@ # Doze +`Dozing` is a low-powered state of the device. If Always-on Display (AOD), pulsing, or wake-gestures are enabled, then the device will enter the `dozing` state after a user intent to turn off the screen (ie: power button) or the screen times out. + Always-on Display (AOD) provides an alternative 'screen-off' experience. Instead, of completely turning the display off, it provides a distraction-free, glanceable experience for the phone in a low-powered mode. In this low-powered mode, the display will have a lower refresh rate and the UI should frequently shift its displayed contents in order to prevent burn-in. The recommended max on-pixel-ratio (OPR) is 5% to reduce battery consumption.  @@ -58,7 +60,7 @@ When Dozing is enabled, it can still be suppressed based on the device state. On Refer to the documentation in [DozeSuppressors][15] for more information. ## AOD burn-in and image retention -Because AOD will show an image on the screen for an elogated period of time, AOD designs must take into consideration burn-in (leaving a permanent mark on the screen). Temporary burn-in is called image-retention. +Because AOD will show an image on the screen for an elongated period of time, AOD designs must take into consideration burn-in (leaving a permanent mark on the screen). Temporary burn-in is called image-retention. To prevent burn-in, it is recommended to often shift UI on the screen. [DozeUi][17] schedules a call to dozeTimeTick every minute to request a shift in UI for all elements on AOD. The amount of shift can be determined by undergoing simulated AOD testing since this may vary depending on the display. diff --git a/packages/SystemUI/docs/device-entry/glossary.md b/packages/SystemUI/docs/device-entry/glossary.md index f3d12c21a3a5..7f19b1688de0 100644 --- a/packages/SystemUI/docs/device-entry/glossary.md +++ b/packages/SystemUI/docs/device-entry/glossary.md @@ -2,38 +2,38 @@ ## Keyguard -| Term | Description | -| :-----------: | ----------- | -| Keyguard, [keyguard.md][1] | Coordinates the first experience when turning on the display of a device, as long as the user has not specified a security method of NONE. Consists of the lock screen and bouncer.| -| Lock screen<br><br>| The first screen available when turning on the display of a device, as long as the user has not specified a security method of NONE. On the lock screen, users can access:<ul><li>Quick Settings - users can swipe down from the top of the screen to interact with quick settings tiles</li><li>[Keyguard Status Bar][9] - This special status bar shows SIM related information and system icons.</li><li>Clock - uses the font specified at [clock.xml][8]. If the clock font supports variable weights, users will experience delightful clock weight animations - in particular, on transitions between the lock screen and AOD.</li><li>Notifications - ability to view and interact with notifications depending on user lock screen notification settings: `Settings > Display > Lock screen > Privacy`</li><li>Message area - contains device information like biometric errors, charging information and device policy information. Also includes user configured information from `Settings > Display > Lock screen > Add text on lock screen`. </li><li>Bouncer - if the user has a primary authentication method, they can swipe up from the bottom of the screen to bring up the bouncer.</li></ul>The lock screen is one state of the notification shade. See [StatusBarState#KEYGUARD][10] and [StatusBarState#SHADE_LOCKED][10].| -| Bouncer, [bouncer.md][2]<br><br>| The component responsible for displaying the primary security method set by the user (password, PIN, pattern). The bouncer can also show SIM-related security methods, allowing the user to unlock the device or SIM.| -| Split shade | State of the shade (which keyguard is a part of) in which notifications are on the right side and Quick Settings on the left. For keyguard that means notifications being on the right side and clock with media being on the left.<br><br>Split shade is automatically activated - using resources - for big screens in landscape, see [sw600dp-land/config.xml][3] `config_use_split_notification_shade`.<br><br>In that state we can see the big clock more often - every time when media is not visible on the lock screen. When there is no media and no notifications - or we enter AOD - big clock is always positioned in the center of the screen.<br><br>The magic of positioning views happens by changing constraints of [NotificationsQuickSettingsContainer][4] and positioning elements vertically in [KeyguardClockPositionAlgorithm][5]| -| Ambient display (AOD), [doze.md][6]<br><br>| UI shown when the device is in a low-powered display state. This is controlled by the doze component. The same lock screen views (ie: clock, notification shade) are used on AOD. The AOSP image on the left shows the usage of a clock that does not support variable weights which is why the clock is thicker in that image than what users see on Pixel devices.| +| Term | Description | +|--------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Keyguard, [keyguard.md][1] | Coordinates the first experience when turning on the display of a device, as long as the user has not specified a security method of NONE. Consists of the lock screen and bouncer. | +| Lock screen<br><br> | The first screen available when turning on the display of a device, as long as the user has not specified a security method of NONE. On the lock screen, users can access:<ul><li>Quick Settings - users can swipe down from the top of the screen to interact with quick settings tiles</li><li>[Keyguard Status Bar][9] - This special status bar shows SIM related information and system icons.</li><li>Clock - uses the font specified at [clock.xml][8]. If the clock font supports variable weights, users will experience delightful clock weight animations - in particular, on transitions between the lock screen and AOD.</li><li>Notifications - ability to view and interact with notifications depending on user lock screen notification settings: `Settings > Display > Lock screen > Privacy`</li><li>Message area - contains device information like biometric errors, charging information and device policy information. Also includes user configured information from `Settings > Display > Lock screen > Add text on lock screen`. </li><li>Bouncer - if the user has a primary authentication method, they can swipe up from the bottom of the screen to bring up the bouncer.</li></ul>The lock screen is one state of the notification shade. See [StatusBarState#KEYGUARD][10] and [StatusBarState#SHADE_LOCKED][10]. | +| Bouncer, [bouncer.md][2]<br><br> | The component responsible for displaying the primary security method set by the user (password, PIN, pattern). The bouncer can also show SIM-related security methods, allowing the user to unlock the device or SIM. | +| Split shade | State of the shade (which keyguard is a part of) in which notifications are on the right side and Quick Settings on the left. For keyguard that means notifications being on the right side and clock with media being on the left.<br><br>Split shade is automatically activated - using resources - for big screens in landscape, see [sw600dp-land/config.xml][3] `config_use_split_notification_shade`.<br><br>In that state we can see the big clock more often - every time when media is not visible on the lock screen. When there is no media and no notifications - or we enter AOD - big clock is always positioned in the center of the screen.<br><br>The magic of positioning views happens by changing constraints of [NotificationsQuickSettingsContainer][4] and positioning elements vertically in [KeyguardClockPositionAlgorithm][5] | +| Ambient display (AOD), [doze.md][6]<br><br> | UI shown when the device is in a low-powered display state. This is controlled by the doze component. The same lock screen views (ie: clock, notification shade) are used on AOD. The AOSP image on the left shows the usage of a clock that does not support variable weights which is why the clock is thicker in that image than what users see on Pixel devices. | ## General Authentication Terms -| Term | Description | -| ----------- | ----------- | -| Primary Authentication | The strongest form of authentication. Includes: Pin, pattern and password input.| -| Biometric Authentication | Face or fingerprint input. Biometric authentication is categorized into different classes of security. See [Measuring Biometric Security][7].| +| Term | Description | +|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| Primary Authentication | The strongest form of authentication. Includes: Pin, pattern and password input. | +| Biometric Authentication | Face or fingerprint input. Biometric authentication is categorized into different classes of security. See [Measuring Biometric Security][7]. | ## Face Authentication Terms -| Term | Description | -| ----------- | ----------- | -| Passive Authentication | When a user hasn't explicitly requested an authentication method; however, it may still put the device in an unlocked state.<br><br>For example, face authentication is triggered immediately when waking the device; however, users may not have the intent of unlocking their device. Instead, they could have wanted to just check the lock screen. Because of this, SystemUI provides the option for a bypass OR non-bypass face authentication experience which have different user flows.<br><br>In contrast, fingerprint authentication is considered an active authentication method since users need to actively put their finger on the fingerprint sensor to authenticate. Therefore, it's an explicit request for authentication and SystemUI knows the user has the intent for device-entry.| -| Bypass | Used to refer to the face authentication bypass device entry experience. We have this distinction because face auth is a passive authentication method (see above).| -| Bypass User Journey <br><br>| Once the user successfully authenticates with face, the keyguard immediately dismisses and the user is brought to the home screen/last app. This CUJ prioritizes speed of device entry. SystemUI hides interactive views (notifications) on the lock screen to avoid putting users in a state where the lock screen could immediately disappear while they're interacting with affordances on the lock screen.| -| Non-bypass User Journey | Once the user successfully authenticates with face, the device remains on keyguard until the user performs an action to indicate they'd like to enter the device (ie: swipe up on the lock screen or long press on the unlocked icon). This CUJ prioritizes notification visibility.| +| Term | Description | +|-----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Passive Authentication | When a user hasn't explicitly requested an authentication method; however, it may still put the device in an unlocked state.<br><br>For example, face authentication is triggered immediately when waking the device; however, users may not have the intent of unlocking their device. Instead, they could have wanted to just check the lock screen. Because of this, SystemUI provides the option for a bypass OR non-bypass face authentication experience which have different user flows.<br><br>In contrast, fingerprint authentication is considered an active authentication method since users need to actively put their finger on the fingerprint sensor to authenticate. Therefore, it's an explicit request for authentication and SystemUI knows the user has the intent for device-entry. | +| Bypass | Used to refer to the face authentication bypass device entry experience. We have this distinction because face auth is a passive authentication method (see above). | +| Bypass User Journey <br><br> | Once the user successfully authenticates with face, the keyguard immediately dismisses and the user is brought to the home screen/last app. This CUJ prioritizes speed of device entry. SystemUI hides interactive views (notifications) on the lock screen to avoid putting users in a state where the lock screen could immediately disappear while they're interacting with affordances on the lock screen. | +| Non-bypass User Journey | Once the user successfully authenticates with face, the device remains on keyguard until the user performs an action to indicate they'd like to enter the device (ie: swipe up on the lock screen or long press on the unlocked icon). This CUJ prioritizes notification visibility. | ## Fingerprint Authentication Terms -| Term | Description | -| ----------- | ----------- | -| Under-display fingerprint sensor (UDFPS) | References the HW affordance for a fingerprint sensor that is under the display, which requires a software visual affordance. System UI supports showing the UDFPS affordance on the lock screen and on AOD. Users cannot authenticate from the screen-off state.<br><br>Supported SystemUI CUJs include:<ul><li> sliding finger on the screen to the UDFPS area to being authentication (as opposed to directly placing finger in the UDFPS area) </li><li> when a11y services are enabled, there is a haptic played when a touch is detected on UDFPS</li><li>after two hard-fingerprint-failures, the primary authentication bouncer is shown</li><li> when tapping on an affordance that requests to dismiss the lock screen, the user may see the UDFPS icon highlighted - see UDFPS bouncer</li></ul>| -| UDFPS Bouncer | UI that highlights the UDFPS sensor. Users can get into this state after tapping on a notification from the lock screen or locked expanded shade.| +| Term | Description | +|------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Under-display fingerprint sensor (UDFPS) | References the HW affordance for a fingerprint sensor that is under the display, which requires a software visual affordance. System UI supports showing the UDFPS affordance on the lock screen and on AOD. Users cannot authenticate from the screen-off state.<br><br>Supported SystemUI CUJs include:<ul><li> sliding finger on the screen to the UDFPS area to being authentication (as opposed to directly placing finger in the UDFPS area) </li><li> when a11y services are enabled, there is a haptic played when a touch is detected on UDFPS</li><li>after multiple consecutive hard-fingerprint-failures, the primary authentication bouncer is shown. The exact number of attempts is defined in: [BiometricUnlockController#UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER][4]</li><li> when tapping on an affordance that requests to dismiss the lock screen, the user may see the UDFPS icon highlighted - see UDFPS bouncer</li></ul> | +| UDFPS Bouncer | UI that highlights the UDFPS sensor. Users can get into this state after tapping on a notification from the lock screen or locked expanded shade. | ## Other Authentication Terms -| Term | Description | -| ---------- | ----------- | -| Trust Agents | Provides signals to the keyguard to allow it to lock less frequently.| +| Term | Description | +|--------------|-----------------------------------------------------------------------| +| Trust Agents | Provides signals to the keyguard to allow it to lock less frequently. | [1]: /frameworks/base/packages/SystemUI/docs/device-entry/keyguard.md @@ -46,3 +46,4 @@ [8]: /frameworks/base/packages/SystemUI/res-keyguard/font/clock.xml [9]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java [10]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java +[11]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java 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/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/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/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/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/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/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..b5f42a164495 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; @@ -973,7 +974,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; 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/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/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..05e3f1ce87a6 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -39,8 +39,8 @@ import javax.inject.Inject; * 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,6 +49,7 @@ 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 ClipboardManager mClipboardManager; @@ -59,7 +60,7 @@ public class ClipboardListener extends CoreStartable public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy, ClipboardOverlayControllerFactory overlayFactory, ClipboardManager clipboardManager, UiEventLogger uiEventLogger) { - super(context); + mContext = context; mDeviceConfig = deviceConfigProxy; mOverlayFactory = overlayFactory; mClipboardManager = clipboardManager; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index c2dffe8bdad0..d05bd5120872 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.people.PeopleProvider; +import com.android.systemui.statusbar.QsFrameTranslateModule; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.unfold.FoldStateLogger; import com.android.systemui.unfold.FoldStateLoggingProvider; @@ -63,6 +64,7 @@ import dagger.Subcomponent; @Subcomponent(modules = { DefaultComponentBinder.class, DependencyProvider.class, + QsFrameTranslateModule.class, SystemUIBinder.class, SystemUIModule.class, SystemUICoreStartableModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index d70b971dba14..dc3dadb32669 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -61,7 +61,6 @@ import com.android.systemui.smartspace.dagger.SmartspaceModule; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.QsFrameTranslateModule; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; @@ -133,7 +132,6 @@ import dagger.Provides; PeopleModule.class, PluginModule.class, PrivacyModule.class, - QsFrameTranslateModule.class, ScreenshotModule.class, SensorModule.class, MultiUserUtilsModule.class, 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..0ccb222c8acc 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; 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/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..d89451992351 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -105,10 +105,6 @@ public class Flags { */ 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`. * @@ -223,6 +219,7 @@ public class Flags { public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904); public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905); public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906); + public static final UnreleasedFlag UMO_SURFACE_RIPPLE = new UnreleasedFlag(907); // 1000 - dock public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING = @@ -232,6 +229,8 @@ public class Flags { public static final UnreleasedFlag ROUNDED_BOX_RIPPLE = new UnreleasedFlag(1002, /* teamfood= */ true); + public static final UnreleasedFlag REFACTORED_DOCK_SETUP = new UnreleasedFlag(1003, true); + // 1100 - windowing @Keep public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS = 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/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/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/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/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/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 529d0662028a..1b81e88e62ba 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -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/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..80bff83d03a0 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,7 +128,8 @@ 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>() @@ -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 @@ -633,12 +657,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 +716,7 @@ class MediaCarouselController @Inject constructor( mediaCarouselScrollHandler.setCarouselBounds( currentCarouselWidth, currentCarouselHeight) updatePageIndicatorLocation() + updatePageIndicatorAlpha() } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt index ef49fd35d703..a776897b2fd5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt @@ -47,7 +47,7 @@ private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig]. */ private val translationConfig = PhysicsAnimator.SpringConfig( - SpringForce.STIFFNESS_MEDIUM, + SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY) /** @@ -289,7 +289,10 @@ class MediaCarouselScrollHandler( return false } } - if (isUp || motionEvent.action == MotionEvent.ACTION_CANCEL) { + if (motionEvent.action == MotionEvent.ACTION_MOVE) { + // cancel on going animation if there is any. + PhysicsAnimator.getInstance(this).cancel() + } else if (isUp || motionEvent.action == MotionEvent.ACTION_CANCEL) { // It's an up and the fling didn't take it above val relativePos = scrollView.relativeScrollX % playerWidthPlusPadding val scrollXAmount: Int 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/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/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/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/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/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/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 50a10bc0b15a..6da8c69c013b 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); @@ -682,11 +708,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 +762,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 +1645,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/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/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/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 3e445ddfc2a1..d39368012487 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -36,6 +36,7 @@ import android.util.ArraySet; import android.util.Log; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; @@ -182,6 +183,10 @@ public class TileLifecycleManager extends BroadcastReceiver implements setBindService(true); } + /** + * Binds or unbinds to IQSService + */ + @WorkerThread public void setBindService(boolean bind) { if (mBound && mUnbindImmediate) { // If we are already bound and expecting to unbind, this means we should stay bound 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/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/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt index 309059fdb9ad..95cc0dcadfb4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt @@ -76,7 +76,7 @@ class RequestProcessor @Inject constructor( ) } else { // Create a new request of the same type which includes the top component - ScreenshotRequest(request.source, request.type, info.component) + ScreenshotRequest(request.type, request.source, info.component) } } 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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 6d76c171b143..a49b7f03acc6 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; @@ -706,6 +763,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 +880,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 +968,6 @@ public final class NotificationPanelViewController extends PanelViewController { mLargeScreenShadeHeaderController = largeScreenShadeHeaderController; mLayoutInflater = layoutInflater; mFeatureFlags = featureFlags; - mFalsingManager = falsingManager; mFalsingCollector = falsingCollector; mPowerManager = powerManager; mWakeUpCoordinator = coordinator; @@ -838,7 +983,6 @@ public final class NotificationPanelViewController extends PanelViewController { mUserManager = userManager; mMediaDataManager = mediaDataManager; mTapAgainViewController = tapAgainViewController; - mInteractionJankMonitor = interactionJankMonitor; mSysUiState = sysUiState; mPanelEventsEmitter = panelEventsEmitter; pulseExpansionHandler.setPulseExpandAbortListener(() -> { @@ -1040,9 +1184,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()); @@ -1715,11 +1864,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 +1877,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 +1910,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 +1986,123 @@ 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); + private 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 +2164,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 +2180,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 +2201,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 +2334,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 +2347,7 @@ public final class NotificationPanelViewController extends PanelViewController { return false; } - @Override - protected float getOpeningHeight() { + private float getOpeningHeight() { return mNotificationStackScrollLayoutController.getOpeningHeight(); } @@ -2236,9 +2498,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 +2520,7 @@ public final class NotificationPanelViewController extends PanelViewController { return expands; } - @Override - protected boolean shouldGestureWaitForTouchSlop() { + private boolean shouldGestureWaitForTouchSlop() { if (mExpectingSynthesizedDown) { mExpectingSynthesizedDown = false; return false; @@ -2326,7 +2598,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); } @@ -3066,8 +3338,8 @@ public final class NotificationPanelViewController extends PanelViewController { } } - @Override - protected boolean canCollapsePanelOnTouch() { + @VisibleForTesting + boolean canCollapsePanelOnTouch() { if (!isInSettings() && mBarState == KEYGUARD) { return true; } @@ -3079,7 +3351,6 @@ public final class NotificationPanelViewController extends PanelViewController { return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS); } - @Override public int getMaxPanelHeight() { int min = mStatusBarMinHeight; if (!(mBarState == KEYGUARD) @@ -3113,8 +3384,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 +3563,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 +3579,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 +3631,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(); @@ -3383,14 +3690,18 @@ public final class NotificationPanelViewController extends PanelViewController { } private void updateQsFrameTranslation() { - mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs, mOverExpansion, - mQsTranslationForFullShadeTransition); + mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs, + mNavigationBarBottomHeight + mAmbientState.getStackTopMargin()); + } - @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); @@ -3400,10 +3711,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 */); @@ -3420,37 +3732,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 @@ -3475,8 +3798,8 @@ public final class NotificationPanelViewController extends PanelViewController { } } - @Override - protected boolean isTrackingBlocked() { + @VisibleForTesting + boolean isTrackingBlocked() { return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch; } @@ -3498,19 +3821,17 @@ 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) { + private void setIsClosing(boolean isClosing) { boolean wasClosing = isClosing(); - super.setIsClosing(isClosing); + mClosing = isClosing; if (wasClosing != isClosing) { mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing); } @@ -3523,7 +3844,6 @@ public final class NotificationPanelViewController extends PanelViewController { } } - @Override public boolean isDozing() { return mDozing; } @@ -3540,8 +3860,7 @@ public final class NotificationPanelViewController extends PanelViewController { mKeyguardStatusViewController.dozeTimeTick(); } - @Override - protected boolean onMiddleClicked() { + private boolean onMiddleClicked() { switch (mBarState) { case KEYGUARD: if (!mDozingOnDown) { @@ -3600,15 +3919,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); @@ -3622,8 +3939,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(); @@ -3712,8 +4028,7 @@ public final class NotificationPanelViewController extends PanelViewController { mCentralSurfaces.clearNotificationEffects(); } - @Override - protected boolean isPanelVisibleBecauseOfHeadsUp() { + private boolean isPanelVisibleBecauseOfHeadsUp() { return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway) && mBarState == StatusBarState.SHADE; } @@ -3828,9 +4143,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); } @@ -4030,9 +4351,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()); @@ -4175,126 +4501,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 = @@ -4346,8 +4559,7 @@ public final class NotificationPanelViewController extends PanelViewController { } }; - @Override - protected OnConfigurationChangedListener createOnConfigurationChangedListener() { + private OnConfigurationChangedListener createOnConfigurationChangedListener() { return new OnConfigurationChangedListener(); } @@ -4409,6 +4621,593 @@ 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)); + } + + private void notifyExpandingStarted() { + if (!mExpanding) { + mExpanding = true; + onExpandingStarted(); + } + } + + private 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) { @@ -4817,13 +5616,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()); @@ -5081,4 +5885,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..7bee0ba17afc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -11,38 +11,65 @@ import javax.inject.Inject private const val TAG = "systemui.shade" /** Lightweight logging utility for the Shade. */ -class ShadeLogger @Inject constructor( - @ShadeLog - private val buffer: LogBuffer -) { - fun v(@CompileTimeConstant msg: String) { - buffer.log(TAG, LogLevel.VERBOSE, msg) - } +class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { + fun v(@CompileTimeConstant msg: String) { + buffer.log(TAG, LogLevel.VERBOSE, msg) + } - private inline fun log( - logLevel: LogLevel, - initializer: LogMessage.() -> Unit, - noinline printer: LogMessage.() -> String - ) { - buffer.log(TAG, logLevel, initializer, printer) - } + private inline fun log( + logLevel: LogLevel, + initializer: LogMessage.() -> Unit, + noinline printer: LogMessage.() -> String + ) { + buffer.log(TAG, logLevel, initializer, printer) + } - fun onQsInterceptMoveQsTrackingEnabled(h: Float) { - log(LogLevel.VERBOSE, - { double1 = h.toDouble() }, - { "onQsIn[tercept: move action, QS tracking enabled. h = $double1" }) - } + fun onQsInterceptMoveQsTrackingEnabled(h: Float) { + log( + LogLevel.VERBOSE, + { double1 = h.toDouble() }, + { "onQsIntercept: move action, QS tracking enabled. h = $double1" }) + } - fun logMotionEvent(event: MotionEvent, message: String) { - log(LogLevel.VERBOSE, { - str1 = message - long1 = event.eventTime - long2 = event.downTime - int1 = event.action - int2 = event.classification - double1 = event.y.toDouble() - }, { - "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,classification=$int2" + 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" }) + } } 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/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/QsFrameTranslateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java index 78077386179a..59afb18195dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java @@ -36,8 +36,7 @@ public abstract class QsFrameTranslateController { /** * Calculate and translate the QS Frame on the Y-axis. */ - public abstract void translateQsFrame(View qsFrame, QS qs, float overExpansion, - float qsTranslationForFullShadeTransition); + public abstract void translateQsFrame(View qsFrame, QS qs, int bottomInset); /** * Calculate the top padding for notifications panel. This could be the supplied diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java index 33e224579bef..85b522cbd9d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java @@ -27,6 +27,8 @@ import javax.inject.Inject; /** * Default implementation of QS Translation. This by default does not do much. + * This class can be subclassed to allow System UI variants the flexibility to change position of + * the Quick Settings frame. */ @SysUISingleton public class QsFrameTranslateImpl extends QsFrameTranslateController { @@ -37,8 +39,8 @@ public class QsFrameTranslateImpl extends QsFrameTranslateController { } @Override - public void translateQsFrame(View qsFrame, QS qs, float overExpansion, - float qsTranslationForFullShadeTransition) { + public void translateQsFrame(View qsFrame, QS qs, int bottomInset) { + // Empty implementation by default, meant to be overridden by subclasses. } @Override 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..822840dfd86b 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; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index ccf6feca6992..8f3eb4f7e223 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -440,6 +440,42 @@ class HeadsUpCoordinator @Inject constructor( override fun onEntryCleanUp(entry: NotificationEntry) { mHeadsUpViewBinder.abortBindCallback(entry) } + + /** + * Identify notifications whose heads-up state changes when the notification rankings are + * updated, and have those changed notifications alert if necessary. + * + * This method will occur after any operations in onEntryAdded or onEntryUpdated, so any + * handling of ranking changes needs to take into account that we may have just made a + * PostedEntry for some of these notifications. + */ + override fun onRankingApplied() { + // Because a ranking update may cause some notifications that are no longer (or were + // never) in mPostedEntries to need to alert, we need to check every notification + // known to the pipeline. + for (entry in mNotifPipeline.allNotifs) { + // The only entries we can consider alerting for here are entries that have never + // interrupted and that now say they should heads up; if they've alerted in the + // past, we don't want to incorrectly alert a second time if there wasn't an + // explicit notification update. + if (entry.hasInterrupted()) continue + + // The cases where we should consider this notification to be updated: + // - if this entry is not present in PostedEntries, and is now in a shouldHeadsUp + // state + // - if it is present in PostedEntries and the previous state of shouldHeadsUp + // differs from the updated one + val shouldHeadsUpEver = mNotificationInterruptStateProvider.checkHeadsUp(entry, + /* log= */ false) + val postedShouldHeadsUpEver = mPostedEntries[entry.key]?.shouldHeadsUpEver ?: false + val shouldUpdateEntry = postedShouldHeadsUpEver != shouldHeadsUpEver + + if (shouldUpdateEntry) { + mLogger.logEntryUpdatedByRanking(entry.key, shouldHeadsUpEver) + onEntryUpdated(entry) + } + } + } } /** 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 204a494c32e8..8625cdbc89d5 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 @@ -59,4 +59,13 @@ class HeadsUpCoordinatorLogger constructor( " numPostedEntries=$int1 logicalGroupSize=$int2" }) } + + fun logEntryUpdatedByRanking(key: String, shouldHun: Boolean) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + bool1 = shouldHun + }, { + "updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1" + }) + } } 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..5dbec8dcba20 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 @@ -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/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 087dc71f6cf2..1b006485c83d 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.setAlpha(alpha); + mChildrenContainer.setContentAlpha(alpha); } } 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 7b23a56af836..0dda2632db66 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,6 +461,20 @@ 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/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 836cacc185c6..55c577f1ea39 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 @@ -1115,6 +1115,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateAlgorithmLayoutMinHeight(); updateOwnTranslationZ(); + // Give The Algorithm information regarding the QS height so it can layout notifications + // properly. Needed for some devices that grows notifications down-to-top + mStackScrollAlgorithm.updateQSFrameTop(mQsHeader == null ? 0 : mQsHeader.getHeight()); + // Once the layout has finished, we don't need to animate any scrolling clampings anymore. mAnimateStackYForContentHeightChange = false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 8d28f7524f9a..0502159f46cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -417,12 +417,19 @@ public class StackScrollAlgorithm { } /** + * Update the position of QS Frame. + */ + public void updateQSFrameTop(int qsHeight) { + // Intentionally empty for sub-classes in other device form factors to override + } + + /** * Determine the positions for the views. This is the main part of the algorithm. * * @param algorithmState The state in which the current pass of the algorithm is currently in * @param ambientState The current ambient state */ - private void updatePositionsForState(StackScrollAlgorithmState algorithmState, + protected void updatePositionsForState(StackScrollAlgorithmState algorithmState, AmbientState ambientState) { if (!ambientState.isOnKeyguard() || (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) { @@ -448,7 +455,7 @@ public class StackScrollAlgorithm { * @return Fraction to apply to view height and gap between views. * Does not include shelf height even if shelf is showing. */ - private float getExpansionFractionWithoutShelf( + protected float getExpansionFractionWithoutShelf( StackScrollAlgorithmState algorithmState, AmbientState ambientState) { 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..34935db72467 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; @@ -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/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index b9312c7fc40a..ebc79ecde134 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -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(); } @@ -1175,6 +1177,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/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt index 5b2d69564585..2f0ebf752a23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt @@ -35,8 +35,8 @@ 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)) @@ -112,6 +112,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/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..5cbdf7c43a12 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. diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index a345d99b47c6..3d56f2317660 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -100,7 +100,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 +114,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { private final SecureSettings mSecureSettings; private final Executor mMainExecutor; private final Handler mBgHandler; + private final Context mContext; private final boolean mIsMonetEnabled; private final UserTracker mUserTracker; private final DeviceProvisionedController mDeviceProvisionedController; @@ -361,8 +362,7 @@ 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; mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET); mDeviceProvisionedController = deviceProvisionedController; mBroadcastDispatcher = broadcastDispatcher; 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/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..ba5a82a42d94 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) } } } @@ -264,7 +276,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 +497,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 +590,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..91c592177d19 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 @@ -48,7 +48,7 @@ constructor( private val dialogLaunchAnimator: DialogLaunchAnimator, private val interactor: UserInteractor, private val featureFlags: FeatureFlags, -) : CoreStartable(context) { +) : CoreStartable { private var currentDialog: Dialog? = null 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..219dae29117f 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) /** @@ -118,6 +117,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 +179,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/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/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/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..2319f4386798 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -231,7 +231,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { } @Override - protected void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mExecutor.runAllReady(); } 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/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/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/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..cc6874ba3ba7 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 @@ -276,6 +281,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!)) } + @Test fun testMediaLoaded_ScrollToActivePlayer() { listener.value.onMediaDataLoaded("playing local", null, @@ -287,8 +293,8 @@ 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) + listener.value.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, + false) mediaCarouselController.shouldScrollToActivePlayer = true // switching between media players. listener.value.onMediaDataLoaded("playing local", @@ -398,4 +404,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/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/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/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/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/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt index 5cb27a47d384..46a502acba16 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt @@ -99,13 +99,14 @@ class RequestProcessorTest { policy.getDefaultDisplayId(), DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)) - val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD) + val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER) val processor = RequestProcessor(imageCapture, policy, flags, scope) val processedRequest = processor.process(request) // Request has topComponent added, but otherwise unchanged. assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN) + assertThat(processedRequest.source).isEqualTo(SCREENSHOT_OTHER) assertThat(processedRequest.topComponent).isEqualTo(component) } 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..c0dae03023c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -716,6 +716,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); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index 2970807afb36..340bc96f80c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -47,6 +47,8 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.time.FakeSystemClock +import java.util.ArrayList +import java.util.function.Consumer import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -57,10 +59,8 @@ import org.mockito.BDDMockito.given import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import java.util.ArrayList -import java.util.function.Consumer import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -671,8 +671,64 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(mHeadsUpManager, never()).showNotification(mGroupChild2) } + @Test + fun testOnRankingApplied_newEntryShouldAlert() { + // GIVEN that mEntry has never interrupted in the past, and now should + assertFalse(mEntry.hasInterrupted()) + setShouldHeadsUp(mEntry) + whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) + + // WHEN a ranking applied update occurs + mCollectionListener.onRankingApplied() + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + + // THEN the notification is shown + finishBind(mEntry) + verify(mHeadsUpManager).showNotification(mEntry) + } + + @Test + fun testOnRankingApplied_alreadyAlertedEntryShouldNotAlertAgain() { + // GIVEN that mEntry has alerted in the past + mEntry.setInterruption() + setShouldHeadsUp(mEntry) + whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) + + // WHEN a ranking applied update occurs + mCollectionListener.onRankingApplied() + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + + // THEN the notification is never bound or shown + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(mHeadsUpManager, never()).showNotification(any()) + } + + @Test + fun testOnRankingApplied_entryUpdatedToHun() { + // GIVEN that mEntry is added in a state where it should not HUN + setShouldHeadsUp(mEntry, false) + mCollectionListener.onEntryAdded(mEntry) + + // and it is then updated such that it should now HUN + setShouldHeadsUp(mEntry) + whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) + + // WHEN a ranking applied update occurs + mCollectionListener.onRankingApplied() + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + + // THEN the notification is shown + finishBind(mEntry) + verify(mHeadsUpManager).showNotification(mEntry) + } + private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) { whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should) + whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any())) + .thenReturn(should) } private fun finishBind(entry: NotificationEntry) { 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/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/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/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/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/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/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..fa751007198e 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 @@ -333,6 +333,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/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 619b29d33361..a63647cc1549 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -467,6 +467,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; @@ -1374,6 +1377,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( @@ -2495,6 +2500,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 @@ -4417,6 +4430,8 @@ public final class PowerManagerService extends SystemService + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig); pw.println(" mTheaterModeEnabled=" + mTheaterModeEnabled); + pw.println(" mKeepDreamingWhenUndockingConfig=" + + mKeepDreamingWhenUndockingConfig); pw.println(" mSuspendWhenScreenOffDueToProximityConfig=" + mSuspendWhenScreenOffDueToProximityConfig); pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9c080e856500..2eb2cf643c42 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4076,7 +4076,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // to the restarted activity. nowVisible = mVisibleRequested; } - mTransitionController.requestCloseTransitionIfNeeded(this); + // upgrade transition trigger to task if this is the last activity since it means we are + // closing the task. + final WindowContainer trigger = remove && task != null && task.getChildCount() == 1 + ? task : this; + mTransitionController.requestCloseTransitionIfNeeded(trigger); cleanUp(true /* cleanServices */, true /* setState */); if (remove) { if (mStartingData != null && mVisible && task != null) { @@ -7634,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/Transition.java b/services/core/java/com/android/server/wm/Transition.java index b1862b6f274f..46253c1933b6 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1878,15 +1878,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe flags |= FLAG_TRANSLUCENT; } final Task task = wc.asTask(); - if (task != null && task.voiceSession != null) { - flags |= FLAG_IS_VOICE_INTERACTION; - } if (task != null) { final ActivityRecord topActivity = task.getTopNonFinishingActivity(); if (topActivity != null && topActivity.mStartingData != null && topActivity.mStartingData.hasImeSurface()) { flags |= FLAG_WILL_IME_SHOWN; } + if (task.voiceSession != null) { + flags |= FLAG_IS_VOICE_INTERACTION; + } } Task parentTask = null; final ActivityRecord record = wc.asActivityRecord(); @@ -1914,20 +1914,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Whether the container fills its parent Task bounds. flags |= FLAG_FILLS_TASK; } - } - final DisplayContent dc = wc.asDisplayContent(); - if (dc != null) { - flags |= FLAG_IS_DISPLAY; - if (dc.hasAlertWindowSurfaces()) { - flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS; + } else { + final DisplayContent dc = wc.asDisplayContent(); + if (dc != null) { + flags |= FLAG_IS_DISPLAY; + if (dc.hasAlertWindowSurfaces()) { + flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS; + } + } else if (isWallpaper(wc)) { + flags |= FLAG_IS_WALLPAPER; + } else if (isInputMethod(wc)) { + flags |= FLAG_IS_INPUT_METHOD; + } else { + // In this condition, the wc can only be WindowToken or DisplayArea. + final int type = wc.getWindowType(); + if (type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW + && type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { + flags |= TransitionInfo.FLAG_IS_SYSTEM_WINDOW; + } } } - if (isWallpaper(wc)) { - flags |= FLAG_IS_WALLPAPER; - } - if (isInputMethod(wc)) { - flags |= FLAG_IS_INPUT_METHOD; - } if (occludesKeyguard(wc)) { flags |= FLAG_OCCLUDES_KEYGUARD; } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index e8682f7a3b22..26ce4ae8415c 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -126,19 +126,27 @@ class TransitionController { mTransitionTracer = transitionTracer; mTransitionPlayerDeath = () -> { synchronized (mAtm.mGlobalLock) { - // Clean-up/finish any playing transitions. - for (int i = 0; i < mPlayingTransitions.size(); ++i) { - mPlayingTransitions.get(i).cleanUpOnFailure(); - } - mPlayingTransitions.clear(); - mTransitionPlayer = null; - mTransitionPlayerProc = null; - mRemotePlayer.clear(); - mRunningLock.doNotifyLocked(); + detachPlayer(); } }; } + private void detachPlayer() { + if (mTransitionPlayer == null) return; + // Clean-up/finish any playing transitions. + for (int i = 0; i < mPlayingTransitions.size(); ++i) { + mPlayingTransitions.get(i).cleanUpOnFailure(); + } + mPlayingTransitions.clear(); + if (mCollectingTransition != null) { + mCollectingTransition.abort(); + } + mTransitionPlayer = null; + mTransitionPlayerProc = null; + mRemotePlayer.clear(); + mRunningLock.doNotifyLocked(); + } + /** @see #createTransition(int, int) */ @NonNull Transition createTransition(int type) { @@ -193,7 +201,7 @@ class TransitionController { if (mTransitionPlayer.asBinder() != null) { mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0); } - mTransitionPlayer = null; + detachPlayer(); } if (player.asBinder() != null) { player.asBinder().linkToDeath(mTransitionPlayerDeath, 0); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 949fa9662258..32a110ea530e 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1136,10 +1136,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final LauncherAppsServiceInternal launcherApps = LocalServices.getService( LauncherAppsServiceInternal.class); - launcherApps.startShortcut(caller.mUid, caller.mPid, callingPackage, - hop.getShortcutInfo().getPackage(), null /* default featureId */, + final boolean success = launcherApps.startShortcut(caller.mUid, caller.mPid, + callingPackage, hop.getShortcutInfo().getPackage(), null /* featureId */, hop.getShortcutInfo().getId(), null /* sourceBounds */, launchOpts, hop.getShortcutInfo().getUserId()); + if (success) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } break; } case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: { 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..a5c181d53286 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; @@ -215,7 +216,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 +229,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); 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..2d0cbfd27289 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -612,6 +612,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(); 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/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java index eafcef2f1d38..1e74451a8d4d 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java @@ -210,21 +210,15 @@ final class TranslationManagerServiceImpl extends final int translatedAppUid = getAppUidByComponentName(getContext(), componentName, getUserId()); final String packageName = componentName.getPackageName(); - if (activityDestroyed) { - // In the Activity destroy case, we only calls onTranslationFinished() in - // non-finisTranslation() state. If there is a finisTranslation() calls by apps, we - // should remove the waiting callback to avoid callback twice. + // In the Activity destroyed case, we only call onTranslationFinished() in + // non-finishTranslation() state. If there is a finishTranslation() call by apps, we + // should remove the waiting callback to avoid invoking callbacks twice. + if (activityDestroyed || mWaitingFinishedCallbackActivities.contains(token)) { invokeCallbacks(STATE_UI_TRANSLATION_FINISHED, /* sourceSpec= */ null, /* targetSpec= */ null, packageName, translatedAppUid); mWaitingFinishedCallbackActivities.remove(token); - } else { - if (mWaitingFinishedCallbackActivities.contains(token)) { - invokeCallbacks(STATE_UI_TRANSLATION_FINISHED, - /* sourceSpec= */ null, /* targetSpec= */ null, - packageName, translatedAppUid); - mWaitingFinishedCallbackActivities.remove(token); - } + mActiveTranslations.remove(token); } } @@ -237,6 +231,9 @@ final class TranslationManagerServiceImpl extends // Activity is the new Activity, the original Activity is paused in the same task. // To make sure the operation still work, we use the token to find the target Activity in // this task, not the top Activity only. + // + // Note: getAttachedNonFinishingActivityForTask() takes the shareable activity token. We + // call this method so that we can get the regular activity token below. ActivityTokens candidateActivityTokens = mActivityTaskManagerInternal.getAttachedNonFinishingActivityForTask(taskId, token); if (candidateActivityTokens == null) { @@ -263,27 +260,27 @@ final class TranslationManagerServiceImpl extends getAppUidByComponentName(getContext(), componentName, getUserId()); String packageName = componentName.getPackageName(); - invokeCallbacksIfNecessaryLocked(state, sourceSpec, targetSpec, packageName, activityToken, + invokeCallbacksIfNecessaryLocked(state, sourceSpec, targetSpec, packageName, token, translatedAppUid); - updateActiveTranslationsLocked(state, sourceSpec, targetSpec, packageName, activityToken, + updateActiveTranslationsLocked(state, sourceSpec, targetSpec, packageName, token, translatedAppUid); } @GuardedBy("mLock") private void updateActiveTranslationsLocked(int state, TranslationSpec sourceSpec, - TranslationSpec targetSpec, String packageName, IBinder activityToken, + TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken, int translatedAppUid) { // We keep track of active translations and their state so that we can: // 1. Trigger callbacks that are registered after translation has started. // See registerUiTranslationStateCallbackLocked(). // 2. NOT trigger callbacks when the state didn't change. // See invokeCallbacksIfNecessaryLocked(). - ActiveTranslation activeTranslation = mActiveTranslations.get(activityToken); + ActiveTranslation activeTranslation = mActiveTranslations.get(shareableActivityToken); switch (state) { case STATE_UI_TRANSLATION_STARTED: { if (activeTranslation == null) { try { - activityToken.linkToDeath(this, /* flags= */ 0); + shareableActivityToken.linkToDeath(this, /* flags= */ 0); } catch (RemoteException e) { Slog.w(TAG, "Failed to call linkToDeath for translated app with uid=" + translatedAppUid + "; activity is already dead", e); @@ -294,7 +291,7 @@ final class TranslationManagerServiceImpl extends packageName, translatedAppUid); return; } - mActiveTranslations.put(activityToken, + mActiveTranslations.put(shareableActivityToken, new ActiveTranslation(sourceSpec, targetSpec, translatedAppUid, packageName)); } @@ -317,7 +314,7 @@ final class TranslationManagerServiceImpl extends case STATE_UI_TRANSLATION_FINISHED: { if (activeTranslation != null) { - mActiveTranslations.remove(activityToken); + mActiveTranslations.remove(shareableActivityToken); } break; } @@ -332,12 +329,12 @@ final class TranslationManagerServiceImpl extends @GuardedBy("mLock") private void invokeCallbacksIfNecessaryLocked(int state, TranslationSpec sourceSpec, - TranslationSpec targetSpec, String packageName, IBinder activityToken, + TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken, int translatedAppUid) { boolean shouldInvokeCallbacks = true; int stateForCallbackInvocation = state; - ActiveTranslation activeTranslation = mActiveTranslations.get(activityToken); + ActiveTranslation activeTranslation = mActiveTranslations.get(shareableActivityToken); if (activeTranslation == null) { if (state != STATE_UI_TRANSLATION_STARTED) { shouldInvokeCallbacks = false; @@ -403,14 +400,6 @@ final class TranslationManagerServiceImpl extends } } - if (DEBUG) { - Slog.d(TAG, - (shouldInvokeCallbacks ? "" : "NOT ") - + "Invoking callbacks for translation state=" - + stateForCallbackInvocation + " for app with uid=" + translatedAppUid - + " packageName=" + packageName); - } - if (shouldInvokeCallbacks) { invokeCallbacks(stateForCallbackInvocation, sourceSpec, targetSpec, packageName, translatedAppUid); @@ -448,7 +437,7 @@ final class TranslationManagerServiceImpl extends pw.println(waitingFinishCallbackSize); for (IBinder activityToken : mWaitingFinishedCallbackActivities) { pw.print(prefix); - pw.print("activityToken: "); + pw.print("shareableActivityToken: "); pw.println(activityToken); } } @@ -458,7 +447,14 @@ final class TranslationManagerServiceImpl extends int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName, int translatedAppUid) { Bundle result = createResultForCallback(state, sourceSpec, targetSpec, packageName); - if (mCallbacks.getRegisteredCallbackCount() == 0) { + int registeredCallbackCount = mCallbacks.getRegisteredCallbackCount(); + if (DEBUG) { + Slog.d(TAG, "Invoking " + registeredCallbackCount + " callbacks for translation state=" + + state + " for app with uid=" + translatedAppUid + + " packageName=" + packageName); + } + + if (registeredCallbackCount == 0) { return; } List<InputMethodInfo> enabledInputMethods = getEnabledInputMethods(); @@ -521,8 +517,10 @@ final class TranslationManagerServiceImpl extends @GuardedBy("mLock") public void registerUiTranslationStateCallbackLocked(IRemoteCallback callback, int sourceUid) { mCallbacks.register(callback, sourceUid); - - if (mActiveTranslations.size() == 0) { + int numActiveTranslations = mActiveTranslations.size(); + Slog.i(TAG, "New registered callback for sourceUid=" + sourceUid + " with currently " + + numActiveTranslations + " active translations"); + if (numActiveTranslations == 0) { return; } |