Merge "Reland - Fix flicker when swiching resolution change" into tm-qpr-dev
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 41a784a..0126cb6 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -28,7 +28,7 @@
ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/packages/SystemUI/ktfmt_includes.txt ${PREUPLOAD_FILES}
-ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
owners_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "OWNERS$"
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index 7e814af..1403ba2 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -432,8 +432,9 @@
/**
* Callbacks that receives notifications for animation timing and frame commit timing.
+ * @hide
*/
- interface AnimationFrameCallback {
+ public interface AnimationFrameCallback {
/**
* Run animation based on the frame time.
* @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 61cca00..0c08735 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -257,6 +257,12 @@
private static final String KEY_LAUNCH_TASK_ID = "android.activity.launchTaskId";
/**
+ * See {@link #setDisableStartingWindow}.
+ * @hide
+ */
+ private static final String KEY_DISABLE_STARTING_WINDOW = "android.activity.disableStarting";
+
+ /**
* See {@link #setPendingIntentLaunchFlags(int)}
* @hide
*/
@@ -467,6 +473,7 @@
private PictureInPictureParams mLaunchIntoPipParams;
private boolean mDismissKeyguard;
private boolean mIgnorePendingIntentCreatorForegroundState;
+ private boolean mDisableStartingWindow;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -1272,6 +1279,7 @@
mDismissKeyguard = opts.getBoolean(KEY_DISMISS_KEYGUARD);
mIgnorePendingIntentCreatorForegroundState = opts.getBoolean(
KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE);
+ mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
}
/**
@@ -1671,6 +1679,22 @@
}
/**
+ * Sets whether recents disable showing starting window when activity launch.
+ * @hide
+ */
+ @RequiresPermission(START_TASKS_FROM_RECENTS)
+ public void setDisableStartingWindow(boolean disable) {
+ mDisableStartingWindow = disable;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getDisableStartingWindow() {
+ return mDisableStartingWindow;
+ }
+
+ /**
* Specifies intent flags to be applied for any activity started from a PendingIntent.
*
* @hide
@@ -2178,6 +2202,9 @@
b.putBoolean(KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE,
mIgnorePendingIntentCreatorForegroundState);
}
+ if (mDisableStartingWindow) {
+ b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
+ }
return b;
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index da6a551..edf96f7 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -159,6 +159,7 @@
void clearRequestedListenerHints(in INotificationListener token);
void requestHintsFromListener(in INotificationListener token, int hints);
int getHintsFromListener(in INotificationListener token);
+ int getHintsFromListenerNoToken();
void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
int getInterruptionFilterFromListener(in INotificationListener token);
void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f320b74..e837920 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -9591,21 +9591,16 @@
@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 @@
--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/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 392f52a..f6d27ad 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -66,9 +66,9 @@
/**
* Class to notify the user of events that happen. This is how you tell
- * the user that something has happened in the background. {@more}
+ * the user that something has happened in the background.
*
- * Notifications can take different forms:
+ * <p>Notifications can take different forms:
* <ul>
* <li>A persistent icon that goes in the status bar and is accessible
* through the launcher, (when the user selects it, a designated Intent
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index e022ca3..0ea53ce 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2080,6 +2080,21 @@
}
/**
+ * Set the live wallpaper for the given screen(s).
+ *
+ * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
+ * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
+ * another user's wallpaper.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
+ public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+ @SetWallpaperFlags int which) {
+ return setWallpaperComponent(name);
+ }
+
+ /**
* Set the display position of the current wallpaper within any larger space, when
* that wallpaper is visible behind the given window. The X and Y offsets
* are floating point numbers ranging from 0 to 1, representing where the
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 34c91c3..8e09939 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -12150,6 +12150,15 @@
* Attempts by the admin to grant these permissions, when the admin is restricted from doing
* so, will be silently ignored (no exception will be thrown).
*
+ * Control over the following permissions are restricted for managed profile owners:
+ * <ul>
+ * <li>Manifest.permission.READ_SMS</li>
+ * </ul>
+ * <p>
+ * A managed profile owner may not grant these permissions (i.e. call this method with any of
+ * the permissions listed above and {@code grantState} of
+ * {@code #PERMISSION_GRANT_STATE_GRANTED}), but may deny them.
+ *
* @param admin Which profile or device owner this request is associated with.
* @param packageName The application to grant or revoke a permission to.
* @param permission The permission to grant or revoke.
diff --git a/core/java/android/app/servertransaction/PendingTransactionActions.java b/core/java/android/app/servertransaction/PendingTransactionActions.java
index a47fe82..8174778 100644
--- a/core/java/android/app/servertransaction/PendingTransactionActions.java
+++ b/core/java/android/app/servertransaction/PendingTransactionActions.java
@@ -25,11 +25,12 @@
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 @@
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 @@
// 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);
+ } catch (RuntimeException runtimeException) {
+ // Collect the statistics about bundle
+ final String bundleStats = collectBundleStates();
- 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;
+ 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/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
index 79d7b21..3c66a15 100644
--- a/core/java/android/app/smartspace/SmartspaceTarget.java
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -245,6 +245,10 @@
public static final int UI_TEMPLATE_COMBINED_CARDS = 6;
// Sub-card template whose data is represented by {@link SubCardTemplateData}
public static final int UI_TEMPLATE_SUB_CARD = 7;
+ // Reserved: 8
+ // Template type used by non-UI template features for sending logging information in the
+ // base template data. This should not be used for UI template features.
+ // public static final int UI_TEMPLATE_LOGGING_ONLY = 8;
/**
* The types of the Smartspace ui templates.
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 0f6010f..2a47851 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -272,6 +272,9 @@
@Override
public void onServiceConnected(ComponentName component, IBinder binder) {
mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder);
+ if (mProxy == null) {
+ throw new IllegalStateException("Camera Proxy service is null");
+ }
try {
mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported();
} catch (RemoteException e) {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 06c35b5..3d5c34c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -962,7 +962,7 @@
* also be bumped.
*/
static final String[] USER_ACTIVITY_TYPES = {
- "other", "button", "touch", "accessibility", "attention"
+ "other", "button", "touch", "accessibility", "attention", "faceDown", "deviceState"
};
public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 13ca2c3..a46868e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -345,6 +345,39 @@
public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6;
/**
+ * @hide
+ */
+ @IntDef(prefix = { "USER_ACTIVITY_EVENT_" }, value = {
+ USER_ACTIVITY_EVENT_OTHER,
+ USER_ACTIVITY_EVENT_BUTTON,
+ USER_ACTIVITY_EVENT_TOUCH,
+ USER_ACTIVITY_EVENT_ACCESSIBILITY,
+ USER_ACTIVITY_EVENT_ATTENTION,
+ USER_ACTIVITY_EVENT_FACE_DOWN,
+ USER_ACTIVITY_EVENT_DEVICE_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserActivityEvent{}
+
+ /**
+ *
+ * Convert the user activity event to a string for debugging purposes.
+ * @hide
+ */
+ public static String userActivityEventToString(@UserActivityEvent int userActivityEvent) {
+ switch (userActivityEvent) {
+ case USER_ACTIVITY_EVENT_OTHER: return "other";
+ case USER_ACTIVITY_EVENT_BUTTON: return "button";
+ case USER_ACTIVITY_EVENT_TOUCH: return "touch";
+ case USER_ACTIVITY_EVENT_ACCESSIBILITY: return "accessibility";
+ case USER_ACTIVITY_EVENT_ATTENTION: return "attention";
+ case USER_ACTIVITY_EVENT_FACE_DOWN: return "faceDown";
+ case USER_ACTIVITY_EVENT_DEVICE_STATE: return "deviceState";
+ default: return Integer.toString(userActivityEvent);
+ }
+ }
+
+ /**
* User activity flag: If already dimmed, extend the dim timeout
* but do not brighten. This flag is useful for keeping the screen on
* a little longer without causing a visible change such as when
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 5ca0da2..f62cc87 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -335,4 +335,10 @@
/** Allows power button to intercept a power key button press. */
public abstract boolean interceptPowerKeyDown(KeyEvent event);
+
+ /**
+ * Internal version of {@link android.os.PowerManager#nap} which allows for napping while the
+ * device is not awake.
+ */
+ public abstract void nap(long eventTime, boolean allowWake);
}
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 0a6a405..16f9a12 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -115,6 +115,7 @@
private final int mMaxStreamVolume;
private boolean mAffectedByRingerMode;
private boolean mNotificationOrRing;
+ private final boolean mNotifAliasRing;
private final Receiver mReceiver = new Receiver();
private Handler mHandler;
@@ -179,6 +180,8 @@
if (mNotificationOrRing) {
mRingerMode = mAudioManager.getRingerModeInternal();
}
+ mNotifAliasRing = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_alias_ring_notif_stream_types);
mZenMode = mNotificationManager.getZenMode();
if (hasAudioProductStrategies()) {
@@ -280,7 +283,15 @@
if (zenMuted) {
mSeekBar.setProgress(mLastAudibleStreamVolume, true);
} else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
- mSeekBar.setProgress(0, true);
+ /**
+ * the first variable above is preserved and the conditions below are made explicit
+ * so that when user attempts to slide the notification seekbar out of vibrate the
+ * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased
+ */
+ if (mNotifAliasRing || mStreamType == AudioManager.STREAM_RING
+ || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
+ mSeekBar.setProgress(0, true);
+ }
} else if (mMuted) {
mSeekBar.setProgress(0, true);
} else {
@@ -354,6 +365,7 @@
// set the time of stop volume
if ((mStreamType == AudioManager.STREAM_VOICE_CALL
|| mStreamType == AudioManager.STREAM_RING
+ || (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION)
|| mStreamType == AudioManager.STREAM_ALARM)) {
sStopVolumeTime = java.lang.System.currentTimeMillis();
}
@@ -631,8 +643,8 @@
}
private void updateVolumeSlider(int streamType, int streamValue) {
- final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
- : (streamType == mStreamType);
+ final boolean streamMatch = mNotifAliasRing && mNotificationOrRing
+ ? isNotificationOrRing(streamType) : streamType == mStreamType;
if (mSeekBar != null && streamMatch && streamValue != -1) {
final boolean muted = mAudioManager.isStreamMute(mStreamType)
|| streamValue == 0;
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 6956cd4..295171c 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -29,16 +29,18 @@
*
* @param doze If true, starts the doze dream component if one has been configured,
* otherwise starts the user-specified dream.
+ * @param reason The reason to start dreaming, which is logged to help debugging.
*/
- public abstract void startDream(boolean doze);
+ public abstract void startDream(boolean doze, String reason);
/**
* Called by the power manager to stop a dream.
*
* @param immediate If true, ends the dream summarily, otherwise gives it some time
* to perform a proper exit transition.
+ * @param reason The reason to stop dreaming, which is logged to help debugging.
*/
- public abstract void stopDream(boolean immediate);
+ public abstract void stopDream(boolean immediate, String reason);
/**
* Called by the power manager to determine whether a dream is running.
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 4324442..aa45c20 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -42,8 +42,11 @@
private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
@Override
public void startDream(WindowManager.LayoutParams layoutParams,
- IDreamOverlayCallback callback) {
+ IDreamOverlayCallback callback, String dreamComponent,
+ boolean shouldShowComplications) {
mDreamOverlayCallback = callback;
+ mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
+ mShowComplications = shouldShowComplications;
onStartDream(layoutParams);
}
};
@@ -56,10 +59,6 @@
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
- mShowComplications = intent.getBooleanExtra(DreamService.EXTRA_SHOW_COMPLICATIONS,
- DreamService.DEFAULT_SHOW_COMPLICATIONS);
- mDreamComponent = intent.getParcelableExtra(DreamService.EXTRA_DREAM_COMPONENT,
- ComponentName.class);
return mDreamOverlay.asBinder();
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index d066ee7..3c1fef0 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -31,9 +31,9 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
@@ -68,6 +68,8 @@
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.ObservableServiceConnection;
+import com.android.internal.util.PersistentServiceConnection;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -75,7 +77,8 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
-import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -211,20 +214,8 @@
private static final String DREAM_META_DATA_ROOT_TAG = "dream";
/**
- * Extra containing a boolean for whether to show complications on the overlay.
- * @hide
- */
- public static final String EXTRA_SHOW_COMPLICATIONS =
- "android.service.dreams.SHOW_COMPLICATIONS";
-
- /**
- * Extra containing the component name for the active dream.
- * @hide
- */
- public static final String EXTRA_DREAM_COMPONENT = "android.service.dreams.DREAM_COMPONENT";
-
- /**
* The default value for whether to show complications on the overlay.
+ *
* @hide
*/
public static final boolean DEFAULT_SHOW_COMPLICATIONS = false;
@@ -248,80 +239,72 @@
private boolean mDebug = false;
+ private ComponentName mDreamComponent;
+ private boolean mShouldShowComplications;
+
private DreamServiceWrapper mDreamServiceWrapper;
private Runnable mDispatchAfterOnAttachedToWindow;
- private final OverlayConnection mOverlayConnection;
+ private OverlayConnection mOverlayConnection;
- private static class OverlayConnection implements ServiceConnection {
+ private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
// Overlay set during onBind.
private IDreamOverlay mOverlay;
- // A Queue of pending requests to execute on the overlay.
- private final ArrayDeque<Consumer<IDreamOverlay>> mRequests;
+ // A list of pending requests to execute on the overlay.
+ private final ArrayList<Consumer<IDreamOverlay>> mConsumers = new ArrayList<>();
- private boolean mBound;
-
- OverlayConnection() {
- mRequests = new ArrayDeque<>();
- }
-
- public void bind(Context context, @Nullable ComponentName overlayService,
- ComponentName dreamService) {
- if (overlayService == null) {
- return;
+ private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
+ @Override
+ public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
+ IDreamOverlay service) {
+ mOverlay = service;
+ for (Consumer<IDreamOverlay> consumer : mConsumers) {
+ consumer.accept(mOverlay);
+ }
}
- final ServiceInfo serviceInfo = fetchServiceInfo(context, dreamService);
-
- final Intent overlayIntent = new Intent();
- overlayIntent.setComponent(overlayService);
- overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS,
- fetchShouldShowComplications(context, serviceInfo));
- overlayIntent.putExtra(EXTRA_DREAM_COMPONENT, dreamService);
-
- context.bindService(overlayIntent,
- this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
- mBound = true;
- }
-
- public void unbind(Context context) {
- if (!mBound) {
- return;
+ @Override
+ public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
+ int reason) {
+ mOverlay = null;
}
+ };
- context.unbindService(this);
- mBound = false;
- }
-
- public void request(Consumer<IDreamOverlay> request) {
- mRequests.push(request);
- evaluate();
- }
-
- private void evaluate() {
- if (mOverlay == null) {
- return;
- }
-
- // Any new requests that arrive during this loop will be processed synchronously after
- // the loop exits.
- while (!mRequests.isEmpty()) {
- final Consumer<IDreamOverlay> request = mRequests.pop();
- request.accept(mOverlay);
- }
+ OverlayConnection(Context context,
+ Executor executor,
+ Handler handler,
+ ServiceTransformer<IDreamOverlay> transformer,
+ Intent serviceIntent,
+ int flags,
+ int minConnectionDurationMs,
+ int maxReconnectAttempts,
+ int baseReconnectDelayMs) {
+ super(context, executor, handler, transformer, serviceIntent, flags,
+ minConnectionDurationMs,
+ maxReconnectAttempts, baseReconnectDelayMs);
}
@Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- // Store Overlay and execute pending requests.
- mOverlay = IDreamOverlay.Stub.asInterface(service);
- evaluate();
+ public boolean bind() {
+ addCallback(mCallback);
+ return super.bind();
}
@Override
- public void onServiceDisconnected(ComponentName name) {
- // Clear Overlay binder to prevent further request processing.
- mOverlay = null;
+ public void unbind() {
+ removeCallback(mCallback);
+ super.unbind();
+ }
+
+ public void addConsumer(Consumer<IDreamOverlay> consumer) {
+ mConsumers.add(consumer);
+ if (mOverlay != null) {
+ consumer.accept(mOverlay);
+ }
+ }
+
+ public void removeConsumer(Consumer<IDreamOverlay> consumer) {
+ mConsumers.remove(consumer);
}
}
@@ -336,7 +319,6 @@
public DreamService() {
mDreamManager = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
- mOverlayConnection = new OverlayConnection();
}
/**
@@ -532,7 +514,7 @@
return mWindow;
}
- /**
+ /**
* Inflates a layout resource and set it to be the content view for this Dream.
* Behaves similarly to {@link android.app.Activity#setContentView(int)}.
*
@@ -955,6 +937,11 @@
@Override
public void onCreate() {
if (mDebug) Slog.v(mTag, "onCreate()");
+
+ mDreamComponent = new ComponentName(this, getClass());
+ mShouldShowComplications = fetchShouldShowComplications(this /*context*/,
+ fetchServiceInfo(this /*context*/, mDreamComponent));
+
super.onCreate();
}
@@ -996,13 +983,26 @@
public final IBinder onBind(Intent intent) {
if (mDebug) Slog.v(mTag, "onBind() intent = " + intent);
mDreamServiceWrapper = new DreamServiceWrapper();
+ final ComponentName overlayComponent = intent.getParcelableExtra(
+ EXTRA_DREAM_OVERLAY_COMPONENT, ComponentName.class);
// Connect to the overlay service if present.
- if (!mWindowless) {
- mOverlayConnection.bind(
+ if (!mWindowless && overlayComponent != null) {
+ final Resources resources = getResources();
+ final Intent overlayIntent = new Intent().setComponent(overlayComponent);
+
+ mOverlayConnection = new OverlayConnection(
/* context= */ this,
- intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT),
- new ComponentName(this, getClass()));
+ getMainExecutor(),
+ mHandler,
+ IDreamOverlay.Stub::asInterface,
+ overlayIntent,
+ /* flags= */ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ resources.getInteger(R.integer.config_minDreamOverlayDurationMs),
+ resources.getInteger(R.integer.config_dreamOverlayMaxReconnectAttempts),
+ resources.getInteger(R.integer.config_dreamOverlayReconnectTimeoutMs));
+
+ mOverlayConnection.bind();
}
return mDreamServiceWrapper;
@@ -1011,7 +1011,9 @@
@Override
public boolean onUnbind(Intent intent) {
// We must unbind from any overlay connection if we are unbound before finishing.
- mOverlayConnection.unbind(this);
+ if (mOverlayConnection != null) {
+ mOverlayConnection.unbind();
+ }
return super.onUnbind(intent);
}
@@ -1040,7 +1042,9 @@
}
mFinished = true;
- mOverlayConnection.unbind(this);
+ if (mOverlayConnection != null) {
+ mOverlayConnection.unbind();
+ }
if (mDreamToken == null) {
Slog.w(mTag, "Finish was called before the dream was attached.");
@@ -1337,19 +1341,26 @@
mWindow.getDecorView().addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
+ private Consumer<IDreamOverlay> mDreamStartOverlayConsumer;
+
@Override
public void onViewAttachedToWindow(View v) {
mDispatchAfterOnAttachedToWindow.run();
- // Request the DreamOverlay be told to dream with dream's window parameters
- // once the window has been attached.
- mOverlayConnection.request(overlay -> {
- try {
- overlay.startDream(mWindow.getAttributes(), mOverlayCallback);
- } catch (RemoteException e) {
- Log.e(mTag, "could not send window attributes:" + e);
- }
- });
+ if (mOverlayConnection != null) {
+ // Request the DreamOverlay be told to dream with dream's window
+ // parameters once the window has been attached.
+ mDreamStartOverlayConsumer = overlay -> {
+ try {
+ overlay.startDream(mWindow.getAttributes(), mOverlayCallback,
+ mDreamComponent.flattenToString(),
+ mShouldShowComplications);
+ } catch (RemoteException e) {
+ Log.e(mTag, "could not send window attributes:" + e);
+ }
+ };
+ mOverlayConnection.addConsumer(mDreamStartOverlayConsumer);
+ }
}
@Override
@@ -1362,6 +1373,9 @@
mActivity = null;
finish();
}
+ if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) {
+ mOverlayConnection.removeConsumer(mDreamStartOverlayConsumer);
+ }
}
});
}
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 2b6633d..05ebbfe 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -31,7 +31,11 @@
* @param params The {@link LayoutParams} for the associated DreamWindow, including the window
token of the Dream Activity.
* @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
- * dream.
+ * dream.
+ * @param dreamComponent The component name of the dream service requesting overlay.
+ * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
+ * and weather.
*/
- void startDream(in LayoutParams params, in IDreamOverlayCallback callback);
+ void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
+ in String dreamComponent, in boolean shouldShowComplications);
}
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index 206e4fa..e5ad85c 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -42,7 +42,8 @@
/** @hide */
@IntDef(prefix = { "DISMISSAL_SURFACE_" }, value = {
- DISMISSAL_NOT_DISMISSED, DISMISSAL_OTHER, DISMISSAL_PEEK, DISMISSAL_AOD, DISMISSAL_SHADE
+ DISMISSAL_NOT_DISMISSED, DISMISSAL_OTHER, DISMISSAL_PEEK, DISMISSAL_AOD,
+ DISMISSAL_SHADE, DISMISSAL_BUBBLE, DISMISSAL_LOCKSCREEN
})
@Retention(RetentionPolicy.SOURCE)
public @interface DismissalSurface {}
@@ -75,7 +76,12 @@
* Notification has been dismissed as a bubble.
* @hide
*/
- public static final int DISMISSAL_BUBBLE = 3;
+ public static final int DISMISSAL_BUBBLE = 4;
+ /**
+ * Notification has been dismissed from the lock screen.
+ * @hide
+ */
+ public static final int DISMISSAL_LOCKSCREEN = 5;
/** @hide */
@IntDef(prefix = { "DISMISS_SENTIMENT_" }, value = {
diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl
index 56e2486..f46c60f 100644
--- a/core/java/android/service/wallpaper/IWallpaperService.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperService.aidl
@@ -25,6 +25,6 @@
oneway interface IWallpaperService {
void attach(IWallpaperConnection connection,
IBinder windowToken, int windowType, boolean isPreview,
- int reqWidth, int reqHeight, in Rect padding, int displayId);
+ int reqWidth, int reqHeight, in Rect padding, int displayId, int which);
void detach();
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 01749c0..e5792a9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -18,6 +18,7 @@
import static android.app.WallpaperManager.COMMAND_FREEZE;
import static android.app.WallpaperManager.COMMAND_UNFREEZE;
+import static android.app.WallpaperManager.SetWallpaperFlags;
import static android.graphics.Matrix.MSCALE_X;
import static android.graphics.Matrix.MSCALE_Y;
import static android.graphics.Matrix.MSKEW_X;
@@ -2427,7 +2428,7 @@
@Override
public void attach(IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
- int displayId) {
+ int displayId, @SetWallpaperFlags int which) {
mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
windowType, isPreview, reqWidth, reqHeight, padding, displayId);
}
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 44f419a..e407707 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -241,6 +241,8 @@
*/
public boolean willShowImeOnTarget;
+ public int rotationChange;
+
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
@@ -302,6 +304,7 @@
backgroundColor = in.readInt();
showBackdrop = in.readBoolean();
willShowImeOnTarget = in.readBoolean();
+ rotationChange = in.readInt();
}
public void setShowBackdrop(boolean shouldShowBackdrop) {
@@ -316,6 +319,14 @@
return willShowImeOnTarget;
}
+ public void setRotationChange(int rotationChange) {
+ this.rotationChange = rotationChange;
+ }
+
+ public int getRotationChange() {
+ return rotationChange;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -345,6 +356,7 @@
dest.writeInt(backgroundColor);
dest.writeBoolean(showBackdrop);
dest.writeBoolean(willShowImeOnTarget);
+ dest.writeInt(rotationChange);
}
public void dump(PrintWriter pw, String prefix) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 91f36bf..8f4a836 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -284,7 +284,7 @@
* @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
@@ -1102,7 +1102,7 @@
// Update the last resource config in case the resource configuration was changed while
// activity relaunched.
- mLastConfigurationFromResources.setTo(getConfiguration());
+ updateLastConfigurationFromResources(getConfiguration());
}
private Configuration getConfiguration() {
@@ -5396,13 +5396,7 @@
// Update the display with new DisplayAdjustments.
updateInternalDisplay(mDisplay.getDisplayId(), localResources);
- final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection();
- final int currentLayoutDirection = config.getLayoutDirection();
- mLastConfigurationFromResources.setTo(config);
- if (lastLayoutDirection != currentLayoutDirection
- && mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
- mView.setLayoutDirection(currentLayoutDirection);
- }
+ updateLastConfigurationFromResources(config);
mView.dispatchConfigurationChanged(config);
// We could have gotten this {@link Configuration} update after we called
@@ -5416,6 +5410,17 @@
updateForceDarkMode();
}
+ private void updateLastConfigurationFromResources(Configuration resConfig) {
+ final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection();
+ final int currentLayoutDirection = resConfig.getLayoutDirection();
+ mLastConfigurationFromResources.setTo(resConfig);
+ // Update layout direction in case the language or screen layout is changed.
+ if (lastLayoutDirection != currentLayoutDirection && mView != null
+ && mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
+ mView.setLayoutDirection(currentLayoutDirection);
+ }
+ }
+
/**
* Return true if child is an ancestor of parent, (or equal to the parent).
*/
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 218ca58..2f77901 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -777,12 +777,6 @@
int TAKE_SCREENSHOT_FULLSCREEN = 1;
/**
- * Invoke screenshot flow allowing the user to select a region.
- * @hide
- */
- int TAKE_SCREENSHOT_SELECTED_REGION = 2;
-
- /**
* Invoke screenshot flow with an image provided by the caller.
* @hide
*/
@@ -794,7 +788,6 @@
* @hide
*/
@IntDef({TAKE_SCREENSHOT_FULLSCREEN,
- TAKE_SCREENSHOT_SELECTED_REGION,
TAKE_SCREENSHOT_PROVIDED_IMAGE})
@interface ScreenshotType {}
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 6bf2474..514df59 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -175,10 +175,7 @@
*/
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/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index 64b2638..841354a 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -33,19 +33,19 @@
private final int mDisplayId;
- private final boolean mVisibleRequested;
+ private final boolean mVisible;
public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
- boolean visibleRequested) {
+ boolean visible) {
mConfiguration.setTo(configuration);
mDisplayId = displayId;
- mVisibleRequested = visibleRequested;
+ mVisible = visible;
}
public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.mDisplayId;
- mVisibleRequested = info.mVisibleRequested;
+ mVisible = info.mVisible;
}
/** The {@link Configuration} of the parent Task */
@@ -62,9 +62,9 @@
return mDisplayId;
}
- /** Whether the parent Task is requested to be visible or not */
- public boolean isVisibleRequested() {
- return mVisibleRequested;
+ /** Whether the parent Task is visible or not */
+ public boolean isVisible() {
+ return mVisible;
}
/**
@@ -80,7 +80,7 @@
return false;
}
return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId
- && mVisibleRequested == that.mVisibleRequested;
+ && mVisible == that.mVisible;
}
@WindowConfiguration.WindowingMode
@@ -93,7 +93,7 @@
return TaskFragmentParentInfo.class.getSimpleName() + ":{"
+ "config=" + mConfiguration
+ ", displayId=" + mDisplayId
- + ", visibleRequested=" + mVisibleRequested
+ + ", visible=" + mVisible
+ "}";
}
@@ -114,14 +114,14 @@
final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj;
return mConfiguration.equals(that.mConfiguration)
&& mDisplayId == that.mDisplayId
- && mVisibleRequested == that.mVisibleRequested;
+ && mVisible == that.mVisible;
}
@Override
public int hashCode() {
int result = mConfiguration.hashCode();
result = 31 * result + mDisplayId;
- result = 31 * result + (mVisibleRequested ? 1 : 0);
+ result = 31 * result + (mVisible ? 1 : 0);
return result;
}
@@ -129,13 +129,13 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
mConfiguration.writeToParcel(dest, flags);
dest.writeInt(mDisplayId);
- dest.writeBoolean(mVisibleRequested);
+ dest.writeBoolean(mVisible);
}
private TaskFragmentParentInfo(Parcel in) {
mConfiguration.readFromParcel(in);
mDisplayId = in.readInt();
- mVisibleRequested = in.readBoolean();
+ mVisible = in.readBoolean();
}
public static final Creator<TaskFragmentParentInfo> CREATOR =
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 641d1a1..8815ab3 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -132,8 +132,14 @@
*/
public static final int FLAG_IS_BEHIND_STARTING_WINDOW = 1 << 14;
+ /** 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 << 15;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 17;
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
@@ -153,6 +159,8 @@
FLAG_CROSS_PROFILE_OWNER_THUMBNAIL,
FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
FLAG_IS_BEHIND_STARTING_WINDOW,
+ FLAG_IS_OCCLUDED,
+ FLAG_IS_SYSTEM_WINDOW,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -362,6 +370,12 @@
if ((flags & FLAG_IS_BEHIND_STARTING_WINDOW) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("IS_BEHIND_STARTING_WINDOW");
}
+ 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");
}
@@ -400,6 +414,7 @@
public static final class Change implements Parcelable {
private final WindowContainerToken mContainer;
private WindowContainerToken mParent;
+ private WindowContainerToken mLastParent;
private final SurfaceControl mLeash;
private @TransitionMode int mMode = TRANSIT_NONE;
private @ChangeFlags int mFlags = FLAG_NONE;
@@ -428,6 +443,7 @@
private Change(Parcel in) {
mContainer = in.readTypedObject(WindowContainerToken.CREATOR);
mParent = in.readTypedObject(WindowContainerToken.CREATOR);
+ mLastParent = in.readTypedObject(WindowContainerToken.CREATOR);
mLeash = new SurfaceControl();
mLeash.readFromParcel(in);
mMode = in.readInt();
@@ -451,6 +467,14 @@
mParent = parent;
}
+ /**
+ * Sets the parent of this change's container before the transition if this change's
+ * container is reparented in the transition.
+ */
+ public void setLastParent(@Nullable WindowContainerToken lastParent) {
+ mLastParent = lastParent;
+ }
+
/** Sets the transition mode for this change */
public void setMode(@TransitionMode int mode) {
mMode = mode;
@@ -534,6 +558,17 @@
return mParent;
}
+ /**
+ * @return the parent of the changing container before the transition if it is reparented
+ * in the transition. The parent window may not be collected in the transition as a
+ * participant, and it may have been detached from the display. {@code null} if the changing
+ * container has not been reparented in the transition, or if the parent is not organizable.
+ */
+ @Nullable
+ public WindowContainerToken getLastParent() {
+ return mLastParent;
+ }
+
/** @return which action this change represents. */
public @TransitionMode int getMode() {
return mMode;
@@ -633,6 +668,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeTypedObject(mContainer, flags);
dest.writeTypedObject(mParent, flags);
+ dest.writeTypedObject(mLastParent, flags);
mLeash.writeToParcel(dest, flags);
dest.writeInt(mMode);
dest.writeInt(mFlags);
@@ -672,13 +708,37 @@
@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;
- 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 4f74ca7..2ae2c09 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.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 @@
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 @@
mListViewDataChanged = false;
}
-
private void createPlaceHolders() {
mNumShortcutResults = 0;
mServiceTargets.clear();
@@ -268,12 +269,16 @@
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 @@
}
}
+ 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 @@
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 @@
} 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 @@
* 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 @@
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 f6075b0..4a1f7eb 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -870,7 +870,12 @@
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 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/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 5f4a9cd..473134e 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -172,14 +172,14 @@
@Override
public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
- prepareIntentForCrossProfileLaunch(mResolvedIntent, userId);
+ TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, userId);
activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
return true;
}
@Override
public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
- prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier());
+ TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier());
activity.startActivityAsUser(mResolvedIntent, options, user);
return false;
}
@@ -224,13 +224,6 @@
}
};
- private static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) {
- final int currentUserId = UserHandle.myUserId();
- if (targetUserId != currentUserId) {
- intent.fixUris(currentUserId);
- }
- }
-
private DisplayResolveInfo(Parcel in) {
mDisplayLabel = in.readCharSequence();
mExtendedInfo = in.readCharSequence();
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index 264e4f7..d7f3a76 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.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 @@
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 @@
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 @@
}
}
}
- // 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 @@
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 @@
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;
@@ -232,6 +259,7 @@
}
intent.setComponent(mChooserTarget.getComponentName());
intent.putExtras(mChooserTarget.getIntentExtras());
+ TargetInfo.prepareIntentForCrossProfileLaunch(intent, userId);
// Important: we will ignore the target security checks in ActivityManager
// if and only if the ChooserTarget's target package is the same package
@@ -270,10 +298,17 @@
}
@Override
- public Drawable getDisplayIcon(Context context) {
+ public synchronized Drawable getDisplayIcon(Context context) {
return mDisplayIcon;
}
+ /**
+ * @return true if display icon is available
+ */
+ public synchronized boolean hasDisplayIcon() {
+ return mDisplayIcon != null;
+ }
+
public ChooserTarget getChooserTarget() {
return mChooserTarget;
}
diff --git a/core/java/com/android/internal/app/chooser/TargetInfo.java b/core/java/com/android/internal/app/chooser/TargetInfo.java
index f56ab17..7bb7ddc 100644
--- a/core/java/com/android/internal/app/chooser/TargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/TargetInfo.java
@@ -130,4 +130,15 @@
* @return true if this target should be pinned to the front by the request of the user
*/
boolean isPinned();
+
+ /**
+ * Fix the URIs in {@code intent} if cross-profile sharing is required. This should be called
+ * before launching the intent as another user.
+ */
+ static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) {
+ final int currentUserId = UserHandle.myUserId();
+ if (targetUserId != currentUserId) {
+ intent.fixUris(currentUserId);
+ }
+ }
}
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 627631a..d503904 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -115,7 +115,7 @@
Slog.i(TAG, "Setting initial brightness to default value of: " + defaultBrightness);
}
- mBrightnessSyncObserver.startObserving();
+ mBrightnessSyncObserver.startObserving(mHandler);
mHandler.sendEmptyMessageAtTime(MSG_RUN_UPDATE, mClock.uptimeMillis());
}
@@ -482,30 +482,31 @@
}
};
- private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (selfChange) {
- return;
+ private ContentObserver createBrightnessContentObserver(Handler handler) {
+ return new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (selfChange) {
+ return;
+ }
+ if (BRIGHTNESS_URI.equals(uri)) {
+ handleBrightnessChangeInt(getScreenBrightnessInt());
+ }
}
- if (BRIGHTNESS_URI.equals(uri)) {
- handleBrightnessChangeInt(getScreenBrightnessInt());
- }
- }
- };
+ };
+ }
boolean isObserving() {
return mIsObserving;
}
- void startObserving() {
+ void startObserving(Handler handler) {
final ContentResolver cr = mContext.getContentResolver();
- cr.registerContentObserver(BRIGHTNESS_URI, false, mContentObserver,
- UserHandle.USER_ALL);
- mDisplayManager.registerDisplayListener(mListener, mHandler,
+ cr.registerContentObserver(BRIGHTNESS_URI, false,
+ createBrightnessContentObserver(handler), UserHandle.USER_ALL);
+ mDisplayManager.registerDisplayListener(mListener, handler,
DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
mIsObserving = true;
}
-
}
}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
new file mode 100644
index 0000000..d4fe7c8d
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.dynamicanimation.animation;
+
+import android.animation.AnimationHandler;
+import android.animation.ValueAnimator;
+import android.annotation.FloatRange;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.os.Looper;
+import android.util.AndroidRuntimeException;
+import android.util.FloatProperty;
+import android.view.View;
+
+import java.util.ArrayList;
+
+/**
+ * This class is the base class of physics-based animations. It manages the animation's
+ * lifecycle such as {@link #start()} and {@link #cancel()}. This base class also handles the common
+ * setup for all the subclass animations. For example, DynamicAnimation supports adding
+ * {@link OnAnimationEndListener} and {@link OnAnimationUpdateListener} so that the important
+ * animation events can be observed through the callbacks. The start conditions for any subclass of
+ * DynamicAnimation can be set using {@link #setStartValue(float)} and
+ * {@link #setStartVelocity(float)}.
+ *
+ * @param <T> subclass of DynamicAnimation
+ */
+public abstract class DynamicAnimation<T extends DynamicAnimation<T>>
+ implements AnimationHandler.AnimationFrameCallback {
+
+ /**
+ * ViewProperty holds the access of a property of a {@link View}. When an animation is
+ * created with a {@link ViewProperty} instance, the corresponding property value of the view
+ * will be updated through this ViewProperty instance.
+ */
+ public abstract static class ViewProperty extends FloatProperty<View> {
+ private ViewProperty(String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * View's translationX property.
+ */
+ public static final ViewProperty TRANSLATION_X = new ViewProperty("translationX") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setTranslationX(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getTranslationX();
+ }
+ };
+
+ /**
+ * View's translationY property.
+ */
+ public static final ViewProperty TRANSLATION_Y = new ViewProperty("translationY") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setTranslationY(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getTranslationY();
+ }
+ };
+
+ /**
+ * View's translationZ property.
+ */
+ public static final ViewProperty TRANSLATION_Z = new ViewProperty("translationZ") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setTranslationZ(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getTranslationZ();
+ }
+ };
+
+ /**
+ * View's scaleX property.
+ */
+ public static final ViewProperty SCALE_X = new ViewProperty("scaleX") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setScaleX(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getScaleX();
+ }
+ };
+
+ /**
+ * View's scaleY property.
+ */
+ public static final ViewProperty SCALE_Y = new ViewProperty("scaleY") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setScaleY(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getScaleY();
+ }
+ };
+
+ /**
+ * View's rotation property.
+ */
+ public static final ViewProperty ROTATION = new ViewProperty("rotation") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setRotation(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getRotation();
+ }
+ };
+
+ /**
+ * View's rotationX property.
+ */
+ public static final ViewProperty ROTATION_X = new ViewProperty("rotationX") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setRotationX(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getRotationX();
+ }
+ };
+
+ /**
+ * View's rotationY property.
+ */
+ public static final ViewProperty ROTATION_Y = new ViewProperty("rotationY") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setRotationY(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getRotationY();
+ }
+ };
+
+ /**
+ * View's x property.
+ */
+ public static final ViewProperty X = new ViewProperty("x") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setX(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getX();
+ }
+ };
+
+ /**
+ * View's y property.
+ */
+ public static final ViewProperty Y = new ViewProperty("y") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setY(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getY();
+ }
+ };
+
+ /**
+ * View's z property.
+ */
+ public static final ViewProperty Z = new ViewProperty("z") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setZ(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getZ();
+ }
+ };
+
+ /**
+ * View's alpha property.
+ */
+ public static final ViewProperty ALPHA = new ViewProperty("alpha") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setAlpha(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getAlpha();
+ }
+ };
+
+ // Properties below are not RenderThread compatible
+ /**
+ * View's scrollX property.
+ */
+ public static final ViewProperty SCROLL_X = new ViewProperty("scrollX") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setScrollX((int) value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return (float) view.getScrollX();
+ }
+ };
+
+ /**
+ * View's scrollY property.
+ */
+ public static final ViewProperty SCROLL_Y = new ViewProperty("scrollY") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setScrollY((int) value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return (float) view.getScrollY();
+ }
+ };
+
+ /**
+ * The minimum visible change in pixels that can be visible to users.
+ */
+ @SuppressLint("MinMaxConstant")
+ public static final float MIN_VISIBLE_CHANGE_PIXELS = 1f;
+ /**
+ * The minimum visible change in degrees that can be visible to users.
+ */
+ @SuppressLint("MinMaxConstant")
+ public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES = 1f / 10f;
+ /**
+ * The minimum visible change in alpha that can be visible to users.
+ */
+ @SuppressLint("MinMaxConstant")
+ public static final float MIN_VISIBLE_CHANGE_ALPHA = 1f / 256f;
+ /**
+ * The minimum visible change in scale that can be visible to users.
+ */
+ @SuppressLint("MinMaxConstant")
+ public static final float MIN_VISIBLE_CHANGE_SCALE = 1f / 500f;
+
+ // Use the max value of float to indicate an unset state.
+ private static final float UNSET = Float.MAX_VALUE;
+
+ // Multiplier to the min visible change value for value threshold
+ private static final float THRESHOLD_MULTIPLIER = 0.75f;
+
+ // Internal tracking for velocity.
+ float mVelocity = 0;
+
+ // Internal tracking for value.
+ float mValue = UNSET;
+
+ // Tracks whether start value is set. If not, the animation will obtain the value at the time
+ // of starting through the getter and use that as the starting value of the animation.
+ boolean mStartValueIsSet = false;
+
+ // Target to be animated.
+ final Object mTarget;
+
+ // View property id.
+ final FloatProperty mProperty;
+
+ // Package private tracking of animation lifecycle state. Visible to subclass animations.
+ boolean mRunning = false;
+
+ // Min and max values that defines the range of the animation values.
+ float mMaxValue = Float.MAX_VALUE;
+ float mMinValue = -mMaxValue;
+
+ // Last frame time. Always gets reset to -1 at the end of the animation.
+ private long mLastFrameTime = 0;
+
+ private float mMinVisibleChange;
+
+ // List of end listeners
+ private final ArrayList<OnAnimationEndListener> mEndListeners = new ArrayList<>();
+
+ // List of update listeners
+ private final ArrayList<OnAnimationUpdateListener> mUpdateListeners = new ArrayList<>();
+
+ // Animation handler used to schedule updates for this animation.
+ private AnimationHandler mAnimationHandler;
+
+ // Internal state for value/velocity pair.
+ static class MassState {
+ float mValue;
+ float mVelocity;
+ }
+
+ /**
+ * Creates a dynamic animation with the given FloatValueHolder instance.
+ *
+ * @param floatValueHolder the FloatValueHolder instance to be animated.
+ */
+ DynamicAnimation(final FloatValueHolder floatValueHolder) {
+ mTarget = null;
+ mProperty = new FloatProperty("FloatValueHolder") {
+ @Override
+ public Float get(Object object) {
+ return floatValueHolder.getValue();
+ }
+
+ @Override
+ public void setValue(Object object, float value) {
+ floatValueHolder.setValue(value);
+ }
+ };
+ mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
+ }
+
+ /**
+ * Creates a dynamic animation to animate the given property for the given {@link View}
+ *
+ * @param object the Object whose property is to be animated
+ * @param property the property to be animated
+ */
+
+ <K> DynamicAnimation(K object, FloatProperty<K> property) {
+ mTarget = object;
+ mProperty = property;
+ if (mProperty == ROTATION || mProperty == ROTATION_X
+ || mProperty == ROTATION_Y) {
+ mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES;
+ } else if (mProperty == ALPHA) {
+ mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA;
+ } else if (mProperty == SCALE_X || mProperty == SCALE_Y) {
+ mMinVisibleChange = MIN_VISIBLE_CHANGE_SCALE;
+ } else {
+ mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
+ }
+ }
+
+ /**
+ * Sets the start value of the animation. If start value is not set, the animation will get
+ * the current value for the view's property, and use that as the start value.
+ *
+ * @param startValue start value for the animation
+ * @return the Animation whose start value is being set
+ */
+ @SuppressWarnings("unchecked")
+ public T setStartValue(float startValue) {
+ mValue = startValue;
+ mStartValueIsSet = true;
+ return (T) this;
+ }
+
+ /**
+ * Start velocity of the animation. Default velocity is 0. Unit: change in property per
+ * second (e.g. pixels per second, scale/alpha value change per second).
+ *
+ * <p>Note when using a fixed value as the start velocity (as opposed to getting the velocity
+ * through touch events), it is recommended to define such a value in dp/second and convert it
+ * to pixel/second based on the density of the screen to achieve a consistent look across
+ * different screens.
+ *
+ * <p>To convert from dp/second to pixel/second:
+ * <pre class="prettyprint">
+ * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond,
+ * getResources().getDisplayMetrics());
+ * </pre>
+ *
+ * @param startVelocity start velocity of the animation
+ * @return the Animation whose start velocity is being set
+ */
+ @SuppressWarnings("unchecked")
+ public T setStartVelocity(float startVelocity) {
+ mVelocity = startVelocity;
+ return (T) this;
+ }
+
+ /**
+ * Sets the max value of the animation. Animations will not animate beyond their max value.
+ * Whether or not animation will come to an end when max value is reached is dependent on the
+ * child animation's implementation.
+ *
+ * @param max maximum value of the property to be animated
+ * @return the Animation whose max value is being set
+ */
+ @SuppressWarnings("unchecked")
+ public T setMaxValue(float max) {
+ // This max value should be checked and handled in the subclass animations, instead of
+ // assuming the end of the animations when the max/min value is hit in the base class.
+ // The reason is that hitting max/min value may just be a transient state, such as during
+ // the spring oscillation.
+ mMaxValue = max;
+ return (T) this;
+ }
+
+ /**
+ * Sets the min value of the animation. Animations will not animate beyond their min value.
+ * Whether or not animation will come to an end when min value is reached is dependent on the
+ * child animation's implementation.
+ *
+ * @param min minimum value of the property to be animated
+ * @return the Animation whose min value is being set
+ */
+ @SuppressWarnings("unchecked")
+ public T setMinValue(float min) {
+ mMinValue = min;
+ return (T) this;
+ }
+
+ /**
+ * Adds an end listener to the animation for receiving onAnimationEnd callbacks. If the listener
+ * is {@code null} or has already been added to the list of listeners for the animation, no op.
+ *
+ * @param listener the listener to be added
+ * @return the animation to which the listener is added
+ */
+ @SuppressWarnings("unchecked")
+ public T addEndListener(OnAnimationEndListener listener) {
+ if (!mEndListeners.contains(listener)) {
+ mEndListeners.add(listener);
+ }
+ return (T) this;
+ }
+
+ /**
+ * Removes the end listener from the animation, so as to stop receiving animation end callbacks.
+ *
+ * @param listener the listener to be removed
+ */
+ public void removeEndListener(OnAnimationEndListener listener) {
+ removeEntry(mEndListeners, listener);
+ }
+
+ /**
+ * Adds an update listener to the animation for receiving per-frame animation update callbacks.
+ * If the listener is {@code null} or has already been added to the list of listeners for the
+ * animation, no op.
+ *
+ * <p>Note that update listener should only be added before the start of the animation.
+ *
+ * @param listener the listener to be added
+ * @return the animation to which the listener is added
+ * @throws UnsupportedOperationException if the update listener is added after the animation has
+ * started
+ */
+ @SuppressWarnings("unchecked")
+ public T addUpdateListener(OnAnimationUpdateListener listener) {
+ if (isRunning()) {
+ // Require update listener to be added before the animation, such as when we start
+ // the animation, we know whether the animation is RenderThread compatible.
+ throw new UnsupportedOperationException("Error: Update listeners must be added before"
+ + "the animation.");
+ }
+ if (!mUpdateListeners.contains(listener)) {
+ mUpdateListeners.add(listener);
+ }
+ return (T) this;
+ }
+
+ /**
+ * Removes the update listener from the animation, so as to stop receiving animation update
+ * callbacks.
+ *
+ * @param listener the listener to be removed
+ */
+ public void removeUpdateListener(OnAnimationUpdateListener listener) {
+ removeEntry(mUpdateListeners, listener);
+ }
+
+
+ /**
+ * This method sets the minimal change of animation value that is visible to users, which helps
+ * determine a reasonable threshold for the animation's termination condition. It is critical
+ * to set the minimal visible change for custom properties (i.e. non-<code>ViewProperty</code>s)
+ * unless the custom property is in pixels.
+ *
+ * <p>For custom properties, this minimum visible change defaults to change in pixel
+ * (i.e. {@link #MIN_VISIBLE_CHANGE_PIXELS}. It is recommended to adjust this value that is
+ * reasonable for the property to be animated. A general rule of thumb to calculate such a value
+ * is: minimum visible change = range of custom property value / corresponding pixel range. For
+ * example, if the property to be animated is a progress (from 0 to 100) that corresponds to a
+ * 200-pixel change. Then the min visible change should be 100 / 200. (i.e. 0.5).
+ *
+ * <p>It's not necessary to call this method when animating {@link ViewProperty}s, as the
+ * minimum visible change will be derived from the property. For example, if the property to be
+ * animated is in pixels (i.e. {@link #TRANSLATION_X}, {@link #TRANSLATION_Y},
+ * {@link #TRANSLATION_Z}, @{@link #SCROLL_X} or {@link #SCROLL_Y}), the default minimum visible
+ * change is 1 (pixel). For {@link #ROTATION}, {@link #ROTATION_X} or {@link #ROTATION_Y}, the
+ * animation will use {@link #MIN_VISIBLE_CHANGE_ROTATION_DEGREES} as the min visible change,
+ * which is 1/10. Similarly, the minimum visible change for alpha (
+ * i.e. {@link #MIN_VISIBLE_CHANGE_ALPHA} is defined as 1 / 256.
+ *
+ * @param minimumVisibleChange minimum change in property value that is visible to users
+ * @return the animation whose min visible change is being set
+ * @throws IllegalArgumentException if the given threshold is not positive
+ */
+ @SuppressWarnings("unchecked")
+ public T setMinimumVisibleChange(@FloatRange(from = 0.0, fromInclusive = false)
+ float minimumVisibleChange) {
+ if (minimumVisibleChange <= 0) {
+ throw new IllegalArgumentException("Minimum visible change must be positive.");
+ }
+ mMinVisibleChange = minimumVisibleChange;
+ setValueThreshold(minimumVisibleChange * THRESHOLD_MULTIPLIER);
+ return (T) this;
+ }
+
+ /**
+ * Returns the minimum change in the animation property that could be visibly different to
+ * users.
+ *
+ * @return minimum change in property value that is visible to users
+ */
+ public float getMinimumVisibleChange() {
+ return mMinVisibleChange;
+ }
+
+ /**
+ * Remove {@code null} entries from the list.
+ */
+ private static <T> void removeNullEntries(ArrayList<T> list) {
+ // Clean up null entries
+ for (int i = list.size() - 1; i >= 0; i--) {
+ if (list.get(i) == null) {
+ list.remove(i);
+ }
+ }
+ }
+
+ /**
+ * Remove an entry from the list by marking it {@code null} and clean up later.
+ */
+ private static <T> void removeEntry(ArrayList<T> list, T entry) {
+ int id = list.indexOf(entry);
+ if (id >= 0) {
+ list.set(id, null);
+ }
+ }
+
+ /****************Animation Lifecycle Management***************/
+
+ /**
+ * Starts an animation. If the animation has already been started, no op. Note that calling
+ * {@link #start()} will not immediately set the property value to start value of the animation.
+ * The property values will be changed at each animation pulse, which happens before the draw
+ * pass. As a result, the changes will be reflected in the next frame, the same as if the values
+ * were set immediately. This method should only be called on main thread.
+ *
+ * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
+ * is created on the same thread as the first call to start/cancel an animation. All the
+ * subsequent animation lifecycle manipulations need to be on that same thread, until the
+ * AnimationHandler is reset (using [setAnimationHandler]).
+ *
+ * @throws AndroidRuntimeException if this method is not called on the same thread as the
+ * animation handler
+ */
+ @MainThread
+ public void start() {
+ if (!isCurrentThread()) {
+ throw new AndroidRuntimeException("Animations may only be started on the same thread "
+ + "as the animation handler");
+ }
+ if (!mRunning) {
+ startAnimationInternal();
+ }
+ }
+
+ boolean isCurrentThread() {
+ return Thread.currentThread() == Looper.myLooper().getThread();
+ }
+
+ /**
+ * Cancels the on-going animation. If the animation hasn't started, no op.
+ *
+ * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
+ * is created on the same thread as the first call to start/cancel an animation. All the
+ * subsequent animation lifecycle manipulations need to be on that same thread, until the
+ * AnimationHandler is reset (using [setAnimationHandler]).
+ *
+ * @throws AndroidRuntimeException if this method is not called on the same thread as the
+ * animation handler
+ */
+ @MainThread
+ public void cancel() {
+ if (!isCurrentThread()) {
+ throw new AndroidRuntimeException("Animations may only be canceled from the same "
+ + "thread as the animation handler");
+ }
+ if (mRunning) {
+ endAnimationInternal(true);
+ }
+ }
+
+ /**
+ * Returns whether the animation is currently running.
+ *
+ * @return {@code true} if the animation is currently running, {@code false} otherwise
+ */
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ /************************** Private APIs below ********************************/
+
+ // This gets called when the animation is started, to finish the setup of the animation
+ // before the animation pulsing starts.
+ private void startAnimationInternal() {
+ if (!mRunning) {
+ mRunning = true;
+ if (!mStartValueIsSet) {
+ mValue = getPropertyValue();
+ }
+ // Sanity check:
+ if (mValue > mMaxValue || mValue < mMinValue) {
+ throw new IllegalArgumentException("Starting value need to be in between min"
+ + " value and max value");
+ }
+ getAnimationHandler().addAnimationFrameCallback(this, 0);
+ }
+ }
+
+ /**
+ * This gets call on each frame of the animation. Animation value and velocity are updated
+ * in this method based on the new frame time. The property value of the view being animated
+ * is then updated. The animation's ending conditions are also checked in this method. Once
+ * the animation reaches equilibrium, the animation will come to its end, and end listeners
+ * will be notified, if any.
+ */
+ @Override
+ public boolean doAnimationFrame(long frameTime) {
+ if (mLastFrameTime == 0) {
+ // First frame.
+ mLastFrameTime = frameTime;
+ setPropertyValue(mValue);
+ return false;
+ }
+ long deltaT = frameTime - mLastFrameTime;
+ mLastFrameTime = frameTime;
+ float durationScale = ValueAnimator.getDurationScale();
+ deltaT = durationScale == 0.0f ? Integer.MAX_VALUE : (long) (deltaT / durationScale);
+ boolean finished = updateValueAndVelocity(deltaT);
+ // Clamp value & velocity.
+ mValue = Math.min(mValue, mMaxValue);
+ mValue = Math.max(mValue, mMinValue);
+
+ setPropertyValue(mValue);
+
+ if (finished) {
+ endAnimationInternal(false);
+ }
+ return finished;
+ }
+
+ @Override
+ public void commitAnimationFrame(long frameTime) {
+ doAnimationFrame(frameTime);
+ }
+
+ /**
+ * Updates the animation state (i.e. value and velocity). This method is package private, so
+ * subclasses can override this method to calculate the new value and velocity in their custom
+ * way.
+ *
+ * @param deltaT time elapsed in millisecond since last frame
+ * @return whether the animation has finished
+ */
+ abstract boolean updateValueAndVelocity(long deltaT);
+
+ /**
+ * Internal method to reset the animation states when animation is finished/canceled.
+ */
+ private void endAnimationInternal(boolean canceled) {
+ mRunning = false;
+ getAnimationHandler().removeCallback(this);
+ mLastFrameTime = 0;
+ mStartValueIsSet = false;
+ for (int i = 0; i < mEndListeners.size(); i++) {
+ if (mEndListeners.get(i) != null) {
+ mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity);
+ }
+ }
+ removeNullEntries(mEndListeners);
+ }
+
+ /**
+ * Updates the property value through the corresponding setter.
+ */
+ @SuppressWarnings("unchecked")
+ void setPropertyValue(float value) {
+ mProperty.setValue(mTarget, value);
+ for (int i = 0; i < mUpdateListeners.size(); i++) {
+ if (mUpdateListeners.get(i) != null) {
+ mUpdateListeners.get(i).onAnimationUpdate(this, mValue, mVelocity);
+ }
+ }
+ removeNullEntries(mUpdateListeners);
+ }
+
+ /**
+ * Returns the default threshold.
+ */
+ float getValueThreshold() {
+ return mMinVisibleChange * THRESHOLD_MULTIPLIER;
+ }
+
+ /**
+ * Obtain the property value through the corresponding getter.
+ */
+ @SuppressWarnings("unchecked")
+ private float getPropertyValue() {
+ return (Float) mProperty.get(mTarget);
+ }
+
+ /**
+ * Returns the {@link AnimationHandler} used to schedule updates for this animator.
+ *
+ * @return the {@link AnimationHandler} for this animator.
+ */
+ @NonNull
+ public AnimationHandler getAnimationHandler() {
+ return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance();
+ }
+
+ /****************Sub class animations**************/
+ /**
+ * Returns the acceleration at the given value with the given velocity.
+ **/
+ abstract float getAcceleration(float value, float velocity);
+
+ /**
+ * Returns whether the animation has reached equilibrium.
+ */
+ abstract boolean isAtEquilibrium(float value, float velocity);
+
+ /**
+ * Updates the default value threshold for the animation based on the property to be animated.
+ */
+ abstract void setValueThreshold(float threshold);
+
+ /**
+ * An animation listener that receives end notifications from an animation.
+ */
+ public interface OnAnimationEndListener {
+ /**
+ * Notifies the end of an animation. Note that this callback will be invoked not only when
+ * an animation reach equilibrium, but also when the animation is canceled.
+ *
+ * @param animation animation that has ended or was canceled
+ * @param canceled whether the animation has been canceled
+ * @param value the final value when the animation stopped
+ * @param velocity the final velocity when the animation stopped
+ */
+ void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+ float velocity);
+ }
+
+ /**
+ * Implementors of this interface can add themselves as update listeners
+ * to an <code>DynamicAnimation</code> instance to receive callbacks on every animation
+ * frame, after the current frame's values have been calculated for that
+ * <code>DynamicAnimation</code>.
+ */
+ public interface OnAnimationUpdateListener {
+
+ /**
+ * Notifies the occurrence of another frame of the animation.
+ *
+ * @param animation animation that the update listener is added to
+ * @param value the current value of the animation
+ * @param velocity the current velocity of the animation
+ */
+ void onAnimationUpdate(DynamicAnimation animation, float value, float velocity);
+ }
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java b/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java
new file mode 100644
index 0000000..c3a2cac
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.dynamicanimation.animation;
+
+/**
+ * <p>FloatValueHolder holds a float value. FloatValueHolder provides a setter and a getter (
+ * i.e. {@link #setValue(float)} and {@link #getValue()}) to access this float value. Animations can
+ * be performed on a FloatValueHolder instance. During each frame of the animation, the
+ * FloatValueHolder will have its value updated via {@link #setValue(float)}. The caller can
+ * obtain the up-to-date animation value via {@link FloatValueHolder#getValue()}.
+ *
+ * @see SpringAnimation#SpringAnimation(FloatValueHolder)
+ */
+
+public class FloatValueHolder {
+ private float mValue = 0.0f;
+
+ /**
+ * Constructs a holder for a float value that is initialized to 0.
+ */
+ public FloatValueHolder() {
+ }
+
+ /**
+ * Constructs a holder for a float value that is initialized to the input value.
+ *
+ * @param value the value to initialize the value held in the FloatValueHolder
+ */
+ public FloatValueHolder(float value) {
+ setValue(value);
+ }
+
+ /**
+ * Sets the value held in the FloatValueHolder instance.
+ *
+ * @param value float value held in the FloatValueHolder instance
+ */
+ public void setValue(float value) {
+ mValue = value;
+ }
+
+ /**
+ * Returns the float value held in the FloatValueHolder instance.
+ *
+ * @return float value held in the FloatValueHolder instance
+ */
+ public float getValue() {
+ return mValue;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt b/core/java/com/android/internal/dynamicanimation/animation/Force.java
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
copy to core/java/com/android/internal/dynamicanimation/animation/Force.java
index ca667dd..fcb9c45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
+++ b/core/java/com/android/internal/dynamicanimation/animation/Force.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.internal.dynamicanimation.animation;
-/** A listener interface to be notified of state change events for the notification panel. */
-fun interface PanelStateListener {
- /** Called when the panel's expansion state has changed. */
- fun onPanelStateChanged(@PanelState state: Int)
+/**
+ * Hide this for now, in case we want to change the API.
+ */
+interface Force {
+ // Acceleration based on position.
+ float getAcceleration(float position, float velocity);
+
+ boolean isAtEquilibrium(float value, float velocity);
}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java b/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java
new file mode 100644
index 0000000..2f3b72c
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.dynamicanimation.animation;
+
+import android.util.AndroidRuntimeException;
+import android.util.FloatProperty;
+
+/**
+ * SpringAnimation is an animation that is driven by a {@link SpringForce}. The spring force defines
+ * the spring's stiffness, damping ratio, as well as the rest position. Once the SpringAnimation is
+ * started, on each frame the spring force will update the animation's value and velocity.
+ * The animation will continue to run until the spring force reaches equilibrium. If the spring used
+ * in the animation is undamped, the animation will never reach equilibrium. Instead, it will
+ * oscillate forever.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * </div>
+ *
+ * <p>To create a simple {@link SpringAnimation} that uses the default {@link SpringForce}:</p>
+ * <pre class="prettyprint">
+ * // Create an animation to animate view's X property, set the rest position of the
+ * // default spring to 0, and start the animation with a starting velocity of 5000 (pixel/s).
+ * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.X, 0)
+ * .setStartVelocity(5000);
+ * anim.start();
+ * </pre>
+ *
+ * <p>Alternatively, a {@link SpringAnimation} can take a pre-configured {@link SpringForce}, and
+ * use that to drive the animation. </p>
+ * <pre class="prettyprint">
+ * // Create a low stiffness, low bounce spring at position 0.
+ * SpringForce spring = new SpringForce(0)
+ * .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ * .setStiffness(SpringForce.STIFFNESS_LOW);
+ * // Create an animation to animate view's scaleY property, and start the animation using
+ * // the spring above and a starting value of 0.5. Additionally, constrain the range of value for
+ * // the animation to be non-negative, effectively preventing any spring overshoot.
+ * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.SCALE_Y)
+ * .setMinValue(0).setSpring(spring).setStartValue(1);
+ * anim.start();
+ * </pre>
+ */
+public final class SpringAnimation extends DynamicAnimation<SpringAnimation> {
+
+ private SpringForce mSpring = null;
+ private float mPendingPosition = UNSET;
+ private static final float UNSET = Float.MAX_VALUE;
+ private boolean mEndRequested = false;
+
+ /**
+ * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During
+ * the animation, the {@link FloatValueHolder} instance will be updated via
+ * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date
+ * animation value via {@link FloatValueHolder#getValue()}.
+ *
+ * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via
+ * {@link FloatValueHolder#setValue(float)} outside of the animation during an
+ * animation run will not have any effect on the on-going animation.
+ *
+ * @param floatValueHolder the property to be animated
+ */
+ public SpringAnimation(FloatValueHolder floatValueHolder) {
+ super(floatValueHolder);
+ }
+
+ /**
+ * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During
+ * the animation, the {@link FloatValueHolder} instance will be updated via
+ * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date
+ * animation value via {@link FloatValueHolder#getValue()}.
+ *
+ * A Spring will be created with the given final position and default stiffness and damping
+ * ratio. This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
+ *
+ * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via
+ * {@link FloatValueHolder#setValue(float)} outside of the animation during an
+ * animation run will not have any effect on the on-going animation.
+ *
+ * @param floatValueHolder the property to be animated
+ * @param finalPosition the final position of the spring to be created.
+ */
+ public SpringAnimation(FloatValueHolder floatValueHolder, float finalPosition) {
+ super(floatValueHolder);
+ mSpring = new SpringForce(finalPosition);
+ }
+
+ /**
+ * This creates a SpringAnimation that animates the property of the given object.
+ * Note, a spring will need to setup through {@link #setSpring(SpringForce)} before
+ * the animation starts.
+ *
+ * @param object the Object whose property will be animated
+ * @param property the property to be animated
+ * @param <K> the class on which the Property is declared
+ */
+ public <K> SpringAnimation(K object, FloatProperty<K> property) {
+ super(object, property);
+ }
+
+ /**
+ * This creates a SpringAnimation that animates the property of the given object. A Spring will
+ * be created with the given final position and default stiffness and damping ratio.
+ * This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
+ *
+ * @param object the Object whose property will be animated
+ * @param property the property to be animated
+ * @param finalPosition the final position of the spring to be created.
+ * @param <K> the class on which the Property is declared
+ */
+ public <K> SpringAnimation(K object, FloatProperty<K> property,
+ float finalPosition) {
+ super(object, property);
+ mSpring = new SpringForce(finalPosition);
+ }
+
+ /**
+ * Returns the spring that the animation uses for animations.
+ *
+ * @return the spring that the animation uses for animations
+ */
+ public SpringForce getSpring() {
+ return mSpring;
+ }
+
+ /**
+ * Uses the given spring as the force that drives this animation. If this spring force has its
+ * parameters re-configured during the animation, the new configuration will be reflected in the
+ * animation immediately.
+ *
+ * @param force a pre-defined spring force that drives the animation
+ * @return the animation that the spring force is set on
+ */
+ public SpringAnimation setSpring(SpringForce force) {
+ mSpring = force;
+ return this;
+ }
+
+ @Override
+ public void start() {
+ sanityCheck();
+ mSpring.setValueThreshold(getValueThreshold());
+ super.start();
+ }
+
+ /**
+ * Updates the final position of the spring.
+ * <p/>
+ * When the animation is running, calling this method would assume the position change of the
+ * spring as a continuous movement since last frame, which yields more accurate results than
+ * changing the spring position directly through {@link SpringForce#setFinalPosition(float)}.
+ * <p/>
+ * If the animation hasn't started, calling this method will change the spring position, and
+ * immediately start the animation.
+ *
+ * @param finalPosition rest position of the spring
+ */
+ public void animateToFinalPosition(float finalPosition) {
+ if (isRunning()) {
+ mPendingPosition = finalPosition;
+ } else {
+ if (mSpring == null) {
+ mSpring = new SpringForce(finalPosition);
+ }
+ mSpring.setFinalPosition(finalPosition);
+ start();
+ }
+ }
+
+ /**
+ * Cancels the on-going animation. If the animation hasn't started, no op. Note that this method
+ * should only be called on main thread.
+ *
+ * @throws AndroidRuntimeException if this method is not called on the main thread
+ */
+ @Override
+ public void cancel() {
+ super.cancel();
+ if (mPendingPosition != UNSET) {
+ if (mSpring == null) {
+ mSpring = new SpringForce(mPendingPosition);
+ } else {
+ mSpring.setFinalPosition(mPendingPosition);
+ }
+ mPendingPosition = UNSET;
+ }
+ }
+
+ /**
+ * Skips to the end of the animation. If the spring is undamped, an
+ * {@link IllegalStateException} will be thrown, as the animation would never reach to an end.
+ * It is recommended to check {@link #canSkipToEnd()} before calling this method. If animation
+ * is not running, no-op.
+ *
+ * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
+ * is created on the same thread as the first call to start/cancel an animation. All the
+ * subsequent animation lifecycle manipulations need to be on that same thread, until the
+ * AnimationHandler is reset (using [setAnimationHandler]).
+ *
+ * @throws IllegalStateException if the spring is undamped (i.e. damping ratio = 0)
+ * @throws AndroidRuntimeException if this method is not called on the same thread as the
+ * animation handler
+ */
+ public void skipToEnd() {
+ if (!canSkipToEnd()) {
+ throw new UnsupportedOperationException("Spring animations can only come to an end"
+ + " when there is damping");
+ }
+ if (!isCurrentThread()) {
+ throw new AndroidRuntimeException("Animations may only be started on the same thread "
+ + "as the animation handler");
+ }
+ if (mRunning) {
+ mEndRequested = true;
+ }
+ }
+
+ /**
+ * Queries whether the spring can eventually come to the rest position.
+ *
+ * @return {@code true} if the spring is damped, otherwise {@code false}
+ */
+ public boolean canSkipToEnd() {
+ return mSpring.mDampingRatio > 0;
+ }
+
+ /************************ Below are private APIs *************************/
+
+ private void sanityCheck() {
+ if (mSpring == null) {
+ throw new UnsupportedOperationException("Incomplete SpringAnimation: Either final"
+ + " position or a spring force needs to be set.");
+ }
+ double finalPosition = mSpring.getFinalPosition();
+ if (finalPosition > mMaxValue) {
+ throw new UnsupportedOperationException("Final position of the spring cannot be greater"
+ + " than the max value.");
+ } else if (finalPosition < mMinValue) {
+ throw new UnsupportedOperationException("Final position of the spring cannot be less"
+ + " than the min value.");
+ }
+ }
+
+ @Override
+ boolean updateValueAndVelocity(long deltaT) {
+ // If user had requested end, then update the value and velocity to end state and consider
+ // animation done.
+ if (mEndRequested) {
+ if (mPendingPosition != UNSET) {
+ mSpring.setFinalPosition(mPendingPosition);
+ mPendingPosition = UNSET;
+ }
+ mValue = mSpring.getFinalPosition();
+ mVelocity = 0;
+ mEndRequested = false;
+ return true;
+ }
+
+ if (mPendingPosition != UNSET) {
+ // Approximate by considering half of the time spring position stayed at the old
+ // position, half of the time it's at the new position.
+ MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT / 2);
+ mSpring.setFinalPosition(mPendingPosition);
+ mPendingPosition = UNSET;
+
+ massState = mSpring.updateValues(massState.mValue, massState.mVelocity, deltaT / 2);
+ mValue = massState.mValue;
+ mVelocity = massState.mVelocity;
+
+ } else {
+ MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT);
+ mValue = massState.mValue;
+ mVelocity = massState.mVelocity;
+ }
+
+ mValue = Math.max(mValue, mMinValue);
+ mValue = Math.min(mValue, mMaxValue);
+
+ if (isAtEquilibrium(mValue, mVelocity)) {
+ mValue = mSpring.getFinalPosition();
+ mVelocity = 0f;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ float getAcceleration(float value, float velocity) {
+ return mSpring.getAcceleration(value, velocity);
+ }
+
+ @Override
+ boolean isAtEquilibrium(float value, float velocity) {
+ return mSpring.isAtEquilibrium(value, velocity);
+ }
+
+ @Override
+ void setValueThreshold(float threshold) {
+ }
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java b/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java
new file mode 100644
index 0000000..36242ae2
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.dynamicanimation.animation;
+
+import android.annotation.FloatRange;
+
+/**
+ * Spring Force defines the characteristics of the spring being used in the animation.
+ * <p>
+ * By configuring the stiffness and damping ratio, callers can create a spring with the look and
+ * feel suits their use case. Stiffness corresponds to the spring constant. The stiffer the spring
+ * is, the harder it is to stretch it, the faster it undergoes dampening.
+ * <p>
+ * Spring damping ratio describes how oscillations in a system decay after a disturbance.
+ * When damping ratio > 1* (i.e. over-damped), the object will quickly return to the rest position
+ * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will
+ * return to equilibrium within the shortest amount of time. When damping ratio is less than 1
+ * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without any
+ * damping (i.e. damping ratio = 0), the mass will oscillate forever.
+ */
+public final class SpringForce implements Force {
+ /**
+ * Stiffness constant for extremely stiff spring.
+ */
+ public static final float STIFFNESS_HIGH = 10_000f;
+ /**
+ * Stiffness constant for medium stiff spring. This is the default stiffness for spring force.
+ */
+ public static final float STIFFNESS_MEDIUM = 1500f;
+ /**
+ * Stiffness constant for a spring with low stiffness.
+ */
+ public static final float STIFFNESS_LOW = 200f;
+ /**
+ * Stiffness constant for a spring with very low stiffness.
+ */
+ public static final float STIFFNESS_VERY_LOW = 50f;
+
+ /**
+ * Damping ratio for a very bouncy spring. Note for under-damped springs
+ * (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring.
+ */
+ public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f;
+ /**
+ * Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring
+ * force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio,
+ * the more bouncy the spring.
+ */
+ public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f;
+ /**
+ * Damping ratio for a spring with low bounciness. Note for under-damped springs
+ * (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness.
+ */
+ public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f;
+ /**
+ * Damping ratio for a spring with no bounciness. This damping ratio will create a critically
+ * damped spring that returns to equilibrium within the shortest amount of time without
+ * oscillating.
+ */
+ public static final float DAMPING_RATIO_NO_BOUNCY = 1f;
+
+ // This multiplier is used to calculate the velocity threshold given a certain value threshold.
+ // The idea is that if it takes >= 1 frame to move the value threshold amount, then the velocity
+ // is a reasonable threshold.
+ private static final double VELOCITY_THRESHOLD_MULTIPLIER = 1000.0 / 16.0;
+
+ // Natural frequency
+ double mNaturalFreq = Math.sqrt(STIFFNESS_MEDIUM);
+ // Damping ratio.
+ double mDampingRatio = DAMPING_RATIO_MEDIUM_BOUNCY;
+
+ // Value to indicate an unset state.
+ private static final double UNSET = Double.MAX_VALUE;
+
+ // Indicates whether the spring has been initialized
+ private boolean mInitialized = false;
+
+ // Threshold for velocity and value to determine when it's reasonable to assume that the spring
+ // is approximately at rest.
+ private double mValueThreshold;
+ private double mVelocityThreshold;
+
+ // Intermediate values to simplify the spring function calculation per frame.
+ private double mGammaPlus;
+ private double mGammaMinus;
+ private double mDampedFreq;
+
+ // Final position of the spring. This must be set before the start of the animation.
+ private double mFinalPosition = UNSET;
+
+ // Internal state to hold a value/velocity pair.
+ private final DynamicAnimation.MassState mMassState = new DynamicAnimation.MassState();
+
+ /**
+ * Creates a spring force. Note that final position of the spring must be set through
+ * {@link #setFinalPosition(float)} before the spring animation starts.
+ */
+ public SpringForce() {
+ // No op.
+ }
+
+ /**
+ * Creates a spring with a given final rest position.
+ *
+ * @param finalPosition final position of the spring when it reaches equilibrium
+ */
+ public SpringForce(float finalPosition) {
+ mFinalPosition = finalPosition;
+ }
+
+ /**
+ * Sets the stiffness of a spring. The more stiff a spring is, the more force it applies to
+ * the object attached when the spring is not at the final position. Default stiffness is
+ * {@link #STIFFNESS_MEDIUM}.
+ *
+ * @param stiffness non-negative stiffness constant of a spring
+ * @return the spring force that the given stiffness is set on
+ * @throws IllegalArgumentException if the given spring stiffness is not positive
+ */
+ public SpringForce setStiffness(
+ @FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
+ if (stiffness <= 0) {
+ throw new IllegalArgumentException("Spring stiffness constant must be positive.");
+ }
+ mNaturalFreq = Math.sqrt(stiffness);
+ // All the intermediate values need to be recalculated.
+ mInitialized = false;
+ return this;
+ }
+
+ /**
+ * Gets the stiffness of the spring.
+ *
+ * @return the stiffness of the spring
+ */
+ public float getStiffness() {
+ return (float) (mNaturalFreq * mNaturalFreq);
+ }
+
+ /**
+ * Spring damping ratio describes how oscillations in a system decay after a disturbance.
+ * <p>
+ * When damping ratio > 1 (over-damped), the object will quickly return to the rest position
+ * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will
+ * return to equilibrium within the shortest amount of time. When damping ratio is less than 1
+ * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without
+ * any damping (i.e. damping ratio = 0), the mass will oscillate forever.
+ * <p>
+ * Default damping ratio is {@link #DAMPING_RATIO_MEDIUM_BOUNCY}.
+ *
+ * @param dampingRatio damping ratio of the spring, it should be non-negative
+ * @return the spring force that the given damping ratio is set on
+ * @throws IllegalArgumentException if the {@param dampingRatio} is negative.
+ */
+ public SpringForce setDampingRatio(@FloatRange(from = 0.0) float dampingRatio) {
+ if (dampingRatio < 0) {
+ throw new IllegalArgumentException("Damping ratio must be non-negative");
+ }
+ mDampingRatio = dampingRatio;
+ // All the intermediate values need to be recalculated.
+ mInitialized = false;
+ return this;
+ }
+
+ /**
+ * Returns the damping ratio of the spring.
+ *
+ * @return damping ratio of the spring
+ */
+ public float getDampingRatio() {
+ return (float) mDampingRatio;
+ }
+
+ /**
+ * Sets the rest position of the spring.
+ *
+ * @param finalPosition rest position of the spring
+ * @return the spring force that the given final position is set on
+ */
+ public SpringForce setFinalPosition(float finalPosition) {
+ mFinalPosition = finalPosition;
+ return this;
+ }
+
+ /**
+ * Returns the rest position of the spring.
+ *
+ * @return rest position of the spring
+ */
+ public float getFinalPosition() {
+ return (float) mFinalPosition;
+ }
+
+ /*********************** Below are private APIs *********************/
+
+ @Override
+ public float getAcceleration(float lastDisplacement, float lastVelocity) {
+
+ lastDisplacement -= getFinalPosition();
+
+ double k = mNaturalFreq * mNaturalFreq;
+ double c = 2 * mNaturalFreq * mDampingRatio;
+
+ return (float) (-k * lastDisplacement - c * lastVelocity);
+ }
+
+ @Override
+ public boolean isAtEquilibrium(float value, float velocity) {
+ if (Math.abs(velocity) < mVelocityThreshold
+ && Math.abs(value - getFinalPosition()) < mValueThreshold) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Initialize the string by doing the necessary pre-calculation as well as some sanity check
+ * on the setup.
+ *
+ * @throws IllegalStateException if the final position is not yet set by the time the spring
+ * animation has started
+ */
+ private void init() {
+ if (mInitialized) {
+ return;
+ }
+
+ if (mFinalPosition == UNSET) {
+ throw new IllegalStateException("Error: Final position of the spring must be"
+ + " set before the animation starts");
+ }
+
+ if (mDampingRatio > 1) {
+ // Over damping
+ mGammaPlus = -mDampingRatio * mNaturalFreq
+ + mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1);
+ mGammaMinus = -mDampingRatio * mNaturalFreq
+ - mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1);
+ } else if (mDampingRatio >= 0 && mDampingRatio < 1) {
+ // Under damping
+ mDampedFreq = mNaturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
+ }
+
+ mInitialized = true;
+ }
+
+ /**
+ * Internal only call for Spring to calculate the spring position/velocity using
+ * an analytical approach.
+ */
+ DynamicAnimation.MassState updateValues(double lastDisplacement, double lastVelocity,
+ long timeElapsed) {
+ init();
+
+ double deltaT = timeElapsed / 1000d; // unit: seconds
+ lastDisplacement -= mFinalPosition;
+ double displacement;
+ double currentVelocity;
+ if (mDampingRatio > 1) {
+ // Overdamped
+ double coeffA = lastDisplacement - (mGammaMinus * lastDisplacement - lastVelocity)
+ / (mGammaMinus - mGammaPlus);
+ double coeffB = (mGammaMinus * lastDisplacement - lastVelocity)
+ / (mGammaMinus - mGammaPlus);
+ displacement = coeffA * Math.pow(Math.E, mGammaMinus * deltaT)
+ + coeffB * Math.pow(Math.E, mGammaPlus * deltaT);
+ currentVelocity = coeffA * mGammaMinus * Math.pow(Math.E, mGammaMinus * deltaT)
+ + coeffB * mGammaPlus * Math.pow(Math.E, mGammaPlus * deltaT);
+ } else if (mDampingRatio == 1) {
+ // Critically damped
+ double coeffA = lastDisplacement;
+ double coeffB = lastVelocity + mNaturalFreq * lastDisplacement;
+ displacement = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT);
+ currentVelocity = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT)
+ * -mNaturalFreq + coeffB * Math.pow(Math.E, -mNaturalFreq * deltaT);
+ } else {
+ // Underdamped
+ double cosCoeff = lastDisplacement;
+ double sinCoeff = (1 / mDampedFreq) * (mDampingRatio * mNaturalFreq
+ * lastDisplacement + lastVelocity);
+ displacement = Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
+ * (cosCoeff * Math.cos(mDampedFreq * deltaT)
+ + sinCoeff * Math.sin(mDampedFreq * deltaT));
+ currentVelocity = displacement * -mNaturalFreq * mDampingRatio
+ + Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
+ * (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
+ + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
+ }
+
+ mMassState.mValue = (float) (displacement + mFinalPosition);
+ mMassState.mVelocity = (float) currentVelocity;
+ return mMassState;
+ }
+
+ /**
+ * This threshold defines how close the animation value needs to be before the animation can
+ * finish. This default value is based on the property being animated, e.g. animations on alpha,
+ * scale, translation or rotation would have different thresholds. This value should be small
+ * enough to avoid visual glitch of "jumping to the end". But it shouldn't be so small that
+ * animations take seconds to finish.
+ *
+ * @param threshold the difference between the animation value and final spring position that
+ * is allowed to end the animation when velocity is very low
+ */
+ void setValueThreshold(double threshold) {
+ mValueThreshold = Math.abs(threshold);
+ mVelocityThreshold = mValueThreshold * VELOCITY_THRESHOLD_MULTIPLIER;
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index ec2bc7c..6fed26c 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -167,7 +167,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- static final int VERSION = 208;
+ static final int VERSION = 210;
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -1326,6 +1326,13 @@
LongSamplingCounter mMobileRadioActiveUnknownTime;
LongSamplingCounter mMobileRadioActiveUnknownCount;
+ /**
+ * The soonest the Mobile Radio stats can be updated due to a mobile radio power state change
+ * after it was last updated.
+ */
+ @VisibleForTesting
+ protected static final long MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS = 1000 * 60 * 10;
+
int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
@GuardedBy("this")
@@ -6145,12 +6152,13 @@
@UnsupportedAppUsage
@GuardedBy("this")
- public void noteUserActivityLocked(int uid, int event) {
+ public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event) {
noteUserActivityLocked(uid, event, mClock.elapsedRealtime(), mClock.uptimeMillis());
}
@GuardedBy("this")
- public void noteUserActivityLocked(int uid, int event, long elapsedRealtimeMs, long uptimeMs) {
+ public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event,
+ long elapsedRealtimeMs, long uptimeMs) {
if (mOnBatteryInternal) {
uid = mapUid(uid);
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteUserActivityLocked(event);
@@ -6259,6 +6267,15 @@
} else {
mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
+
+ if (mLastModemActivityInfo != null) {
+ if (elapsedRealtimeMs < mLastModemActivityInfo.getTimestampMillis()
+ + MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS) {
+ // Modem Activity info has been collected recently, don't bother
+ // triggering another update.
+ return false;
+ }
+ }
// Tell the caller to collect radio network/power stats.
return true;
}
@@ -9962,14 +9979,14 @@
}
@Override
- public void noteUserActivityLocked(int type) {
+ public void noteUserActivityLocked(@PowerManager.UserActivityEvent int event) {
if (mUserActivityCounters == null) {
initUserActivityLocked();
}
- if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) {
- mUserActivityCounters[type].stepAtomic();
+ if (event >= 0 && event < NUM_USER_ACTIVITY_TYPES) {
+ mUserActivityCounters[event].stepAtomic();
} else {
- Slog.w(TAG, "Unknown user activity type " + type + " was specified.",
+ Slog.w(TAG, "Unknown user activity type " + event + " was specified.",
new Throwable());
}
}
diff --git a/core/java/com/android/internal/util/ObservableServiceConnection.java b/core/java/com/android/internal/util/ObservableServiceConnection.java
new file mode 100644
index 0000000..3165d29
--- /dev/null
+++ b/core/java/com/android/internal/util/ObservableServiceConnection.java
@@ -0,0 +1,258 @@
+/*
+ * 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.util;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CallbackRegistry.NotifierCallback;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * {@link ObservableServiceConnection} is a concrete implementation of {@link ServiceConnection}
+ * that enables monitoring the status of a binder connection. It also aides in automatically
+ * converting a proxy into an internal wrapper type.
+ *
+ * @param <T> The type of the wrapper over the resulting service.
+ */
+public class ObservableServiceConnection<T> implements ServiceConnection {
+ /**
+ * An interface for converting the service proxy into a given internal wrapper type.
+ *
+ * @param <T> The type of the wrapper over the resulting service.
+ */
+ public interface ServiceTransformer<T> {
+ /**
+ * Called to convert the service proxy to the wrapper type.
+ *
+ * @param service The service proxy to create the wrapper type from.
+ * @return The wrapper type.
+ */
+ T convert(IBinder service);
+ }
+
+ /**
+ * An interface for listening to the connection status.
+ *
+ * @param <T> The wrapper type.
+ */
+ public interface Callback<T> {
+ /**
+ * Invoked when the service has been successfully connected to.
+ *
+ * @param connection The {@link ObservableServiceConnection} instance that is now connected
+ * @param service The service proxy converted into the typed wrapper.
+ */
+ void onConnected(ObservableServiceConnection<T> connection, T service);
+
+ /**
+ * Invoked when the service has been disconnected.
+ *
+ * @param connection The {@link ObservableServiceConnection} that is now disconnected.
+ * @param reason The reason for the disconnection.
+ */
+ void onDisconnected(ObservableServiceConnection<T> connection,
+ @DisconnectReason int reason);
+ }
+
+ /**
+ * Default state, service has not yet disconnected.
+ */
+ public static final int DISCONNECT_REASON_NONE = 0;
+ /**
+ * Disconnection was due to the resulting binding being {@code null}.
+ */
+ public static final int DISCONNECT_REASON_NULL_BINDING = 1;
+ /**
+ * Disconnection was due to the remote end disconnecting.
+ */
+ public static final int DISCONNECT_REASON_DISCONNECTED = 2;
+ /**
+ * Disconnection due to the binder dying.
+ */
+ public static final int DISCONNECT_REASON_BINDING_DIED = 3;
+ /**
+ * Disconnection from an explicit unbinding.
+ */
+ public static final int DISCONNECT_REASON_UNBIND = 4;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DISCONNECT_REASON_NONE,
+ DISCONNECT_REASON_NULL_BINDING,
+ DISCONNECT_REASON_DISCONNECTED,
+ DISCONNECT_REASON_BINDING_DIED,
+ DISCONNECT_REASON_UNBIND
+ })
+ public @interface DisconnectReason {
+ }
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+ private final Executor mExecutor;
+ private final ServiceTransformer<T> mTransformer;
+ private final Intent mServiceIntent;
+ private final int mFlags;
+
+ @GuardedBy("mLock")
+ private T mService;
+ @GuardedBy("mLock")
+ private boolean mBoundCalled = false;
+ @GuardedBy("mLock")
+ private int mLastDisconnectReason = DISCONNECT_REASON_NONE;
+
+ private final CallbackRegistry<Callback<T>, ObservableServiceConnection<T>, T>
+ mCallbackRegistry = new CallbackRegistry<>(
+ new NotifierCallback<Callback<T>, ObservableServiceConnection<T>, T>() {
+ @Override
+ public void onNotifyCallback(Callback<T> callback,
+ ObservableServiceConnection<T> sender,
+ int disconnectReason, T service) {
+ mExecutor.execute(() -> {
+ synchronized (mLock) {
+ if (service != null) {
+ callback.onConnected(sender, service);
+ } else if (mLastDisconnectReason != DISCONNECT_REASON_NONE) {
+ callback.onDisconnected(sender, disconnectReason);
+ }
+ }
+ });
+ }
+ });
+
+ /**
+ * Default constructor for {@link ObservableServiceConnection}.
+ *
+ * @param context The context from which the service will be bound with.
+ * @param executor The executor for connection callbacks to be delivered on
+ * @param transformer A {@link ObservableServiceConnection.ServiceTransformer} for transforming
+ * the resulting service into a desired type.
+ */
+ public ObservableServiceConnection(@NonNull Context context,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ServiceTransformer<T> transformer,
+ Intent serviceIntent,
+ int flags) {
+ mContext = context;
+ mExecutor = executor;
+ mTransformer = transformer;
+ mServiceIntent = serviceIntent;
+ mFlags = flags;
+ }
+
+ /**
+ * Initiate binding to the service.
+ *
+ * @return {@code true} if initiating binding succeed, {@code false} if the binding failed or
+ * if this service is already bound. Regardless of the return value, you should later call
+ * {@link #unbind()} to release the connection.
+ */
+ public boolean bind() {
+ synchronized (mLock) {
+ if (mBoundCalled) {
+ return false;
+ }
+ final boolean bindResult =
+ mContext.bindService(mServiceIntent, mFlags, mExecutor, this);
+ mBoundCalled = true;
+ return bindResult;
+ }
+ }
+
+ /**
+ * Disconnect from the service if bound.
+ */
+ public void unbind() {
+ onDisconnected(DISCONNECT_REASON_UNBIND);
+ }
+
+ /**
+ * Adds a callback for receiving connection updates.
+ *
+ * @param callback The {@link Callback} to receive future updates.
+ */
+ public void addCallback(Callback<T> callback) {
+ mCallbackRegistry.add(callback);
+ mExecutor.execute(() -> {
+ synchronized (mLock) {
+ if (mService != null) {
+ callback.onConnected(this, mService);
+ } else if (mLastDisconnectReason != DISCONNECT_REASON_NONE) {
+ callback.onDisconnected(this, mLastDisconnectReason);
+ }
+ }
+ });
+ }
+
+ /**
+ * Removes previously added callback from receiving future connection updates.
+ *
+ * @param callback The {@link Callback} to be removed.
+ */
+ public void removeCallback(Callback<T> callback) {
+ synchronized (mLock) {
+ mCallbackRegistry.remove(callback);
+ }
+ }
+
+ private void onDisconnected(@DisconnectReason int reason) {
+ synchronized (mLock) {
+ if (!mBoundCalled) {
+ return;
+ }
+ mBoundCalled = false;
+ mLastDisconnectReason = reason;
+ mContext.unbindService(this);
+ mService = null;
+ mCallbackRegistry.notifyCallbacks(this, reason, null);
+ }
+ }
+
+ @Override
+ public final void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mService = mTransformer.convert(service);
+ mLastDisconnectReason = DISCONNECT_REASON_NONE;
+ mCallbackRegistry.notifyCallbacks(this, mLastDisconnectReason, mService);
+ }
+ }
+
+ @Override
+ public final void onServiceDisconnected(ComponentName name) {
+ onDisconnected(DISCONNECT_REASON_DISCONNECTED);
+ }
+
+ @Override
+ public final void onBindingDied(ComponentName name) {
+ onDisconnected(DISCONNECT_REASON_BINDING_DIED);
+ }
+
+ @Override
+ public final void onNullBinding(ComponentName name) {
+ onDisconnected(DISCONNECT_REASON_NULL_BINDING);
+ }
+}
diff --git a/core/java/com/android/internal/util/PersistentServiceConnection.java b/core/java/com/android/internal/util/PersistentServiceConnection.java
new file mode 100644
index 0000000..d201734
--- /dev/null
+++ b/core/java/com/android/internal/util/PersistentServiceConnection.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.Executor;
+
+/**
+ * {@link PersistentServiceConnection} is a concrete implementation of {@link ServiceConnection}
+ * that maintains the binder connection by handling reconnection when a failure occurs.
+ *
+ * @param <T> The transformed connection type handled by the service.
+ *
+ * When the target process is killed (by OOM-killer, force-stopped, crash, etc..) then this class
+ * will trigger a reconnection to the target. This should be used carefully.
+ *
+ * NOTE: This class does *not* handle package-updates -- i.e. even if the binding dies due to
+ * the target package being updated, this class won't reconnect. This is because this class doesn't
+ * know what to do when the service component has gone missing, for example. If the user of this
+ * class wants to restore the connection, then it should call {@link #unbind()} and {@link #bind}
+ * explicitly.
+ */
+public class PersistentServiceConnection<T> extends ObservableServiceConnection<T> {
+ private final Callback<T> mConnectionCallback = new Callback<T>() {
+ private long mConnectedTime;
+
+ @Override
+ public void onConnected(ObservableServiceConnection<T> connection, T service) {
+ mConnectedTime = mInjector.uptimeMillis();
+ }
+
+ @Override
+ public void onDisconnected(ObservableServiceConnection<T> connection,
+ @DisconnectReason int reason) {
+ if (reason == DISCONNECT_REASON_UNBIND) return;
+ synchronized (mLock) {
+ if ((mInjector.uptimeMillis() - mConnectedTime) > mMinConnectionDurationMs) {
+ mReconnectAttempts = 0;
+ bindInternalLocked();
+ } else {
+ scheduleConnectionAttemptLocked();
+ }
+ }
+ }
+ };
+
+ private final Object mLock = new Object();
+ private final Injector mInjector;
+ private final Handler mHandler;
+ private final int mMinConnectionDurationMs;
+ private final int mMaxReconnectAttempts;
+ private final int mBaseReconnectDelayMs;
+ @GuardedBy("mLock")
+ private int mReconnectAttempts;
+ @GuardedBy("mLock")
+ private Object mCancelToken;
+
+ private final Runnable mConnectRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ mCancelToken = null;
+ bindInternalLocked();
+ }
+ }
+ };
+
+ /**
+ * Default constructor for {@link PersistentServiceConnection}.
+ *
+ * @param context The context from which the service will be bound with.
+ * @param executor The executor for connection callbacks to be delivered on
+ * @param transformer A {@link ServiceTransformer} for transforming
+ */
+ public PersistentServiceConnection(Context context,
+ Executor executor,
+ Handler handler,
+ ServiceTransformer<T> transformer,
+ Intent serviceIntent,
+ int flags,
+ int minConnectionDurationMs,
+ int maxReconnectAttempts,
+ int baseReconnectDelayMs) {
+ this(context,
+ executor,
+ handler,
+ transformer,
+ serviceIntent,
+ flags,
+ minConnectionDurationMs,
+ maxReconnectAttempts,
+ baseReconnectDelayMs,
+ new Injector());
+ }
+
+ @VisibleForTesting
+ public PersistentServiceConnection(
+ Context context,
+ Executor executor,
+ Handler handler,
+ ServiceTransformer<T> transformer,
+ Intent serviceIntent,
+ int flags,
+ int minConnectionDurationMs,
+ int maxReconnectAttempts,
+ int baseReconnectDelayMs,
+ Injector injector) {
+ super(context, executor, transformer, serviceIntent, flags);
+ mHandler = handler;
+ mMinConnectionDurationMs = minConnectionDurationMs;
+ mMaxReconnectAttempts = maxReconnectAttempts;
+ mBaseReconnectDelayMs = baseReconnectDelayMs;
+ mInjector = injector;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean bind() {
+ synchronized (mLock) {
+ addCallback(mConnectionCallback);
+ mReconnectAttempts = 0;
+ return bindInternalLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean bindInternalLocked() {
+ return super.bind();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void unbind() {
+ synchronized (mLock) {
+ removeCallback(mConnectionCallback);
+ cancelPendingConnectionAttemptLocked();
+ super.unbind();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void cancelPendingConnectionAttemptLocked() {
+ if (mCancelToken != null) {
+ mHandler.removeCallbacksAndMessages(mCancelToken);
+ mCancelToken = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void scheduleConnectionAttemptLocked() {
+ cancelPendingConnectionAttemptLocked();
+
+ if (mReconnectAttempts >= mMaxReconnectAttempts) {
+ return;
+ }
+
+ final long reconnectDelayMs =
+ (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
+
+ mCancelToken = new Object();
+ mHandler.postDelayed(mConnectRunnable, mCancelToken, reconnectDelayMs);
+ mReconnectAttempts++;
+ }
+
+ /**
+ * Injector for testing
+ */
+ @VisibleForTesting
+ public static class Injector {
+ /**
+ * Returns milliseconds since boot, not counting time spent in deep sleep. Can be overridden
+ * in tests with a fake clock.
+ */
+ public long uptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index a4da8de4..1235b60 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1522,8 +1522,7 @@
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
- STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT,
- SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED})
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
@Retention(RetentionPolicy.SOURCE)
public @interface StrongAuthFlags {}
@@ -1576,12 +1575,6 @@
public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
/**
- * Some authentication is required because the trustagent either timed out or was disabled
- * manually.
- */
- public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100;
-
- /**
* Strong auth flags that do not prevent biometric methods from being accepted as auth.
* If any other flags are set, biometric authentication is disabled.
*/
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index af9c5a5..52ffc98 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -17,6 +17,9 @@
package com.android.internal.widget;
+import static android.content.res.Resources.ID_NULL;
+
+import android.annotation.IdRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -96,6 +99,8 @@
private int mTopOffset;
private boolean mShowAtTop;
+ @IdRes
+ private int mIgnoreOffsetTopLimitViewId = ID_NULL;
private boolean mIsDragging;
private boolean mOpenOnClick;
@@ -156,6 +161,10 @@
mIsMaxCollapsedHeightSmallExplicit =
a.hasValue(R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall);
mShowAtTop = a.getBoolean(R.styleable.ResolverDrawerLayout_showAtTop, false);
+ if (a.hasValue(R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit)) {
+ mIgnoreOffsetTopLimitViewId = a.getResourceId(
+ R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit, ID_NULL);
+ }
a.recycle();
mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material);
@@ -577,12 +586,32 @@
dy -= 1.0f;
}
+ boolean isIgnoreOffsetLimitSet = false;
+ int ignoreOffsetLimit = 0;
+ View ignoreOffsetLimitView = findIgnoreOffsetLimitView();
+ if (ignoreOffsetLimitView != null) {
+ LayoutParams lp = (LayoutParams) ignoreOffsetLimitView.getLayoutParams();
+ ignoreOffsetLimit = ignoreOffsetLimitView.getBottom() + lp.bottomMargin;
+ isIgnoreOffsetLimitSet = true;
+ }
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.ignoreOffset) {
child.offsetTopAndBottom((int) dy);
+ } else if (isIgnoreOffsetLimitSet) {
+ int top = child.getTop();
+ int targetTop = Math.max(
+ (int) (ignoreOffsetLimit + lp.topMargin + dy),
+ lp.mFixedTop);
+ if (top != targetTop) {
+ child.offsetTopAndBottom(targetTop - top);
+ }
+ ignoreOffsetLimit = child.getBottom() + lp.bottomMargin;
}
}
final boolean isCollapsedOld = mCollapseOffset != 0;
@@ -1024,6 +1053,8 @@
final int rightEdge = width - getPaddingRight();
final int widthAvailable = rightEdge - leftEdge;
+ boolean isIgnoreOffsetLimitSet = false;
+ int ignoreOffsetLimit = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
@@ -1036,9 +1067,24 @@
continue;
}
+ if (mIgnoreOffsetTopLimitViewId != ID_NULL && !isIgnoreOffsetLimitSet) {
+ if (mIgnoreOffsetTopLimitViewId == child.getId()) {
+ ignoreOffsetLimit = child.getBottom() + lp.bottomMargin;
+ isIgnoreOffsetLimitSet = true;
+ }
+ }
+
int top = ypos + lp.topMargin;
if (lp.ignoreOffset) {
- top -= mCollapseOffset;
+ if (!isDragging()) {
+ lp.mFixedTop = (int) (top - mCollapseOffset);
+ }
+ if (isIgnoreOffsetLimitSet) {
+ top = Math.max(ignoreOffsetLimit + lp.topMargin, (int) (top - mCollapseOffset));
+ ignoreOffsetLimit = top + child.getMeasuredHeight() + lp.bottomMargin;
+ } else {
+ top -= mCollapseOffset;
+ }
}
final int bottom = top + child.getMeasuredHeight();
@@ -1102,11 +1148,23 @@
mCollapsibleHeightReserved = ss.mCollapsibleHeightReserved;
}
+ private View findIgnoreOffsetLimitView() {
+ if (mIgnoreOffsetTopLimitViewId == ID_NULL) {
+ return null;
+ }
+ View v = findViewById(mIgnoreOffsetTopLimitViewId);
+ if (v != null && v != this && v.getParent() == this && v.getVisibility() != View.GONE) {
+ return v;
+ }
+ return null;
+ }
+
public static class LayoutParams extends MarginLayoutParams {
public boolean alwaysShow;
public boolean ignoreOffset;
public boolean hasNestedScrollIndicator;
public int maxHeight;
+ int mFixedTop;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
diff --git a/core/res/Android.bp b/core/res/Android.bp
index c42517d..179eff8 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -130,6 +130,10 @@
// 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 5ae133b..964fe2d 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 ded23fe..38a71f0 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/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 6a200d05..a06e8a4 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -23,6 +23,7 @@
android:maxWidth="@dimen/resolver_max_width"
android:maxCollapsedHeight="@dimen/resolver_max_collapsed_height"
android:maxCollapsedHeightSmall="56dp"
+ android:ignoreOffsetTopLimit="@id/title_container"
android:id="@id/contentPanel">
<RelativeLayout
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 7c439008..46f0d81 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 1280485..c2f24a5 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 c9550c8..34cac8b 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 b52e57a..4f6d0c7 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 90f1006..fcbe2b8 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 15b25ad..681cc91 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 104808b..b86ce04 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 7f099b0..f8e9efb 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 fb95a1f..73cbf93 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 688d6b2..13487f7 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 abc5867..eab1b3e 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 c1c0d54..73a5978 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 1fe6e1c..0efbc73 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 c1c0d54..73a5978 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 86115d5..c2fe6dd 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 131ccd0..46a5ca4 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 7bb0ba9..ea26a15 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 d46bdc8..d0d585a 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 43de5ba..8f72050 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/attrs.xml b/core/res/res/values/attrs.xml
index 004b5f6..d86aa11 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -9560,6 +9560,12 @@
<attr name="maxCollapsedHeightSmall" format="dimension" />
<!-- Whether the Drawer should be positioned at the top rather than at the bottom. -->
<attr name="showAtTop" format="boolean" />
+ <!-- By default `ResolverDrawerLayout`’s children views with `layout_ignoreOffset` property
+ set to true have a fixed position in the layout that won’t be affected by the drawer’s
+ movements. This property alternates that behavior. It specifies a child view’s id that
+ will push all ignoreOffset siblings below it when the drawer is moved i.e. setting the
+ top limit the ignoreOffset elements. -->
+ <attr name="ignoreOffsetTopLimit" format="reference" />
</declare-styleable>
<declare-styleable name="MessagingLinearLayout">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5d1acbc..41256b8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -553,6 +553,14 @@
<!-- If this is true, then keep dreaming when undocking. -->
<bool name="config_keepDreamingWhenUndocking">false</bool>
+ <!-- The timeout (in ms) to wait before attempting to reconnect to the dream overlay service if
+ it becomes disconnected -->
+ <integer name="config_dreamOverlayReconnectTimeoutMs">1000</integer> <!-- 1 second -->
+ <!-- The maximum number of times to attempt reconnecting to the dream overlay service -->
+ <integer name="config_dreamOverlayMaxReconnectAttempts">3</integer>
+ <!-- The duration after which the dream overlay connection should be considered stable -->
+ <integer name="config_minDreamOverlayDurationMs">10000</integer> <!-- 10 seconds -->
+
<!-- Auto-rotation behavior -->
<!-- If true, enables auto-rotation features using the accelerometer.
@@ -650,6 +658,20 @@
-->
</integer-array>
+ <!-- The device states (supplied by DeviceStateManager) that should be treated as half-folded by
+ the display fold controller. Default is empty. -->
+ <integer-array name="config_halfFoldedDeviceStates">
+ <!-- Example:
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ -->
+ </integer-array>
+
+ <!-- Indicates whether the window manager reacts to half-fold device states by overriding
+ rotation. -->
+ <bool name="config_windowManagerHalfFoldAutoRotateOverride">false</bool>
+
<!-- When a device enters any of these states, it should be woken up. States are defined in
device_state_configuration.xml. -->
<integer-array name="config_deviceStatesOnWhichToWakeUp">
@@ -679,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
@@ -2457,6 +2479,8 @@
<integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes. -->
<!-- Is the system user the only user allowed to dream. -->
<bool name="config_dreamsOnlyEnabledForSystemUser">false</bool>
+ <!-- Whether dreams are disabled when ambient mode is suppressed. -->
+ <bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool>
<!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
@@ -5934,4 +5958,8 @@
TODO(b/236022708) Move rear display state to device state config file
-->
<integer name="config_deviceStateRearDisplay">-1</integer>
+
+ <!-- Whether the lock screen is allowed to run its own live wallpaper,
+ different from the home screen wallpaper. -->
+ <bool name="config_independentLockscreenLiveWallpaper">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6e574bd..5897e3a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2235,10 +2235,14 @@
<java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenNotPowered" />
<java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
<java-symbol type="string" name="config_dreamsDefaultComponent" />
+ <java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" />
<java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" />
<java-symbol type="array" name="config_supportedDreamComplications" />
<java-symbol type="array" name="config_disabledDreamComponents" />
<java-symbol type="bool" name="config_dismissDreamOnActivityStart" />
+ <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
+ <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
+ <java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
<java-symbol type="string" name="config_loggable_dream_prefix" />
<java-symbol type="string" name="config_dozeComponent" />
<java-symbol type="string" name="enable_explore_by_touch_warning_title" />
@@ -4002,6 +4006,8 @@
<!-- For Foldables -->
<java-symbol type="array" name="config_foldedDeviceStates" />
+ <java-symbol type="array" name="config_halfFoldedDeviceStates" />
+ <java-symbol type="bool" name="config_windowManagerHalfFoldAutoRotateOverride" />
<java-symbol type="array" name="config_deviceStatesOnWhichToWakeUp" />
<java-symbol type="array" name="config_deviceStatesOnWhichToSleep" />
<java-symbol type="string" name="config_foldedArea" />
@@ -4841,6 +4847,7 @@
<java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
<java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
<java-symbol type="integer" name="config_deviceStateRearDisplay"/>
+ <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
<!-- For app language picker -->
<java-symbol type="string" name="system_locale_title" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index f9f3b4c..0b8b29b 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.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.Test;
import org.junit.runner.RunWith;
+import java.util.List;
import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
@@ -531,6 +533,108 @@
}
@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 @@
@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 @@
}
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 0000000..8218b98
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.os.Bundle
+import android.service.chooser.ChooserTarget
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.R
+import com.android.internal.app.ChooserListAdapter.LoadDirectShareIconTask
+import com.android.internal.app.chooser.SelectableTargetInfo
+import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator
+import com.android.internal.app.chooser.TargetInfo
+import com.android.server.testutils.any
+import com.android.server.testutils.mock
+import com.android.server.testutils.whenever
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidJUnit4::class)
+class ChooserListAdapterTest {
+ private val packageManager = mock<PackageManager> {
+ whenever(resolveActivity(any(), anyInt())).thenReturn(mock())
+ }
+ private val context = InstrumentationRegistry.getInstrumentation().getContext()
+ private val resolverListController = mock<ResolverListController>()
+ private val chooserListCommunicator = mock<ChooserListAdapter.ChooserListCommunicator> {
+ whenever(maxRankedTargets).thenReturn(0)
+ }
+ private val selectableTargetInfoCommunicator =
+ mock<SelectableTargetInfoCommunicator> {
+ whenever(targetIntent).thenReturn(mock())
+ }
+ private val chooserActivityLogger = mock<ChooserActivityLogger>()
+
+ private fun createChooserListAdapter(
+ taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask
+ ) =
+ ChooserListAdapterOverride(
+ context,
+ emptyList(),
+ emptyArray(),
+ emptyList(),
+ false,
+ resolverListController,
+ chooserListCommunicator,
+ selectableTargetInfoCommunicator,
+ packageManager,
+ chooserActivityLogger,
+ taskProvider
+ )
+
+ @Test
+ fun testDirectShareTargetLoadingIconIsStarted() {
+ val view = createView()
+ val viewHolder = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolder
+ val targetInfo = createSelectableTargetInfo()
+ val iconTask = mock<LoadDirectShareIconTask>()
+ val testSubject = createChooserListAdapter { iconTask }
+ testSubject.testViewBind(view, targetInfo, 0)
+
+ verify(iconTask, times(1)).loadIcon()
+ }
+
+ @Test
+ fun testOnlyOneTaskPerTarget() {
+ val view = createView()
+ val viewHolderOne = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolderOne
+ val targetInfo = createSelectableTargetInfo()
+ val iconTaskOne = mock<LoadDirectShareIconTask>()
+ val testTaskProvider = mock<() -> LoadDirectShareIconTask> {
+ whenever(invoke()).thenReturn(iconTaskOne)
+ }
+ val testSubject = createChooserListAdapter { testTaskProvider.invoke() }
+ testSubject.testViewBind(view, targetInfo, 0)
+
+ val viewHolderTwo = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolderTwo
+ whenever(testTaskProvider()).thenReturn(mock())
+
+ testSubject.testViewBind(view, targetInfo, 0)
+
+ verify(iconTaskOne, times(1)).loadIcon()
+ verify(testTaskProvider, times(1)).invoke()
+ }
+
+ private fun createSelectableTargetInfo(): SelectableTargetInfo =
+ SelectableTargetInfo(
+ context,
+ null,
+ createChooserTarget(),
+ 1f,
+ selectableTargetInfoCommunicator,
+ null
+ )
+
+ private fun createChooserTarget(): ChooserTarget =
+ ChooserTarget(
+ "Title",
+ null,
+ 1f,
+ ComponentName("package", "package.Class"),
+ Bundle()
+ )
+
+ private fun createView(): View {
+ val view = FrameLayout(context)
+ TextView(context).apply {
+ id = R.id.text1
+ view.addView(this)
+ }
+ TextView(context).apply {
+ id = R.id.text2
+ view.addView(this)
+ }
+ ImageView(context).apply {
+ id = R.id.icon
+ view.addView(this)
+ }
+ return view
+ }
+}
+
+private class ChooserListAdapterOverride(
+ context: Context?,
+ payloadIntents: List<Intent>?,
+ initialIntents: Array<out Intent>?,
+ rList: List<ResolveInfo>?,
+ filterLastUsed: Boolean,
+ resolverListController: ResolverListController?,
+ chooserListCommunicator: ChooserListCommunicator?,
+ selectableTargetInfoCommunicator: SelectableTargetInfoCommunicator?,
+ packageManager: PackageManager?,
+ chooserActivityLogger: ChooserActivityLogger?,
+ private val taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask
+) : ChooserListAdapter(
+ context,
+ payloadIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ resolverListController,
+ chooserListCommunicator,
+ selectableTargetInfoCommunicator,
+ packageManager,
+ chooserActivityLogger
+) {
+ override fun createLoadDirectShareIconTask(
+ info: SelectableTargetInfo?
+ ): LoadDirectShareIconTask =
+ taskProvider.invoke(info)
+
+ fun testViewBind(view: View?, info: TargetInfo?, position: Int) {
+ onBindView(view, info, position)
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index d19f9f5..52feac5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -47,6 +47,7 @@
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
+import android.util.Log;
import android.util.MutableInt;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
@@ -82,6 +83,7 @@
* com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
*/
public class BatteryStatsNoteTest extends TestCase {
+ private static final String TAG = BatteryStatsNoteTest.class.getSimpleName();
private static final int UID = 10500;
private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23;
@@ -2031,6 +2033,115 @@
noRadioProcFlags, lastProcStateChangeFlags.value);
}
+
+
+ @SmallTest
+ public void testNoteMobileRadioPowerStateLocked() {
+ long curr;
+ boolean update;
+ final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setOnBatteryInternal(true);
+
+ // Note mobile radio is on.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 1001);
+ bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+ UID);
+
+ // Note mobile radio is still on.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 2001);
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ curr, UID);
+ assertFalse(
+ "noteMobileRadioPowerStateLocked should not request an update when the power "
+ + "state does not change from HIGH.",
+ update);
+
+ // Note mobile radio is off.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 3001);
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ curr, UID);
+ assertTrue(
+ "noteMobileRadioPowerStateLocked should request an update when the power state "
+ + "changes from HIGH to LOW.",
+ update);
+
+ // Note mobile radio is still off.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 4001);
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ curr, UID);
+ assertFalse(
+ "noteMobileRadioPowerStateLocked should not request an update when the power "
+ + "state does not change from LOW.",
+ update);
+
+ // Note mobile radio is on.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 5001);
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ curr, UID);
+ assertFalse(
+ "noteMobileRadioPowerStateLocked should not request an update when the power "
+ + "state changes from LOW to HIGH.",
+ update);
+ }
+
+ @SmallTest
+ public void testNoteMobileRadioPowerStateLocked_rateLimited() {
+ long curr;
+ boolean update;
+ final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setPowerProfile(mock(PowerProfile.class));
+
+ final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+ final ModemActivityInfo mai = new ModemActivityInfo(0L, 0L, 0L, new int[txLevelCount], 0L);
+
+ final long rateLimit = bi.getMobileRadioPowerStateUpdateRateLimit();
+ if (rateLimit < 0) {
+ Log.w(TAG, "Skipping testNoteMobileRadioPowerStateLocked_rateLimited, rateLimit = "
+ + rateLimit);
+ return;
+ }
+ bi.setOnBatteryInternal(true);
+
+ // Note mobile radio is on.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 1001);
+ bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+ UID);
+
+ clocks.realtime = clocks.uptime = 2001;
+ mai.setTimestamp(clocks.realtime);
+ bi.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE,
+ clocks.realtime, clocks.uptime, mNetworkStatsManager);
+
+ // Note mobile radio is off within the rate limit duration.
+ clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7);
+ curr = 1000L * clocks.realtime;
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ curr, UID);
+ assertFalse(
+ "noteMobileRadioPowerStateLocked should not request an update when the power "
+ + "state so soon after a noteModemControllerActivity",
+ update);
+
+ // Note mobile radio is on.
+ clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7);
+ curr = 1000L * clocks.realtime;
+ bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+ UID);
+
+ // Note mobile radio is off much later
+ clocks.realtime = clocks.uptime = clocks.realtime + rateLimit;
+ curr = 1000L * clocks.realtime;
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ curr, UID);
+ assertTrue(
+ "noteMobileRadioPowerStateLocked should request an update when the power state "
+ + "changes from HIGH to LOW much later after a "
+ + "noteModemControllerActivity.",
+ update);
+ }
+
private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
// Note that noteUidProcessStateLocked uses ActivityManager process states.
if (fgOn) {
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index edeb5e9..5ea4f06 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -114,6 +114,10 @@
return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase;
}
+ public long getMobileRadioPowerStateUpdateRateLimit() {
+ return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS;
+ }
+
public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) {
mNetworkStats = networkStats;
return this;
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index fd4fb13..2719431 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -17,7 +17,6 @@
package com.android.internal.util;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
-import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
@@ -85,12 +84,6 @@
}
@Test
- public void testSelectedRegionScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION,
- WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
- }
-
- @Test
public void testProvidedImageScreenshot() {
mScreenshotHelper.provideScreenshot(
new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index adc3676..3798da5 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -34,6 +34,7 @@
"mockito-target-minus-junit4",
"androidx.test.ext.junit",
"truth-prebuilt",
+ "servicestests-utils",
],
libs: [
diff --git a/core/tests/utiltests/src/com/android/internal/util/ObservableServiceConnectionTest.java b/core/tests/utiltests/src/com/android/internal/util/ObservableServiceConnectionTest.java
new file mode 100644
index 0000000..d124ad9
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/ObservableServiceConnectionTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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.util;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.ObservableServiceConnection.ServiceTransformer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayDeque;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+@SmallTest
+public class ObservableServiceConnectionTest {
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName("test.package", "component");
+
+ public static class Foo {
+ int mValue;
+
+ Foo(int value) {
+ mValue = value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Foo)) return false;
+ Foo foo = (Foo) o;
+ return mValue == foo.mValue;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mValue);
+ }
+ }
+
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Intent mIntent;
+ @Mock
+ private Foo mResult;
+ @Mock
+ private IBinder mBinder;
+ @Mock
+ private ServiceTransformer<Foo> mTransformer;
+ @Mock
+ private ObservableServiceConnection.Callback<Foo> mCallback;
+ private final FakeExecutor mExecutor = new FakeExecutor();
+ private ObservableServiceConnection<Foo> mConnection;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mConnection = new ObservableServiceConnection<>(
+ mContext,
+ mExecutor,
+ mTransformer,
+ mIntent,
+ /* flags= */ Context.BIND_AUTO_CREATE);
+ }
+
+ @After
+ public void tearDown() {
+ mExecutor.clearAll();
+ }
+
+ @Test
+ public void testConnect() {
+ // Register twice to ensure only one callback occurs.
+ mConnection.addCallback(mCallback);
+ mConnection.addCallback(mCallback);
+
+ mExecutor.runAll();
+ mConnection.bind();
+
+ // Ensure that no callbacks happen before connection.
+ verify(mCallback, never()).onConnected(any(), any());
+ verify(mCallback, never()).onDisconnected(any(), anyInt());
+
+ when(mTransformer.convert(mBinder)).thenReturn(mResult);
+ mConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+
+ mExecutor.runAll();
+ verify(mCallback, times(1)).onConnected(mConnection, mResult);
+ }
+
+ @Test
+ public void testDisconnectBeforeBind() {
+ mConnection.addCallback(mCallback);
+ mExecutor.runAll();
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+ mExecutor.runAll();
+ // Disconnects before binds should be ignored.
+ verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+ }
+
+ @Test
+ public void testDisconnect() {
+ mConnection.addCallback(mCallback);
+ mExecutor.runAll();
+ mConnection.bind();
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+
+ // Ensure the callback doesn't get triggered until the executor runs.
+ verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+ mExecutor.runAll();
+ // Ensure proper disconnect reason reported.
+ verify(mCallback, times(1)).onDisconnected(mConnection,
+ ObservableServiceConnection.DISCONNECT_REASON_DISCONNECTED);
+ // Verify unbound from service.
+ verify(mContext, times(1)).unbindService(mConnection);
+
+ clearInvocations(mContext);
+ // Ensure unbind after disconnect has no effect on the connection
+ mConnection.unbind();
+ verify(mContext, never()).unbindService(mConnection);
+ }
+
+ @Test
+ public void testBindingDied() {
+ mConnection.addCallback(mCallback);
+ mExecutor.runAll();
+ mConnection.bind();
+ mConnection.onBindingDied(COMPONENT_NAME);
+
+ // Ensure the callback doesn't get triggered until the executor runs.
+ verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+ mExecutor.runAll();
+ // Ensure proper disconnect reason reported.
+ verify(mCallback, times(1)).onDisconnected(mConnection,
+ ObservableServiceConnection.DISCONNECT_REASON_BINDING_DIED);
+ // Verify unbound from service.
+ verify(mContext, times(1)).unbindService(mConnection);
+ }
+
+ @Test
+ public void testNullBinding() {
+ mConnection.addCallback(mCallback);
+ mExecutor.runAll();
+ mConnection.bind();
+ mConnection.onNullBinding(COMPONENT_NAME);
+
+ // Ensure the callback doesn't get triggered until the executor runs.
+ verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+ mExecutor.runAll();
+ // Ensure proper disconnect reason reported.
+ verify(mCallback, times(1)).onDisconnected(mConnection,
+ ObservableServiceConnection.DISCONNECT_REASON_NULL_BINDING);
+ // Verify unbound from service.
+ verify(mContext, times(1)).unbindService(mConnection);
+ }
+
+ @Test
+ public void testUnbind() {
+ mConnection.addCallback(mCallback);
+ mExecutor.runAll();
+ mConnection.bind();
+ mConnection.unbind();
+
+ // Ensure the callback doesn't get triggered until the executor runs.
+ verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+ mExecutor.runAll();
+ verify(mCallback).onDisconnected(mConnection,
+ ObservableServiceConnection.DISCONNECT_REASON_UNBIND);
+ }
+
+ static class FakeExecutor implements Executor {
+ private final Queue<Runnable> mQueue = new ArrayDeque<>();
+
+ @Override
+ public void execute(Runnable command) {
+ mQueue.add(command);
+ }
+
+ public void runAll() {
+ while (!mQueue.isEmpty()) {
+ mQueue.remove().run();
+ }
+ }
+
+ public void clearAll() {
+ while (!mQueue.isEmpty()) {
+ mQueue.remove();
+ }
+ }
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/PersistentServiceConnectionTest.java b/core/tests/utiltests/src/com/android/internal/util/PersistentServiceConnectionTest.java
new file mode 100644
index 0000000..fee4654
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/PersistentServiceConnectionTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.util;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import com.android.internal.util.ObservableServiceConnection.ServiceTransformer;
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+public class PersistentServiceConnectionTest {
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName("test.package", "component");
+ private static final int MAX_RETRIES = 2;
+ private static final int RETRY_DELAY_MS = 1000;
+ private static final int CONNECTION_MIN_DURATION_MS = 5000;
+ private PersistentServiceConnection<Proxy> mConnection;
+
+ public static class Proxy {
+ }
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Intent mIntent;
+ @Mock
+ private Proxy mResult;
+ @Mock
+ private IBinder mBinder;
+ @Mock
+ private ServiceTransformer<Proxy> mTransformer;
+ @Mock
+ private ObservableServiceConnection.Callback<Proxy> mCallback;
+ private TestHandler mHandler;
+ private final FakeExecutor mFakeExecutor = new FakeExecutor();
+ private OffsettableClock mClock;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mClock = new OffsettableClock.Stopped();
+ mHandler = spy(new TestHandler(null, mClock));
+
+ mConnection = new PersistentServiceConnection<>(
+ mContext,
+ mFakeExecutor,
+ mHandler,
+ mTransformer,
+ mIntent,
+ /* flags= */ Context.BIND_AUTO_CREATE,
+ CONNECTION_MIN_DURATION_MS,
+ MAX_RETRIES,
+ RETRY_DELAY_MS,
+ new TestInjector(mClock));
+
+ mClock.fastForward(1000);
+ mConnection.addCallback(mCallback);
+ when(mTransformer.convert(mBinder)).thenReturn(mResult);
+ }
+
+ @After
+ public void tearDown() {
+ mFakeExecutor.clearAll();
+ }
+
+ @Test
+ public void testConnect() {
+ mConnection.bind();
+ mConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+ mFakeExecutor.runAll();
+ // Ensure that we did not schedule a retry
+ verify(mHandler, never()).postDelayed(any(), anyLong());
+ }
+
+ @Test
+ public void testRetryOnBindFailure() {
+ mConnection.bind();
+
+ verify(mContext, times(1)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+
+ // After disconnect, a reconnection should be attempted after the RETRY_DELAY_MS
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+ mFakeExecutor.runAll();
+ advanceTime(RETRY_DELAY_MS);
+ verify(mContext, times(2)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+
+ // Reconnect attempt #2
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+ mFakeExecutor.runAll();
+ advanceTime(RETRY_DELAY_MS * 2);
+ verify(mContext, times(3)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+
+ // There should be no more reconnect attempts, since the maximum is 2
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+ mFakeExecutor.runAll();
+ advanceTime(RETRY_DELAY_MS * 4);
+ verify(mContext, times(3)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+ }
+
+ @Test
+ public void testManualUnbindDoesNotReconnect() {
+ mConnection.bind();
+
+ verify(mContext, times(1)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+
+ mConnection.unbind();
+ // Ensure that disconnection after unbind does not reconnect.
+ mConnection.onServiceDisconnected(COMPONENT_NAME);
+ mFakeExecutor.runAll();
+ advanceTime(RETRY_DELAY_MS);
+
+ verify(mContext, times(1)).bindService(
+ eq(mIntent),
+ anyInt(),
+ eq(mFakeExecutor),
+ eq(mConnection));
+ }
+
+ private void advanceTime(long millis) {
+ mClock.fastForward(millis);
+ mHandler.timeAdvance();
+ }
+
+ static class TestInjector extends PersistentServiceConnection.Injector {
+ private final OffsettableClock mClock;
+
+ TestInjector(OffsettableClock clock) {
+ mClock = clock;
+ }
+
+ @Override
+ public long uptimeMillis() {
+ return mClock.now();
+ }
+ }
+
+ static class FakeExecutor implements Executor {
+ private final Queue<Runnable> mQueue = new ArrayDeque<>();
+
+ @Override
+ public void execute(Runnable command) {
+ mQueue.add(command);
+ }
+
+ public void runAll() {
+ while (!mQueue.isEmpty()) {
+ mQueue.remove().run();
+ }
+ }
+
+ public void clearAll() {
+ while (!mQueue.isEmpty()) {
+ mQueue.remove();
+ }
+ }
+ }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index b1ecb43..31e2abe 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1087,6 +1087,12 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "-1043981272": {
+ "message": "Reverting orientation. Rotating to %s from %s rather than %s.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotation.java"
+ },
"-1042574499": {
"message": "Attempted to add Accessibility overlay window with unknown token %s. Aborting.",
"level": "WARN",
@@ -4285,6 +4291,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "2066210760": {
+ "message": "foldStateChanged: displayId %d, halfFoldStateChanged %s, saved rotation: %d, mUserRotation: %d, mLastSensorRotation: %d, mLastOrientation: %d, mRotation: %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotation.java"
+ },
"2070726247": {
"message": "InsetsSource updateVisibility for %s, serverVisible: %s clientVisible: %s",
"level": "DEBUG",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index fb0a9db..7e9c418 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 @@
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 1;
+ return 2;
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 203ece0..bf7326a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -20,10 +20,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
-import static android.window.TaskFragmentOrganizer.getTransitionType;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -76,6 +77,7 @@
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.extensions.WindowExtensionsProvider;
+import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;
@@ -100,6 +102,10 @@
@GuardedBy("mLock")
final SplitPresenter mPresenter;
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ final TransactionManager mTransactionManager;
+
// Currently applied split configuration.
@GuardedBy("mLock")
private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
@@ -150,6 +156,7 @@
final MainThreadExecutor executor = new MainThreadExecutor();
mHandler = executor.mHandler;
mPresenter = new SplitPresenter(executor, this);
+ mTransactionManager = new TransactionManager(mPresenter);
final ActivityThread activityThread = ActivityThread.currentActivityThread();
final Application application = activityThread.getApplication();
// Register a callback to be notified about activities being created.
@@ -167,7 +174,9 @@
@Override
public void accept(List<CommonFoldingFeature> foldingFeatures) {
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
for (int i = 0; i < mTaskContainers.size(); i++) {
final TaskContainer taskContainer = mTaskContainers.valueAt(i);
if (!taskContainer.isVisible()) {
@@ -186,7 +195,9 @@
updateContainersInTask(wct, taskContainer);
updateAnimationOverride(taskContainer);
}
- mPresenter.applyTransaction(wct);
+ // The WCT should be applied and merged to the device state change transition if
+ // there is one.
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
}
@@ -240,13 +251,25 @@
}
/**
+ * Clears the listener set in {@link SplitController#setSplitInfoListener}.
+ */
+ @Override
+ public void clearSplitInfoCallback() {
+ synchronized (mLock) {
+ mEmbeddingCallback = null;
+ }
+ }
+
+ /**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
*/
@Override
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(
+ transaction.getTransactionToken());
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
for (TaskFragmentTransaction.Change change : changes) {
final int taskId = change.getTaskId();
@@ -297,8 +320,7 @@
// Notify the server, and the server should apply and merge the
// WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction.
- mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct,
- getTransitionType(wct), false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
}
}
@@ -323,6 +345,7 @@
container.setInfo(wct, taskFragmentInfo);
if (container.isFinished()) {
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else {
// Update with the latest Task configuration.
@@ -358,15 +381,18 @@
// Do not finish the dependents if the last activity is reparented to PiP.
// Instead, the original split should be cleanup, and the dependent may be
// expanded to fullscreen.
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
cleanupForEnterPip(wct, container);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (taskFragmentInfo.isTaskClearedForReuse()) {
// Do not finish the dependents if this TaskFragment was cleared due to
// launching activity in the Task.
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (!container.isWaitingActivityAppear()) {
// Do not finish the container before the expected activity appear until
// timeout.
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
}
} else if (wasInPip && isInPip) {
@@ -561,6 +587,7 @@
container.setInfo(wct, taskFragmentInfo);
container.clearPendingAppearedActivities();
if (container.isEmpty()) {
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
break;
@@ -999,11 +1026,10 @@
*/
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- onTaskFragmentAppearEmptyTimeout(wct, container);
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container);
// Can be applied independently as a timeout callback.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- true /* shouldApplyIndependently */);
+ transactionRecord.apply(true /* shouldApplyIndependently */);
}
/**
@@ -1013,6 +1039,7 @@
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
@@ -1552,6 +1579,7 @@
* @param isOnCreated whether this happens during the primary activity onCreated.
*/
@VisibleForTesting
+ @GuardedBy("mLock")
@Nullable
Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) {
// Setting avoid move to front will also skip the animation. We only want to do that when
@@ -1559,6 +1587,8 @@
// Check if the primary is resumed or if this is called when the primary is onCreated
// (not resumed yet).
if (isOnCreated || primaryActivity.isResumed()) {
+ // Only set trigger type if the launch happens in foreground.
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_OPEN);
return null;
}
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1585,6 +1615,8 @@
if (SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
+
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
false /* shouldFinishDependent */);
return true;
@@ -1895,23 +1927,26 @@
// that we don't launch it if an activity itself already requested something to be
// launched to side.
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- SplitController.this.onActivityCreated(wct, activity);
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_OPEN);
+ SplitController.this.onActivityCreated(transactionRecord.getTransaction(),
+ activity);
// The WCT should be applied and merged to the activity launch transition.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@Override
public void onActivityConfigurationChanged(@NonNull Activity activity) {
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- SplitController.this.onActivityConfigurationChanged(wct, activity);
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ SplitController.this.onActivityConfigurationChanged(
+ transactionRecord.getTransaction(), activity);
// The WCT should be applied and merged to the Task change transition so that the
// placeholder is launched in the same transition.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@@ -1967,7 +2002,10 @@
}
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_OPEN);
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
@@ -1980,13 +2018,14 @@
if (launchedInTaskFragment != null) {
// Make sure the WCT is applied immediately instead of being queued so that the
// TaskFragment will be ready before activity attachment.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
// Amend the request to let the WM know that the activity should be placed in
// the dedicated container.
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
mCurrentIntent = intent;
+ } else {
+ transactionRecord.abort();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 91573ff..00943f2d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -140,7 +140,7 @@
void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.getDisplayId();
- mIsVisible = info.isVisibleRequested();
+ mIsVisible = info.isVisible();
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
new file mode 100644
index 0000000..0071fea
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_NONE;
+
+import android.os.IBinder;
+import android.view.WindowManager.TransitionType;
+import android.window.TaskFragmentOrganizer;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Responsible for managing the current {@link WindowContainerTransaction} as a response to device
+ * state changes and app interactions.
+ *
+ * A typical use flow:
+ * 1. Call {@link #startNewTransaction} to start tracking the changes.
+ * 2. Use {@link TransactionRecord#setOriginType(int)} (int)} to record the type of operation that
+ * will start a new transition on system server.
+ * 3. Use {@link #getCurrentTransactionRecord()} to get current {@link TransactionRecord} for
+ * changes.
+ * 4. Call {@link TransactionRecord#apply(boolean)} to request the system server to apply changes in
+ * the current {@link WindowContainerTransaction}, or call {@link TransactionRecord#abort()} to
+ * dispose the current one.
+ *
+ * Note:
+ * There should be only one transaction at a time. The caller should not call
+ * {@link #startNewTransaction} again before calling {@link TransactionRecord#apply(boolean)} or
+ * {@link TransactionRecord#abort()} to the previous transaction.
+ */
+class TransactionManager {
+
+ @NonNull
+ private final TaskFragmentOrganizer mOrganizer;
+
+ @Nullable
+ private TransactionRecord mCurrentTransaction;
+
+ TransactionManager(@NonNull TaskFragmentOrganizer organizer) {
+ mOrganizer = organizer;
+ }
+
+ @NonNull
+ TransactionRecord startNewTransaction() {
+ return startNewTransaction(null /* taskFragmentTransactionToken */);
+ }
+
+ /**
+ * Starts tracking the changes in a new {@link WindowContainerTransaction}. Caller can call
+ * {@link #getCurrentTransactionRecord()} later to continue adding change to the current
+ * transaction until {@link TransactionRecord#apply(boolean)} or
+ * {@link TransactionRecord#abort()} is called.
+ * @param taskFragmentTransactionToken {@link android.window.TaskFragmentTransaction
+ * #getTransactionToken()} if this is a response to a
+ * {@link android.window.TaskFragmentTransaction}.
+ */
+ @NonNull
+ TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) {
+ if (mCurrentTransaction != null) {
+ mCurrentTransaction = null;
+ throw new IllegalStateException(
+ "The previous transaction has not been applied or aborted,");
+ }
+ mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken);
+ return mCurrentTransaction;
+ }
+
+ /**
+ * Gets the current {@link TransactionRecord} started from {@link #startNewTransaction}.
+ */
+ @NonNull
+ TransactionRecord getCurrentTransactionRecord() {
+ if (mCurrentTransaction == null) {
+ throw new IllegalStateException("startNewTransaction() is not invoked before calling"
+ + " getCurrentTransactionRecord().");
+ }
+ return mCurrentTransaction;
+ }
+
+ /** The current transaction. The manager should only handle one transaction at a time. */
+ class TransactionRecord {
+ /**
+ * {@link WindowContainerTransaction} containing the current change.
+ * @see #startNewTransaction(IBinder)
+ * @see #apply (boolean)
+ */
+ @NonNull
+ private final WindowContainerTransaction mTransaction = new WindowContainerTransaction();
+
+ /**
+ * If the current transaction is a response to a
+ * {@link android.window.TaskFragmentTransaction}, this is the
+ * {@link android.window.TaskFragmentTransaction#getTransactionToken()}.
+ * @see #startNewTransaction(IBinder)
+ */
+ @Nullable
+ private final IBinder mTaskFragmentTransactionToken;
+
+ /**
+ * To track of the origin type of the current {@link #mTransaction}. When
+ * {@link #apply (boolean)} to start a new transition, this is the type to request.
+ * @see #setOriginType(int)
+ * @see #getTransactionTransitionType()
+ */
+ @TransitionType
+ private int mOriginType = TRANSIT_NONE;
+
+ TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) {
+ mTaskFragmentTransactionToken = taskFragmentTransactionToken;
+ }
+
+ @NonNull
+ WindowContainerTransaction getTransaction() {
+ ensureCurrentTransaction();
+ return mTransaction;
+ }
+
+ /**
+ * Sets the {@link TransitionType} that triggers this transaction. If there are multiple
+ * calls, only the first call will be respected as the "origin" type.
+ */
+ void setOriginType(@TransitionType int type) {
+ ensureCurrentTransaction();
+ if (mOriginType != TRANSIT_NONE) {
+ // Skip if the origin type has already been set.
+ return;
+ }
+ mOriginType = type;
+ }
+
+ /**
+ * Requests the system server to apply the current transaction started from
+ * {@link #startNewTransaction}.
+ * @param shouldApplyIndependently If {@code true}, the {@link #mCurrentTransaction} will
+ * request a new transition, which will be queued until the
+ * sync engine is free if there is any other active sync.
+ * If {@code false}, the {@link #startNewTransaction} will
+ * be directly applied to the active sync.
+ */
+ void apply(boolean shouldApplyIndependently) {
+ ensureCurrentTransaction();
+ if (mTaskFragmentTransactionToken != null) {
+ // If this is a response to a TaskFragmentTransaction.
+ mOrganizer.onTransactionHandled(mTaskFragmentTransactionToken, mTransaction,
+ getTransactionTransitionType(), shouldApplyIndependently);
+ } else {
+ mOrganizer.applyTransaction(mTransaction, getTransactionTransitionType(),
+ shouldApplyIndependently);
+ }
+ dispose();
+ }
+
+ /** Called when there is no need to {@link #apply(boolean)} the current transaction. */
+ void abort() {
+ ensureCurrentTransaction();
+ dispose();
+ }
+
+ private void dispose() {
+ TransactionManager.this.mCurrentTransaction = null;
+ }
+
+ private void ensureCurrentTransaction() {
+ if (TransactionManager.this.mCurrentTransaction != this) {
+ throw new IllegalStateException(
+ "This transaction has already been apply() or abort().");
+ }
+ }
+
+ /**
+ * Gets the {@link TransitionType} that we will request transition with for the
+ * current {@link WindowContainerTransaction}.
+ */
+ @VisibleForTesting
+ @TransitionType
+ int getTransactionTransitionType() {
+ // Use TRANSIT_CHANGE as default if there is not opening/closing window.
+ return mOriginType != TRANSIT_NONE ? mOriginType : TRANSIT_CHANGE;
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index c76f568..b516e140 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -103,13 +103,20 @@
/**
* Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context
* as a parameter.
+ *
+ * Jetpack {@link androidx.window.layout.ExtensionWindowLayoutInfoBackend} makes sure all
+ * consumers related to the same {@link Context} gets updated {@link WindowLayoutInfo}
+ * together. However only the first registered consumer of a {@link Context} will actually
+ * invoke {@link #addWindowLayoutInfoListener(Context, Consumer)}.
+ * Here we enforce that {@link #addWindowLayoutInfoListener(Context, Consumer)} can only be
+ * called once for each {@link Context}.
*/
- // TODO(b/204073440): Add @Override to hook the API in WM extensions library.
+ @Override
public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
@NonNull Consumer<WindowLayoutInfo> consumer) {
if (mWindowLayoutChangeListeners.containsKey(context)
+ // In theory this method can be called on the same consumer with different context.
|| mWindowLayoutChangeListeners.containsValue(consumer)) {
- // Early return if the listener or consumer has been registered.
return;
}
if (!context.isUiContext()) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 25d0347..a403031 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -132,6 +132,7 @@
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
+ private TransactionManager mTransactionManager;
@Before
public void setUp() {
@@ -140,8 +141,10 @@
.getCurrentWindowLayoutInfo(anyInt(), any());
mSplitController = new SplitController(mWindowLayoutComponent);
mSplitPresenter = mSplitController.mPresenter;
+ mTransactionManager = mSplitController.mTransactionManager;
spyOn(mSplitController);
spyOn(mSplitPresenter);
+ spyOn(mTransactionManager);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -212,6 +215,8 @@
@Test
public void testOnTaskFragmentAppearEmptyTimeout() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any());
mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
@@ -615,6 +620,8 @@
@Test
public void testResolveActivityToContainer_placeholderRule_notInTaskFragment() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -647,6 +654,8 @@
@Test
public void testResolveActivityToContainer_placeholderRule_inTopMostTaskFragment() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -679,6 +688,8 @@
@Test
public void testResolveActivityToContainer_placeholderRule_inSecondarySplit() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -961,6 +972,8 @@
@Test
public void testGetPlaceholderOptions() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
doReturn(true).when(mActivity).isResumed();
assertNull(mSplitController.getPlaceholderOptions(mActivity, false /* isOnCreated */));
@@ -1147,8 +1160,6 @@
+ "of other properties",
SplitController.haveSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
-
-
}
/** Creates a mock activity in the organizer process. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java
new file mode 100644
index 0000000..62006bd
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.clearInvocations;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.window.TaskFragmentOrganizer;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link TransactionManager}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:TransactionManagerTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TransactionManagerTest {
+
+ @Mock
+ private TaskFragmentOrganizer mOrganizer;
+ private TransactionManager mTransactionManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTransactionManager = new TransactionManager(mOrganizer);
+ }
+
+ @Test
+ public void testStartNewTransaction() {
+ mTransactionManager.startNewTransaction();
+
+ // Throw exception if #startNewTransaction is called twice without #apply() or #abort().
+ assertThrows(IllegalStateException.class, mTransactionManager::startNewTransaction);
+
+ // Allow to start new after #apply() the last transaction.
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ transactionRecord = mTransactionManager.startNewTransaction();
+
+ // Allow to start new after #abort() the last transaction.
+ transactionRecord.abort();
+ mTransactionManager.startNewTransaction();
+ }
+
+ @Test
+ public void testSetTransactionOriginType() {
+ // Return TRANSIT_CHANGE if there is no trigger type set.
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+
+ assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
+
+ // Return the first set type.
+ mTransactionManager.getCurrentTransactionRecord().abort();
+ transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_OPEN);
+
+ assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
+
+ transactionRecord.setOriginType(TRANSIT_CLOSE);
+
+ assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
+
+ // Reset when #startNewTransaction().
+ transactionRecord.abort();
+ transactionRecord = mTransactionManager.startNewTransaction();
+
+ assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
+ }
+
+ @Test
+ public void testGetCurrentTransactionRecord() {
+ // Throw exception if #getTransaction is called without calling #startNewTransaction().
+ assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ assertNotNull(transactionRecord);
+
+ // Same WindowContainerTransaction should be returned.
+ assertSame(transactionRecord, mTransactionManager.getCurrentTransactionRecord());
+
+ // Reset after #abort().
+ transactionRecord.abort();
+ assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+
+ // New WindowContainerTransaction after #startNewTransaction().
+ mTransactionManager.startNewTransaction();
+ assertNotEquals(transactionRecord, mTransactionManager.getCurrentTransactionRecord());
+
+ // Reset after #apply().
+ mTransactionManager.getCurrentTransactionRecord().apply(
+ false /* shouldApplyIndependently */);
+ assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+ }
+
+ @Test
+ public void testApply() {
+ // #applyTransaction(false)
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ int transitionType = transactionRecord.getTransactionTransitionType();
+ WindowContainerTransaction wct = transactionRecord.getTransaction();
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+
+ verify(mOrganizer).applyTransaction(wct, transitionType,
+ false /* shouldApplyIndependently */);
+
+ // #applyTransaction(true)
+ clearInvocations(mOrganizer);
+ transactionRecord = mTransactionManager.startNewTransaction();
+ transitionType = transactionRecord.getTransactionTransitionType();
+ wct = transactionRecord.getTransaction();
+ transactionRecord.apply(true /* shouldApplyIndependently */);
+
+ verify(mOrganizer).applyTransaction(wct, transitionType,
+ true /* shouldApplyIndependently */);
+
+ // #onTransactionHandled(false)
+ clearInvocations(mOrganizer);
+ IBinder token = new Binder();
+ transactionRecord = mTransactionManager.startNewTransaction(token);
+ transitionType = transactionRecord.getTransactionTransitionType();
+ wct = transactionRecord.getTransaction();
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+
+ verify(mOrganizer).onTransactionHandled(token, wct, transitionType,
+ false /* shouldApplyIndependently */);
+
+ // #onTransactionHandled(true)
+ clearInvocations(mOrganizer);
+ token = new Binder();
+ transactionRecord = mTransactionManager.startNewTransaction(token);
+ transitionType = transactionRecord.getTransactionTransitionType();
+ wct = transactionRecord.getTransaction();
+ transactionRecord.apply(true /* shouldApplyIndependently */);
+
+ verify(mOrganizer).onTransactionHandled(token, wct, transitionType,
+ true /* shouldApplyIndependently */);
+
+ // Throw exception if there is any more interaction.
+ final TransactionRecord record = transactionRecord;
+ assertThrows(IllegalStateException.class,
+ () -> record.apply(false /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ () -> record.apply(true /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ record::abort);
+ }
+
+ @Test
+ public void testAbort() {
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.abort();
+
+ // Throw exception if there is any more interaction.
+ verifyNoMoreInteractions(mOrganizer);
+ assertThrows(IllegalStateException.class,
+ () -> transactionRecord.apply(false /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ () -> transactionRecord.apply(true /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ transactionRecord::abort);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 2c766d8..4978e04 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 7960dec..f615ad6 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -44,6 +44,10 @@
srcs: [
"src/com/android/wm/shell/util/**/*.java",
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
+ "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
+ "src/com/android/wm/shell/common/TransactionPool.java",
+ "src/com/android/wm/shell/animation/Interpolators.java",
+ "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
],
path: "src",
}
@@ -100,6 +104,21 @@
out: ["wm_shell_protolog.json"],
}
+genrule {
+ name: "protolog.json.gz",
+ srcs: [":generate-wm_shell_protolog.json"],
+ out: ["wmshell.protolog.json.gz"],
+ cmd: "$(location minigzip) -c < $(in) > $(out)",
+ tools: ["minigzip"],
+}
+
+prebuilt_etc {
+ name: "wmshell.protolog.json.gz",
+ system_ext_specific: true,
+ src: ":protolog.json.gz",
+ filename_from_src: true,
+}
+
// End ProtoLog
java_library {
@@ -123,9 +142,6 @@
resource_dirs: [
"res",
],
- java_resources: [
- ":generate-wm_shell_protolog.json",
- ],
static_libs: [
"androidx.appcompat_appcompat",
"androidx.arch.core_core-runtime",
diff --git a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
new file mode 100644
index 0000000..5ecba38
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+ >
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="4.0"
+ android:translateY="4.0" >
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="MM24,40.3 L7.7,24 24,7.7 26.8,10.45 15.3,22H40.3V26H15.3L26.8,37.5Z"/>
+
+ </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
index 8207365..416287d 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
@@ -15,8 +15,7 @@
~ limitations under the License.
-->
<shape android:shape="rectangle"
- android:tintMode="multiply"
- android:tint="@color/decor_caption_title_color"
xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/colorPrimary" />
+ <solid android:color="@android:color/white" />
+ <corners android:radius="20dp" />
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
index f2f1a1d..cf9e632 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
@@ -18,15 +18,13 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="32.0"
- android:viewportHeight="32.0"
- android:tint="@color/decor_button_dark_color"
- >
+ android:viewportHeight="32.0">
<group android:scaleX="0.5"
android:scaleY="0.5"
- android:translateX="8.0"
- android:translateY="8.0" >
+ android:translateX="4.0"
+ android:translateY="4.0" >
<path
- android:fillColor="@android:color/white"
- android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
+ android:fillColor="@android:color/black"
+ android:pathData="M12.45,38.35 L9.65,35.55 21.2,24 9.65,12.45 12.45,9.65 24,21.2 35.55,9.65 38.35,12.45 26.8,24 38.35,35.55 35.55,38.35 24,26.8Z"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
new file mode 100644
index 0000000..c9f2623
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <group android:translateY="8.0">
+ <path
+ android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
index d183e42..38cd570 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -17,39 +17,33 @@
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/caption"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="end"
+ android:gravity="center_horizontal"
android:background="@drawable/decor_caption_title">
<Button
- android:id="@+id/minimize_window"
- android:visibility="gone"
+ android:id="@+id/back_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
- android:layout_gravity="top|end"
- android:contentDescription="@string/maximize_button_text"
- android:background="@drawable/decor_minimize_button_dark"
- android:duplicateParentState="true"/>
+ android:contentDescription="@string/back_button_text"
+ android:background="@drawable/decor_back_button_dark"
+ />
<Button
- android:id="@+id/maximize_window"
- android:layout_width="32dp"
+ android:id="@+id/caption_handle"
+ android:layout_width="128dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
- android:layout_gravity="center_vertical|end"
- android:contentDescription="@string/maximize_button_text"
- android:background="@drawable/decor_maximize_button_dark"
- android:duplicateParentState="true"/>
+ android:contentDescription="@string/handle_text"
+ android:background="@drawable/decor_handle_dark"/>
<Button
android:id="@+id/close_window"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
- android:layout_gravity="center_vertical|end"
android:contentDescription="@string/close_button_text"
- android:background="@drawable/decor_close_button_dark"
- android:duplicateParentState="true"/>
-</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
+ android:background="@drawable/decor_close_button_dark"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index cc60074..f9d153f 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 7aa8770..0fdfed8 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 2d0d970..351e192 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 42153ff..765aaba 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 5da5a97..0ec5db1 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 aff2b9f..4dda1db 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 e325e51..bd4a079 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 3bedc86..a04a50d 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 35e5b38..b652d383 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 34b282b..2cc0ab3 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 0279917..a2badaf 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 d58100b..17f7374 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 abb9cea..084ea86 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 a579394..195c335 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 248cff8..2f92946 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 9c798b8..9c88e78 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 9c798b8..9c88e78 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 9c798b8..9c88e78 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 9c798b8..9c88e78 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 5ff5c58..646b3c5 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 08a7f49..0ad387b 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 139232c..4b85a22 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 d6f3580..cc90578 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 e0efadd6..d1fca9b 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 267dd20..a1d0b58 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 30321a2..5df1e04 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 1278997..4c59b99 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 84c8618..c4b386a 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 59ec2de..f5a106d 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 85d98ed..5d52c58 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 bd90b7e..95484d5 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 41df0df..32ab87c 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 9736a69..dba7f4e 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 3c64494..5ae72eb 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 61f3903..d36a83f 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 044348c..31d483e 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 ca97693..4e8ac62 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 67de8a0..5c9425e 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 cc8527e..d9f514a 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 0ef4563..787ac3e 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 6ba9a68..927f0d7 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 03682d8..955b2f8 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 167a38e..57c7124 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 1175a0e..a69e105 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 b047863..ab51ca0 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 00bf7f4..15bde88 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 3981793..275efa2 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 3599c14..3048630 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 6c41b0c..64ce3f6 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 76dfc0d9..9013828 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 a8bd85e..be02be1 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 5874812..dc884e9 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 88270df..85d380e 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 9a2d1ba..517098d 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 ec9e635d..a2f24f2 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 dc7d98d..6a70d8d 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 5bae2a3..d1d7db8 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 c5ef4f1..52280a1 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 f026205..d2a96d9 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 22c0c37..cc2b2c3 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 0bbffb3..3cb708d 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 07dd4d7..e5be578 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 0bbffb3..3cb708d 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 0aae6a5..c03e043 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 f1ff000..98a775e 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 39bd260..5724061 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 8859231..e3dafb6 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 22fe0f8..12f022e 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 2a3671b..cdd8706 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 09471037..b00c4f4 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 70282a4b..fef3792 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 1aab85c..2d10e20 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 befc8eb..0eeeca7 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 88bb130..91c411f 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -28,7 +28,7 @@
<string name="pip_pause" msgid="690688849510295232">"పాజ్ చేయి"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"దాటవేసి తర్వాత దానికి వెళ్లు"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"దాటవేసి మునుపటి దానికి వెళ్లు"</string>
- <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"పరిమాణం మార్చు"</string>
+ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"సైజ్ మార్చు"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"స్టాచ్"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్స్టాచ్"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్ పని చేయకపోవచ్చు."</string>
@@ -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 63d3bb8..ee362dc 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 50334f5..d8203656 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 e1ea0df..2b105bdb 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 9e713c7..a092531 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 596cf4b..883026a 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 275940a..0330125 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 24fb1ca..6e4a768 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 e52a7b5..df911ed 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 eed1e52..5a497d0 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 f9f28ab..2c2ce33 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 ddb6a4c..f484022 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Nciphisa"</string>
<string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Emuva"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Isibambo"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 1d1162d..d8a5074 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -193,4 +193,8 @@
<string name="minimize_button_text">Minimize</string>
<!-- Accessibility text for the close window button [CHAR LIMIT=NONE] -->
<string name="close_button_text">Close</string>
+ <!-- Accessibility text for the caption back button [CHAR LIMIT=NONE] -->
+ <string name="back_button_text">Back</string>
+ <!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
+ <string name="handle_text">Handle</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 591e347..215308d 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 @@
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 756d802..490975c 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.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 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 @@
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 @@
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 @@
*/
@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 @@
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 @@
*/
@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 @@
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 @@
}
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 @@
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 eb6ac76..58b2366 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 @@
@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 @@
@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/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 86f9d5b..8cbe44b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -29,13 +29,6 @@
public interface BackAnimation {
/**
- * Returns a binder that can be passed to an external process to update back animations.
- */
- default IBackAnimation createExternalInterface() {
- return null;
- }
-
- /**
* Called when a {@link MotionEvent} is generated by a back gesture.
*
* @param touchX the X touch position of the {@link MotionEvent}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 33ecdd8..938189f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -21,6 +21,7 @@
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -57,10 +58,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -105,6 +108,7 @@
private final IActivityTaskManager mActivityTaskManager;
private final Context mContext;
private final ContentResolver mContentResolver;
+ private final ShellController mShellController;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
@Nullable
@@ -231,21 +235,25 @@
public BackAnimationController(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context) {
- this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
- ActivityTaskManager.getService(), context, context.getContentResolver());
+ this(shellInit, shellController, shellExecutor, backgroundHandler,
+ new SurfaceControl.Transaction(), ActivityTaskManager.getService(),
+ context, context.getContentResolver());
}
@VisibleForTesting
BackAnimationController(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull SurfaceControl.Transaction transaction,
@NonNull IActivityTaskManager activityTaskManager,
Context context, ContentResolver contentResolver) {
+ mShellController = shellController;
mShellExecutor = shellExecutor;
mTransaction = transaction;
mActivityTaskManager = activityTaskManager;
@@ -257,6 +265,8 @@
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
+ this::createExternalInterface, this);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -289,7 +299,11 @@
return mBackAnimation;
}
- private final BackAnimation mBackAnimation = new BackAnimationImpl();
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IBackAnimationImpl(this);
+ }
+
+ private final BackAnimationImpl mBackAnimation = new BackAnimationImpl();
@Override
public Context getContext() {
@@ -302,17 +316,6 @@
}
private class BackAnimationImpl implements BackAnimation {
- private IBackAnimationImpl mBackAnimation;
-
- @Override
- public IBackAnimation createExternalInterface() {
- if (mBackAnimation != null) {
- mBackAnimation.invalidate();
- }
- mBackAnimation = new IBackAnimationImpl(BackAnimationController.this);
- return mBackAnimation;
- }
-
@Override
public void onBackMotion(
float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
@@ -331,7 +334,8 @@
}
}
- private static class IBackAnimationImpl extends IBackAnimation.Stub {
+ private static class IBackAnimationImpl extends IBackAnimation.Stub
+ implements ExternalInterfaceBinder {
private BackAnimationController mController;
IBackAnimationImpl(BackAnimationController controller) {
@@ -356,7 +360,8 @@
(controller) -> controller.onBackToLauncherAnimationFinished());
}
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
}
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 93413db..725b205 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_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_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.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 @@
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 @@
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 @@
}
mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
+ if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) {
+ mBubblePositioner.setUsePinnedLocation(true);
+ } else {
+ mBubblePositioner.setUsePinnedLocation(false);
+ }
addToWindowManagerMaybe();
}
@@ -1732,13 +1674,6 @@
}
@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 dbad5df..07c5852 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 @@
* 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 @@
}
/**
- * 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 be100bb..6efad09 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 @@
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 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 @@
// 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 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 @@
// 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 b3104b5..7f891ec 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 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 @@
@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 961722b..0ee0ea6 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 @@
}
/**
+ * 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/common/ExternalInterfaceBinder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
new file mode 100644
index 0000000..aa5b0cb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import android.os.IBinder;
+
+/**
+ * An interface for binders which can be registered to be sent to other processes.
+ */
+public interface ExternalInterfaceBinder {
+ /**
+ * Invalidates this binder (detaches it from the controller it would call).
+ */
+ void invalidate();
+
+ /**
+ * Returns the IBinder to send.
+ */
+ IBinder asBinder();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 419e62d..c2ad1a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -118,6 +118,7 @@
private boolean mFreezeDividerWindow = false;
private int mOrientation;
private int mRotation;
+ private int mDensity;
private final boolean mDimNonImeSide;
@@ -290,9 +291,11 @@
final int rotation = configuration.windowConfiguration.getRotation();
final Rect rootBounds = configuration.windowConfiguration.getBounds();
final int orientation = configuration.orientation;
+ final int density = configuration.densityDpi;
if (mOrientation == orientation
&& mRotation == rotation
+ && mDensity == density
&& mRootBounds.equals(rootBounds)) {
return false;
}
@@ -303,6 +306,7 @@
mTempRect.set(mRootBounds);
mRootBounds.set(rootBounds);
mRotation = rotation;
+ mDensity = density;
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
initDividerPosition(mTempRect);
updateInvisibleRect();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 7d0c4bd..28a1959 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -261,13 +261,14 @@
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
ShellInit shellInit,
+ ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
- new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
- context));
+ new BackAnimationController(shellInit, shellController, shellExecutor,
+ backgroundHandler, context));
}
return Optional.empty();
}
@@ -471,6 +472,7 @@
static Optional<RecentTasksController> provideRecentTasksController(
Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
@@ -478,9 +480,9 @@
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
- RecentTasksController.create(context, shellInit, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopModeTaskRepository,
- mainExecutor));
+ RecentTasksController.create(context, shellInit, shellController,
+ shellCommandHandler, taskStackListener, activityTaskManager,
+ desktopModeTaskRepository, mainExecutor));
}
//
@@ -497,14 +499,15 @@
@Provides
static Transitions provideTransitions(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer organizer,
TransactionPool pool,
DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(context, shellInit, organizer, pool, displayController, mainExecutor,
- mainHandler, animExecutor);
+ return new Transitions(context, shellInit, shellController, organizer, pool,
+ displayController, mainExecutor, mainHandler, animExecutor);
}
@WMSingleton
@@ -621,13 +624,15 @@
@WMSingleton
@Provides
- static StartingWindowController provideStartingWindowController(Context context,
+ static StartingWindowController provideStartingWindowController(
+ Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
@ShellSplashscreenThread ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
TransactionPool pool) {
- return new StartingWindowController(context, shellInit, shellTaskOrganizer,
+ return new StartingWindowController(context, shellInit, shellController, shellTaskOrganizer,
splashScreenExecutor, startingWindowTypeAlgorithm, iconProvider, pool);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 461d7dc..f1670cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -191,13 +191,13 @@
SyncTransactionQueue syncQueue,
@DynamicOverride DesktopModeController desktopModeController) {
return new CaptionWindowDecorViewModel(
- context,
- mainHandler,
- mainChoreographer,
- taskOrganizer,
- displayController,
- syncQueue,
- desktopModeController);
+ context,
+ mainHandler,
+ mainChoreographer,
+ taskOrganizer,
+ displayController,
+ syncQueue,
+ desktopModeController);
}
//
@@ -598,7 +598,9 @@
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeController provideDesktopModeController(Context context, ShellInit shellInit,
+ static DesktopModeController provideDesktopModeController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@@ -606,7 +608,7 @@
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new DesktopModeController(context, shellInit, shellTaskOrganizer,
+ return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index ff3be38..44a467f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -23,9 +23,4 @@
*/
@ExternalThread
public interface DesktopMode {
-
- /** Returns a binder that can be passed to an external process to manipulate DesktopMode. */
- default IDesktopMode createExternalInterface() {
- return null;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 99739c4..b96facf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -19,9 +19,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
@@ -29,23 +31,30 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
+import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -55,18 +64,22 @@
/**
* Handles windowing changes when desktop mode system setting changes
*/
-public class DesktopModeController implements RemoteCallable<DesktopModeController> {
+public class DesktopModeController implements RemoteCallable<DesktopModeController>,
+ Transitions.TransitionHandler {
private final Context mContext;
+ private final ShellController mShellController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final Transitions mTransitions;
private final DesktopModeTaskRepository mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
- private final DesktopMode mDesktopModeImpl = new DesktopModeImpl();
+ private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
private final SettingsObserver mSettingsObserver;
- public DesktopModeController(Context context, ShellInit shellInit,
+ public DesktopModeController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@@ -74,6 +87,7 @@
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mShellTaskOrganizer = shellTaskOrganizer;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mTransitions = transitions;
@@ -85,10 +99,13 @@
private void onInit() {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
+ this::createExternalInterface, this);
mSettingsObserver.observe();
if (DesktopModeStatus.isActive(mContext)) {
updateDesktopModeActive(true);
}
+ mTransitions.addHandler(this);
}
@Override
@@ -108,6 +125,13 @@
return mDesktopModeImpl;
}
+ /**
+ * Creates a new instance of the external interface to pass to another process.
+ */
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IDesktopModeImpl(this);
+ }
+
@VisibleForTesting
void updateDesktopModeActive(boolean active) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -157,7 +181,7 @@
/**
* Show apps on desktop
*/
- public void showDesktopApps() {
+ WindowContainerTransaction showDesktopApps() {
ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
@@ -173,7 +197,12 @@
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
- mShellTaskOrganizer.applyTransaction(wct);
+
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mShellTaskOrganizer.applyTransaction(wct);
+ }
+
+ return wct;
}
/**
@@ -195,6 +224,35 @@
.configuration.windowConfiguration.getWindowingMode();
}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // This handler should never be the sole handler, so should not animate anything.
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+
+ // Only do anything if we are in desktop mode and opening a task/app
+ if (!DesktopModeStatus.isActive(mContext) || request.getType() != TRANSIT_OPEN) {
+ return null;
+ }
+
+ WindowContainerTransaction wct = mTransitions.dispatchRequest(transition, request, this);
+ if (wct == null) {
+ wct = new WindowContainerTransaction();
+ }
+ wct.merge(showDesktopApps(), true /* transfer */);
+ wct.reorder(request.getTriggerTask().token, true /* onTop */);
+
+ return wct;
+ }
+
/**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
@@ -235,24 +293,15 @@
*/
@ExternalThread
private final class DesktopModeImpl implements DesktopMode {
-
- private IDesktopModeImpl mIDesktopMode;
-
- @Override
- public IDesktopMode createExternalInterface() {
- if (mIDesktopMode != null) {
- mIDesktopMode.invalidate();
- }
- mIDesktopMode = new IDesktopModeImpl(DesktopModeController.this);
- return mIDesktopMode;
- }
+ // Do nothing
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IDesktopModeImpl extends IDesktopMode.Stub {
+ private static class IDesktopModeImpl extends IDesktopMode.Stub
+ implements ExternalInterfaceBinder {
private DesktopModeController mController;
@@ -263,7 +312,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index 2aa933d..fbf326e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -29,19 +29,37 @@
### SysUI accessible components
In addition to doing the above, you will also need to provide an interface for calling to SysUI
from the Shell and vice versa. The current pattern is to have a parallel `Optional<Component name>`
-interface that the `<Component name>Controller` implements and handles on the main Shell thread.
+interface that the `<Component name>Controller` implements and handles on the main Shell thread
+(see [SysUI/Shell threading](threading.md)).
In addition, because components accessible to SysUI injection are explicitly listed, you'll have to
add an appropriate method in `WMComponent` to get the interface and update the `Builder` in
`SysUIComponent` to take the interface so it can be injected in SysUI code. The binding between
the two is done in `SystemUIFactory#init()` which will need to be updated as well.
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the external interface within the controller
+- Have all incoming calls post to the main shell thread (inject @ShellMainThread Executor into the
+ controller if needed)
+- Note that callbacks into SysUI should take an associated executor to call back on
+
### Launcher accessible components
Because Launcher is not a part of SystemUI and is a separate process, exposing controllers to
Launcher requires a new AIDL interface to be created and implemented by the controller. The
implementation of the stub interface in the controller otherwise behaves similar to the interface
to SysUI where it posts the work to the main Shell thread.
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the interface binder's `Stub` class within the controller, have it
+ extend `ExternalInterfaceBinder` and implement `invalidate()` to ensure it doesn't hold long
+ references to the outer controller
+- Make the controller implement `RemoteCallable<T>`, and have all incoming calls use one of
+ the `ExecutorUtils.executeRemoteCallWithTaskPermission()` calls to verify the caller's identity
+ and ensure the call happens on the main shell thread and not the binder thread
+- Inject `ShellController` and add the instance of the implementation as external interface
+- In Launcher, update `TouchInteractionService` to pass the interface to `SystemUIProxy`, and then
+ call the SystemUIProxy method as needed in that code
+
### Component initialization
To initialize the component:
- On the Shell side, you potentially need to do two things to initialize the component:
@@ -64,8 +82,9 @@
### General Do's & Dont's
Do:
-- Do add unit tests for all new components
-- Do keep controllers simple and break them down as needed
+- Add unit tests for all new components
+- Keep controllers simple and break them down as needed
+- Any SysUI callbacks should also take an associated executor to run the callback on
Don't:
- **Don't** do initialization in the constructor, only do initialization in the init callbacks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
index 9356660..f86d467 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
@@ -33,9 +33,4 @@
* - If there is a floating task for this intent, and it's not stashed, this stashes it.
*/
void showOrSetStashed(Intent intent);
-
- /** Returns a binder that can be passed to an external process to manipulate FloatingTasks. */
- default IFloatingTasks createExternalInterface() {
- return null;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
index 6755299..b3c09d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
@@ -21,6 +21,7 @@
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
import android.annotation.Nullable;
import android.content.Context;
@@ -40,6 +41,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -136,11 +138,13 @@
if (isFloatingTasksEnabled()) {
shellInit.addInitCallback(this::onInit, this);
}
- mShellCommandHandler.addDumpCallback(this::dump, this);
}
protected void onInit() {
mShellController.addConfigurationChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS,
+ this::createExternalInterface, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
/** Only used for testing. */
@@ -168,6 +172,10 @@
return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IFloatingTasksImpl(this);
+ }
+
@Override
public void onThemeChanged() {
if (mIsFloatingLayerAdded) {
@@ -412,28 +420,18 @@
*/
@ExternalThread
private class FloatingTaskImpl implements FloatingTasks {
- private IFloatingTasksImpl mIFloatingTasks;
-
@Override
public void showOrSetStashed(Intent intent) {
mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent));
}
-
- @Override
- public IFloatingTasks createExternalInterface() {
- if (mIFloatingTasks != null) {
- mIFloatingTasks.invalidate();
- }
- mIFloatingTasks = new IFloatingTasksImpl(FloatingTasksController.this);
- return mIFloatingTasks;
- }
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IFloatingTasksImpl extends IFloatingTasks.Stub {
+ private static class IFloatingTasksImpl extends IFloatingTasks.Stub
+ implements ExternalInterfaceBinder {
private FloatingTasksController mController;
IFloatingTasksImpl(FloatingTasksController controller) {
@@ -443,7 +441,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 7129165..2ee3348 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -30,13 +30,6 @@
OneHandedController.SUPPORT_ONE_HANDED_MODE, false);
/**
- * Returns a binder that can be passed to an external process to manipulate OneHanded.
- */
- default IOneHanded createExternalInterface() {
- return null;
- }
-
- /**
* Enters one handed mode.
*/
void startOneHanded();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index e0c4fe8..679d4ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -24,6 +24,7 @@
import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
import android.annotation.BinderThread;
import android.content.ComponentName;
@@ -49,6 +50,7 @@
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -296,12 +298,18 @@
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_ONE_HANDED,
+ this::createExternalInterface, this);
}
public OneHanded asOneHanded() {
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IOneHandedImpl(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -709,17 +717,6 @@
*/
@ExternalThread
private class OneHandedImpl implements OneHanded {
- private IOneHandedImpl mIOneHanded;
-
- @Override
- public IOneHanded createExternalInterface() {
- if (mIOneHanded != null) {
- mIOneHanded.invalidate();
- }
- mIOneHanded = new IOneHandedImpl(OneHandedController.this);
- return mIOneHanded;
- }
-
@Override
public void startOneHanded() {
mMainExecutor.execute(() -> {
@@ -767,7 +764,7 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IOneHandedImpl extends IOneHanded.Stub {
+ private static class IOneHandedImpl extends IOneHanded.Stub implements ExternalInterfaceBinder {
private OneHandedController mController;
IOneHandedImpl(OneHandedController controller) {
@@ -777,7 +774,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 72b9dd3..f34d2a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -27,14 +27,6 @@
*/
@ExternalThread
public interface Pip {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate PIP.
- */
- default IPip createExternalInterface() {
- return null;
- }
-
/**
* Expand PIP, it's possible that specific request to activate the window via Alt-tab.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 33761d2..2b36b4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -452,14 +452,17 @@
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) {
TransitionInfo.Change pipChange = pipTaskChange;
- if (pipChange == null) {
+ if (mCurrentPipTaskToken == null) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG);
+ } else if (pipChange == null) {
// The pipTaskChange is null, this can happen if we are reparenting the PIP activity
// back to its original Task. In that case, we should animate the activity leash
- // instead, which should be the only non-task, independent, TRANSIT_CHANGE window.
+ // instead, which should be the change whose last parent is the recorded PiP Task.
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() == null && change.getMode() == TRANSIT_CHANGE
- && TransitionInfo.isIndependent(change, info)) {
+ if (mCurrentPipTaskToken.equals(change.getLastParent())) {
+ // Find the activity that is exiting PiP.
pipChange = change;
break;
}
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 3345b1b..616d447 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
@@ -33,6 +33,7 @@
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -68,6 +69,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -159,6 +161,10 @@
// early bail out if the keep clear areas feature is disabled
return;
}
+ if (mPipBoundsState.isStashed()) {
+ // don't move when stashed
+ return;
+ }
// if there is another animation ongoing, wait for it to finish and try again
if (mPipAnimationController.isAnimating()) {
mMainExecutor.removeCallbacks(
@@ -632,6 +638,12 @@
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ this::createExternalInterface, this);
+ }
+
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IPipImpl(this);
}
@Override
@@ -733,6 +745,15 @@
// 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();
}
@@ -1040,17 +1061,6 @@
* The interface for calls from outside the Shell, within the host process.
*/
private class PipImpl implements Pip {
- private IPipImpl mIPip;
-
- @Override
- public IPip createExternalInterface() {
- if (mIPip != null) {
- mIPip.invalidate();
- }
- mIPip = new IPipImpl(PipController.this);
- return mIPip;
- }
-
@Override
public void expandPip() {
mMainExecutor.execute(() -> {
@@ -1098,7 +1108,7 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IPipImpl extends IPip.Stub {
+ private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
private PipController mController;
private final SingleInstanceRemoteListener<PipController,
IPipAnimationListener> mListener;
@@ -1129,7 +1139,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
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 89d85e4..41ff0b3 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 @@
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 @@
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
+
+ mUpdateResizeBoundsCallback = (rect) -> {
+ mUserResizeBounds.set(rect);
+ mMotionHelper.synchronizePinnedStackBounds();
+ mUpdateMovementBoundsRunnable.run();
+ resetState();
+ };
}
public void init() {
@@ -508,15 +516,50 @@
}
}
+ 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 @@
|| 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 1f3f31e..975d4bb 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 @@
}
/**
+ * 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/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 552ebde..93ffb3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -17,22 +17,14 @@
package com.android.wm.shell.protolog;
import android.annotation.Nullable;
-import android.content.Context;
-import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.BaseProtoLogImpl;
import com.android.internal.protolog.ProtoLogViewerConfigReader;
import com.android.internal.protolog.common.IProtoLogGroup;
-import com.android.wm.shell.R;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
import java.io.PrintWriter;
-import org.json.JSONException;
-
/**
* A service for the ProtoLog logging system.
@@ -40,8 +32,9 @@
public class ShellProtoLogImpl extends BaseProtoLogImpl {
private static final String TAG = "ProtoLogImpl";
private static final int BUFFER_CAPACITY = 1024 * 1024;
- // TODO: Get the right path for the proto log file when we initialize the shell components
- private static final String LOG_FILENAME = new File("wm_shell_log.pb").getAbsolutePath();
+ // TODO: find a proper location to save the protolog message file
+ private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
+ private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
private static ShellProtoLogImpl sServiceInstance = null;
@@ -111,18 +104,8 @@
}
public int startTextLogging(String[] groups, PrintWriter pw) {
- try (InputStream is =
- getClass().getClassLoader().getResourceAsStream("wm_shell_protolog.json")){
- mViewerConfig.loadViewerConfig(is);
- return setLogging(true /* setTextLogging */, true, pw, groups);
- } catch (IOException e) {
- Log.i(TAG, "Unable to load log definitions: IOException while reading "
- + "wm_shell_protolog. " + e);
- } catch (JSONException e) {
- Log.i(TAG, "Unable to load log definitions: JSON parsing exception while reading "
- + "wm_shell_protolog. " + e);
- }
- return -1;
+ mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
+ return setLogging(true /* setTextLogging */, true, pw, groups);
}
public int stopTextLogging(String[] groups, PrintWriter pw) {
@@ -130,7 +113,8 @@
}
private ShellProtoLogImpl() {
- super(new File(LOG_FILENAME), null, BUFFER_CAPACITY, new ProtoLogViewerConfigReader());
+ super(new File(LOG_FILENAME), VIEWER_CONFIG_FILENAME, BUFFER_CAPACITY,
+ new ProtoLogViewerConfigReader());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index b71cc32..1a6c1d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -16,7 +16,7 @@
package com.android.wm.shell.recents;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -44,5 +44,5 @@
/**
* Gets the set of running tasks.
*/
- ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
+ RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 59f7233..e8f58fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -16,7 +16,7 @@
package com.android.wm.shell.recents;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
/**
* Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
@@ -31,10 +31,10 @@
/**
* Called when a running task appears.
*/
- void onRunningTaskAppeared(in ActivityManager.RunningTaskInfo taskInfo);
+ void onRunningTaskAppeared(in RunningTaskInfo taskInfo);
/**
* Called when a running task vanishes.
*/
- void onRunningTaskVanished(in ActivityManager.RunningTaskInfo taskInfo);
-}
\ No newline at end of file
+ void onRunningTaskVanished(in RunningTaskInfo taskInfo);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 2a62552..069066e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -29,13 +29,6 @@
@ExternalThread
public interface RecentTasks {
/**
- * Returns a binder that can be passed to an external process to fetch recent tasks.
- */
- default IRecentTasks createExternalInterface() {
- return null;
- }
-
- /**
* Gets the set of recent tasks.
*/
default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 02b5a35..08f3db6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -20,6 +20,7 @@
import static android.content.pm.PackageManager.FEATURE_PC;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -37,6 +38,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -48,6 +50,7 @@
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -69,11 +72,12 @@
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
+ private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
- private final RecentTasks mImpl = new RecentTasksImpl();
+ private final RecentTasksImpl mImpl = new RecentTasksImpl();
private final ActivityTaskManager mActivityTaskManager;
private IRecentTasksListener mListener;
private final boolean mIsDesktopMode;
@@ -97,6 +101,7 @@
public static RecentTasksController create(
Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
@@ -106,18 +111,20 @@
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
- return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
- activityTaskManager, desktopModeTaskRepository, mainExecutor);
+ return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
+ taskStackListener, activityTaskManager, desktopModeTaskRepository, mainExecutor);
}
RecentTasksController(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mShellCommandHandler = shellCommandHandler;
mActivityTaskManager = activityTaskManager;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
@@ -131,7 +138,13 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IRecentTasksImpl(this);
+ }
+
private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
+ this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
@@ -366,17 +379,6 @@
*/
@ExternalThread
private class RecentTasksImpl implements RecentTasks {
- private IRecentTasksImpl mIRecentTasks;
-
- @Override
- public IRecentTasks createExternalInterface() {
- if (mIRecentTasks != null) {
- mIRecentTasks.invalidate();
- }
- mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
- return mIRecentTasks;
- }
-
@Override
public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
Consumer<List<GroupedRecentTaskInfo>> callback) {
@@ -393,7 +395,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IRecentTasksImpl extends IRecentTasks.Stub {
+ private static class IRecentTasksImpl extends IRecentTasks.Stub
+ implements ExternalInterfaceBinder {
private RecentTasksController mController;
private final SingleInstanceRemoteListener<RecentTasksController,
IRecentTasksListener> mListener;
@@ -424,7 +427,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index ecdafa9..eb08d0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -79,26 +79,47 @@
/**
* Starts tasks simultaneously in one transition.
*/
- oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
- in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteTransition remoteTransition, in InstanceId instanceId) = 10;
+ oneway void startTasks(int taskId1, in Bundle options1, int taskId2, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteTransition remoteTransition,
+ in InstanceId instanceId) = 10;
+
+ /**
+ * Starts a pair of intent and task in one transition.
+ */
+ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
+ in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
+
+ /**
+ * Starts a pair of shortcut and task in one transition.
+ */
+ oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId,
+ in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
* Version of startTasks using legacy transition system.
*/
- oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
- int sideTaskId, in Bundle sideOptions, int sidePosition,
- float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
+ oneway void startTasksWithLegacyTransition(int taskId1, in Bundle options1, int taskId2,
+ in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
/**
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
- in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions,
- int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter,
+ in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
in InstanceId instanceId) = 12;
/**
+ * Starts a pair of shortcut and task using legacy transition system.
+ */
+ oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo,
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
+
+ /**
* Blocking call that notifies and gets additional split-screen targets when entering
* recents (for example: the dividerBar).
* @param appTargets apps that will be re-parented to display area
@@ -111,11 +132,5 @@
* does not expect split to currently be running.
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
-
- /**
- * Starts a pair of shortcut and task using legacy transition system.
- */
- oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo, int taskId,
- in Bundle mainOptions, in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
}
+// Last id = 17
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index e73b799..d86aadc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -70,13 +70,6 @@
/** Unregisters listener that gets split screen callback. */
void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
- /**
- * Returns a binder that can be passed to an external process to manipulate SplitScreen.
- */
- default ISplitScreen createExternalInterface() {
- return null;
- }
-
/** Called when device waking up finished. */
void onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 07a6895..c6a2b83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -29,6 +29,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.app.ActivityManager;
@@ -71,6 +72,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -214,6 +216,10 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new ISplitScreenImpl(this);
+ }
+
/**
* This will be called after ShellTaskOrganizer has initialized/registered because of the
* dependency order.
@@ -224,6 +230,8 @@
mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
this);
mShellController.addKeyguardChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN,
+ this::createExternalInterface, this);
if (mStageCoordinator == null) {
// TODO: Multi-display
mStageCoordinator = createStageCoordinator();
@@ -658,7 +666,6 @@
*/
@ExternalThread
private class SplitScreenImpl implements SplitScreen {
- private ISplitScreenImpl mISplitScreen;
private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
@Override
@@ -704,15 +711,6 @@
};
@Override
- public ISplitScreen createExternalInterface() {
- if (mISplitScreen != null) {
- mISplitScreen.invalidate();
- }
- mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
- return mISplitScreen;
- }
-
- @Override
public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
if (mExecutors.containsKey(listener)) return;
@@ -752,7 +750,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private static class ISplitScreenImpl extends ISplitScreen.Stub
+ implements ExternalInterfaceBinder {
private SplitScreenController mController;
private final SingleInstanceRemoteListener<SplitScreenController,
ISplitScreenListener> mListener;
@@ -779,7 +778,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
@@ -828,47 +828,68 @@
}
@Override
- public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ public void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
+ int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
- mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+ taskId1, options1, taskId2, options2, splitPosition,
splitRatio, adapter, instanceId));
}
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
- Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
- int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
+ int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
- pendingIntent, fillInIntent, taskId, mainOptions, sideOptions,
- sidePosition, splitRatio, adapter, instanceId));
+ pendingIntent, fillInIntent, options1, taskId, options2,
+ splitPosition, splitRatio, adapter, instanceId));
}
@Override
public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startShortcutAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition(
- shortcutInfo, taskId, mainOptions, sideOptions, sidePosition,
+ shortcutInfo, options1, taskId, options2, splitPosition,
splitRatio, adapter, instanceId));
}
@Override
- public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio,
+ public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
- (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
- sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition,
+ (controller) -> controller.mStageCoordinator.startTasks(taskId1, options1,
+ taskId2, options2, splitPosition, splitRatio, remoteTransition,
+ instanceId));
+ }
+
+ @Override
+ public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
+ (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
+ fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
+ remoteTransition, instanceId));
+ }
+
+ @Override
+ public void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
+ (controller) -> controller.mStageCoordinator.startShortcutAndTask(shortcutInfo,
+ options1, taskId, options2, splitPosition, splitRatio, remoteTransition,
instanceId));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c17f822..e2ac01f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -207,6 +207,7 @@
private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
private DefaultMixedHandler mMixedHandler;
+ private final Toast mSplitUnsupportedToast;
private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
new SplitWindowManager.ParentContainerCallbacks() {
@@ -300,6 +301,8 @@
mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
mTaskOrganizer.addFocusListener(this);
+ mSplitUnsupportedToast = Toast.makeText(mContext,
+ R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
}
@VisibleForTesting
@@ -329,6 +332,8 @@
mDisplayController.addDisplayWindowListener(this);
mDisplayLayout = new DisplayLayout();
transitions.addHandler(this);
+ mSplitUnsupportedToast = Toast.makeText(mContext,
+ R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
}
public void setMixedHandler(DefaultMixedHandler mixedHandler) {
@@ -470,6 +475,7 @@
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
} else {
// Switch the split position if launching as MULTIPLE_TASK failed.
if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
@@ -516,14 +522,55 @@
}
/** Starts 2 tasks in one transition. */
- void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
- @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
+ void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- mainOptions = mainOptions != null ? mainOptions : new Bundle();
- sideOptions = sideOptions != null ? sideOptions : new Bundle();
- setSideStagePosition(sidePosition, wct);
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startTask(taskId1, options1);
+ startWithTask(wct, taskId2, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /** Start an intent and a task to a split pair in one transition. */
+ void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+
+ startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /** Starts a shortcut and a task to a split pair in one transition. */
+ void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+
+ startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /**
+ * Starts with the second task to a split pair in one transition.
+ *
+ * @param wct transaction to start the first task
+ * @param instanceId if {@code null}, will not log. Otherwise it will be used in
+ * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
+ */
+ private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable Bundle mainOptions, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
if (mMainStage.isActive()) {
mMainStage.evictAllChildren(wct);
mSideStage.evictAllChildren(wct);
@@ -538,60 +585,61 @@
wct.setForceTranslucent(mRootTaskInfo.token, false);
// Make sure the launch options will put tasks in the corresponding split roots
+ mainOptions = mainOptions != null ? mainOptions : new Bundle();
addActivityOptions(mainOptions, mMainStage);
- addActivityOptions(sideOptions, mSideStage);
// Add task launch requests
wct.startTask(mainTaskId, mainOptions);
- wct.startTask(sideTaskId, sideOptions);
mSplitTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
setEnterInstanceId(instanceId);
}
- /** Starts 2 tasks in one legacy transition. */
- void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ /** Starts a pair of tasks using legacy transition. */
+ void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
+ int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.startTask(sideTaskId, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startTask(taskId1, options1);
- startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
instanceId);
}
- /** Start an intent and a task ordered by {@code intentFirst}. */
+ /** Starts a pair of intent and task using legacy transition. */
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
+ /** Starts a pair of shortcut and task using legacy transition. */
void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
/**
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
@@ -694,6 +742,7 @@
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
} else {
mSyncQueue.queue(evictWct);
}
@@ -1727,6 +1776,7 @@
@StageType
private int getStageType(StageTaskListener stage) {
+ if (stage == null) return STAGE_TYPE_UNDEFINED;
return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
}
@@ -1981,8 +2031,8 @@
}
}
- // TODO: fallback logic. Probably start a new transition to exit split before applying
- // anything here. Ideally consolidate with transition-merging.
+ // TODO(b/250853925): fallback logic. Probably start a new transition to exit split before
+ // applying anything here. Ideally consolidate with transition-merging.
if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
if (mainChild == null && sideChild == null) {
throw new IllegalStateException("Launched a task in split, but didn't receive any"
@@ -2244,13 +2294,11 @@
@Override
public void onNoLongerSupportMultiWindow() {
if (mMainStage.isActive()) {
- final Toast splitUnsupportedToast = Toast.makeText(mContext,
- R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
final boolean isMainStage = mMainStageListener == this;
if (!ENABLE_SHELL_TRANSITIONS) {
StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
- splitUnsupportedToast.show();
+ mSplitUnsupportedToast.show();
return;
}
@@ -2259,7 +2307,7 @@
prepareExitSplitScreen(stageType, wct);
mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
- splitUnsupportedToast.show();
+ mSplitUnsupportedToast.show();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 014f02b..8bba4404 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -15,38 +15,20 @@
*/
package com.android.wm.shell.startingsurface;
-import static android.view.Choreographer.CALLBACK_COMMIT;
import static android.view.View.GONE;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM;
import android.animation.Animator;
-import android.animation.ValueAnimator;
import android.content.Context;
-import android.graphics.BlendMode;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.RadialGradient;
import android.graphics.Rect;
-import android.graphics.Shader;
-import android.util.MathUtils;
import android.util.Slog;
-import android.view.Choreographer;
import android.view.SurfaceControl;
-import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
import android.window.SplashScreenView;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.TransactionPool;
/**
@@ -55,14 +37,8 @@
*/
public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private static final boolean DEBUG_EXIT_ANIMATION = false;
- private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
private static final String TAG = StartingWindowController.TAG;
- private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
- private static final Interpolator MASK_RADIUS_INTERPOLATOR =
- new PathInterpolator(0f, 0f, 0.4f, 1f);
- private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
-
private final SurfaceControl mFirstWindowSurface;
private final Rect mFirstWindowFrame = new Rect();
private final SplashScreenView mSplashScreenView;
@@ -75,9 +51,6 @@
private final float mBrandingStartAlpha;
private final TransactionPool mTransactionPool;
- private ValueAnimator mMainAnimator;
- private ShiftUpAnimation mShiftUpAnimation;
- private RadialVanishAnimation mRadialVanishAnimation;
private Runnable mFinishCallback;
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
@@ -121,187 +94,10 @@
}
void startAnimations() {
- mMainAnimator = createAnimator();
- mMainAnimator.start();
- }
-
- // fade out icon, reveal app, shift up main window
- private ValueAnimator createAnimator() {
- // reveal app
- final float transparentRatio = 0.8f;
- final int globalHeight = mSplashScreenView.getHeight();
- final int verticalCircleCenter = 0;
- final int finalVerticalLength = globalHeight - verticalCircleCenter;
- final int halfWidth = mSplashScreenView.getWidth() / 2;
- final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
- Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
- final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
- final float[] stops = {0f, transparentRatio, 1f};
-
- mRadialVanishAnimation = new RadialVanishAnimation(mSplashScreenView);
- mRadialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
- mRadialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
- mRadialVanishAnimation.setRadialPaintParam(colors, stops);
-
- if (mFirstWindowSurface != null && mFirstWindowSurface.isValid()) {
- // shift up main window
- View occludeHoleView = new View(mSplashScreenView.getContext());
- if (DEBUG_EXIT_ANIMATION_BLEND) {
- occludeHoleView.setBackgroundColor(Color.BLUE);
- } else {
- occludeHoleView.setBackgroundColor(mSplashScreenView.getInitBackgroundColor());
- }
- final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
- WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
- mSplashScreenView.addView(occludeHoleView, params);
-
- mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView);
- }
-
- ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
- animator.setDuration(mAnimationDuration);
- animator.setInterpolator(Interpolators.LINEAR);
- animator.addListener(this);
- animator.addUpdateListener(a -> onAnimationProgress((float) a.getAnimatedValue()));
- return animator;
- }
-
- private static class RadialVanishAnimation extends View {
- private final SplashScreenView mView;
- private int mInitRadius;
- private int mFinishRadius;
-
- private final Point mCircleCenter = new Point();
- private final Matrix mVanishMatrix = new Matrix();
- private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- RadialVanishAnimation(SplashScreenView target) {
- super(target.getContext());
- mView = target;
- mView.addView(this);
- mVanishPaint.setAlpha(0);
- }
-
- void onAnimationProgress(float linearProgress) {
- if (mVanishPaint.getShader() == null) {
- return;
- }
-
- final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
- final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
- final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
-
- mVanishMatrix.setScale(scale, scale);
- mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
- mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
- mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
-
- postInvalidate();
- }
-
- void setRadius(int initRadius, int finishRadius) {
- if (DEBUG_EXIT_ANIMATION) {
- Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
- + " final " + finishRadius);
- }
- mInitRadius = initRadius;
- mFinishRadius = finishRadius;
- }
-
- void setCircleCenter(int x, int y) {
- if (DEBUG_EXIT_ANIMATION) {
- Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
- }
- mCircleCenter.set(x, y);
- }
-
- void setRadialPaintParam(int[] colors, float[] stops) {
- // setup gradient shader
- final RadialGradient rShader =
- new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
- mVanishPaint.setShader(rShader);
- if (!DEBUG_EXIT_ANIMATION_BLEND) {
- // We blend the reveal gradient with the splash screen using DST_OUT so that the
- // splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
- // fully invisible when radius = finishRadius AND gradient opacity is 1.
- mVanishPaint.setBlendMode(BlendMode.DST_OUT);
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
- }
- }
-
- private final class ShiftUpAnimation {
- private final float mFromYDelta;
- private final float mToYDelta;
- private final View mOccludeHoleView;
- private final SyncRtSurfaceTransactionApplier mApplier;
- private final Matrix mTmpTransform = new Matrix();
-
- ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView) {
- mFromYDelta = fromYDelta;
- mToYDelta = toYDelta;
- mOccludeHoleView = occludeHoleView;
- mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
- }
-
- void onAnimationProgress(float linearProgress) {
- if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
- || !mSplashScreenView.isAttachedToWindow()) {
- return;
- }
-
- final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
- final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
-
- mOccludeHoleView.setTranslationY(dy);
- mTmpTransform.setTranslate(0 /* dx */, dy);
-
- // set the vsyncId to ensure the transaction doesn't get applied too early.
- final SurfaceControl.Transaction tx = mTransactionPool.acquire();
- tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
- mTmpTransform.postTranslate(mFirstWindowFrame.left,
- mFirstWindowFrame.top + mMainWindowShiftLength);
-
- SyncRtSurfaceTransactionApplier.SurfaceParams
- params = new SyncRtSurfaceTransactionApplier.SurfaceParams
- .Builder(mFirstWindowSurface)
- .withMatrix(mTmpTransform)
- .withMergeTransaction(tx)
- .build();
- mApplier.scheduleApply(params);
-
- mTransactionPool.release(tx);
- }
-
- void finish() {
- if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
- return;
- }
- final SurfaceControl.Transaction tx = mTransactionPool.acquire();
- if (mSplashScreenView.isAttachedToWindow()) {
- tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
-
- SyncRtSurfaceTransactionApplier.SurfaceParams
- params = new SyncRtSurfaceTransactionApplier.SurfaceParams
- .Builder(mFirstWindowSurface)
- .withWindowCrop(null)
- .withMergeTransaction(tx)
- .build();
- mApplier.scheduleApply(params);
- } else {
- tx.setWindowCrop(mFirstWindowSurface, null);
- tx.apply();
- }
- mTransactionPool.release(tx);
-
- Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
- mFirstWindowSurface::release, null);
- }
+ SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
+ mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
+ mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
+ mAppRevealDuration, this);
}
private void reset() {
@@ -316,9 +112,6 @@
mFinishCallback = null;
}
}
- if (mShiftUpAnimation != null) {
- mShiftUpAnimation.finish();
- }
}
@Override
@@ -342,40 +135,4 @@
public void onAnimationRepeat(Animator animation) {
// ignore
}
-
- private void onFadeOutProgress(float linearProgress) {
- final float iconProgress = ICON_INTERPOLATOR.getInterpolation(
- getProgress(linearProgress, 0 /* delay */, mIconFadeOutDuration));
- final View iconView = mSplashScreenView.getIconView();
- final View brandingView = mSplashScreenView.getBrandingView();
- if (iconView != null) {
- iconView.setAlpha(mIconStartAlpha * (1 - iconProgress));
- }
- if (brandingView != null) {
- brandingView.setAlpha(mBrandingStartAlpha * (1 - iconProgress));
- }
- }
-
- private void onAnimationProgress(float linearProgress) {
- onFadeOutProgress(linearProgress);
-
- final float revealLinearProgress = getProgress(linearProgress, mAppRevealDelay,
- mAppRevealDuration);
-
- if (mRadialVanishAnimation != null) {
- mRadialVanishAnimation.onAnimationProgress(revealLinearProgress);
- }
-
- if (mShiftUpAnimation != null) {
- mShiftUpAnimation.onAnimationProgress(revealLinearProgress);
- }
- }
-
- private float getProgress(float linearProgress, long delay, long duration) {
- return MathUtils.constrain(
- (linearProgress * (mAnimationDuration) - delay) / duration,
- 0.0f,
- 1.0f
- );
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
new file mode 100644
index 0000000..3098e55
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.startingsurface;
+
+import static android.view.Choreographer.CALLBACK_COMMIT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.util.MathUtils;
+import android.util.Slog;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.window.SplashScreenView;
+
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.TransactionPool;
+
+/**
+ * Utilities for creating the splash screen window animations.
+ * @hide
+ */
+public class SplashScreenExitAnimationUtils {
+ private static final boolean DEBUG_EXIT_ANIMATION = false;
+ private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
+ private static final String TAG = "SplashScreenExitAnimationUtils";
+
+ private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
+ private static final Interpolator MASK_RADIUS_INTERPOLATOR =
+ new PathInterpolator(0f, 0f, 0.4f, 1f);
+ private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
+
+ /**
+ * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+ * window.
+ * @hide
+ */
+ public static void startAnimations(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ ValueAnimator animator =
+ createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+ transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+ iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+ animatorListener);
+ animator.start();
+ }
+
+ /**
+ * Creates the animator to fade out the icon, reveal the app, and shift up main window.
+ * @hide
+ */
+ private static ValueAnimator createAnimator(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ // reveal app
+ final float transparentRatio = 0.8f;
+ final int globalHeight = splashScreenView.getHeight();
+ final int verticalCircleCenter = 0;
+ final int finalVerticalLength = globalHeight - verticalCircleCenter;
+ final int halfWidth = splashScreenView.getWidth() / 2;
+ final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
+ Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
+ final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
+ final float[] stops = {0f, transparentRatio, 1f};
+
+ RadialVanishAnimation radialVanishAnimation = new RadialVanishAnimation(splashScreenView);
+ radialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
+ radialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
+ radialVanishAnimation.setRadialPaintParam(colors, stops);
+
+ View occludeHoleView = null;
+ ShiftUpAnimation shiftUpAnimation = null;
+ if (firstWindowSurface != null && firstWindowSurface.isValid()) {
+ // shift up main window
+ occludeHoleView = new View(splashScreenView.getContext());
+ if (DEBUG_EXIT_ANIMATION_BLEND) {
+ occludeHoleView.setBackgroundColor(Color.BLUE);
+ } else if (splashScreenView instanceof SplashScreenView) {
+ occludeHoleView.setBackgroundColor(
+ ((SplashScreenView) splashScreenView).getInitBackgroundColor());
+ } else {
+ occludeHoleView.setBackgroundColor(
+ isDarkTheme(splashScreenView.getContext()) ? Color.BLACK : Color.WHITE);
+ }
+ final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
+ splashScreenView.addView(occludeHoleView, params);
+
+ shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
+ firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
+ mMainWindowShiftLength);
+ }
+
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(animationDuration);
+ animator.setInterpolator(Interpolators.LINEAR);
+ if (animatorListener != null) {
+ animator.addListener(animatorListener);
+ }
+ View finalOccludeHoleView = occludeHoleView;
+ ShiftUpAnimation finalShiftUpAnimation = shiftUpAnimation;
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (finalShiftUpAnimation != null) {
+ finalShiftUpAnimation.finish();
+ }
+ splashScreenView.removeView(radialVanishAnimation);
+ splashScreenView.removeView(finalOccludeHoleView);
+ }
+ });
+ animator.addUpdateListener(animation -> {
+ float linearProgress = (float) animation.getAnimatedValue();
+
+ // Fade out progress
+ final float iconProgress =
+ ICON_INTERPOLATOR.getInterpolation(getProgress(
+ linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration));
+ View iconView = null;
+ View brandingView = null;
+ if (splashScreenView instanceof SplashScreenView) {
+ iconView = ((SplashScreenView) splashScreenView).getIconView();
+ brandingView = ((SplashScreenView) splashScreenView).getBrandingView();
+ }
+ if (iconView != null) {
+ iconView.setAlpha(iconStartAlpha * (1 - iconProgress));
+ }
+ if (brandingView != null) {
+ brandingView.setAlpha(brandingStartAlpha * (1 - iconProgress));
+ }
+
+ final float revealLinearProgress = getProgress(linearProgress, appRevealDelay,
+ appRevealDuration, animationDuration);
+
+ radialVanishAnimation.onAnimationProgress(revealLinearProgress);
+
+ if (finalShiftUpAnimation != null) {
+ finalShiftUpAnimation.onAnimationProgress(revealLinearProgress);
+ }
+ });
+ return animator;
+ }
+
+ private static float getProgress(float linearProgress, long delay, long duration,
+ int animationDuration) {
+ return MathUtils.constrain(
+ (linearProgress * (animationDuration) - delay) / duration,
+ 0.0f,
+ 1.0f
+ );
+ }
+
+ private static boolean isDarkTheme(Context context) {
+ Configuration configuration = context.getResources().getConfiguration();
+ int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ return nightMode == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ /**
+ * View which creates a circular reveal of the underlying view.
+ * @hide
+ */
+ @SuppressLint("ViewConstructor")
+ public static class RadialVanishAnimation extends View {
+ private final ViewGroup mView;
+ private int mInitRadius;
+ private int mFinishRadius;
+
+ private final Point mCircleCenter = new Point();
+ private final Matrix mVanishMatrix = new Matrix();
+ private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ public RadialVanishAnimation(ViewGroup target) {
+ super(target.getContext());
+ mView = target;
+ mView.addView(this);
+ if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
+ ((ViewGroup.MarginLayoutParams) getLayoutParams()).setMargins(0, 0, 0, 0);
+ }
+ mVanishPaint.setAlpha(0);
+ }
+
+ void onAnimationProgress(float linearProgress) {
+ if (mVanishPaint.getShader() == null) {
+ return;
+ }
+
+ final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
+ final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
+ final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
+
+ mVanishMatrix.setScale(scale, scale);
+ mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
+ mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
+ mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
+
+ postInvalidate();
+ }
+
+ void setRadius(int initRadius, int finishRadius) {
+ if (DEBUG_EXIT_ANIMATION) {
+ Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
+ + " final " + finishRadius);
+ }
+ mInitRadius = initRadius;
+ mFinishRadius = finishRadius;
+ }
+
+ void setCircleCenter(int x, int y) {
+ if (DEBUG_EXIT_ANIMATION) {
+ Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
+ }
+ mCircleCenter.set(x, y);
+ }
+
+ void setRadialPaintParam(int[] colors, float[] stops) {
+ // setup gradient shader
+ final RadialGradient rShader =
+ new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
+ mVanishPaint.setShader(rShader);
+ if (!DEBUG_EXIT_ANIMATION_BLEND) {
+ // We blend the reveal gradient with the splash screen using DST_OUT so that the
+ // splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
+ // fully invisible when radius = finishRadius AND gradient opacity is 1.
+ mVanishPaint.setBlendMode(BlendMode.DST_OUT);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
+ }
+ }
+
+ /**
+ * Shifts up the main window.
+ * @hide
+ */
+ public static final class ShiftUpAnimation {
+ private final float mFromYDelta;
+ private final float mToYDelta;
+ private final View mOccludeHoleView;
+ private final SyncRtSurfaceTransactionApplier mApplier;
+ private final Matrix mTmpTransform = new Matrix();
+ private final SurfaceControl mFirstWindowSurface;
+ private final ViewGroup mSplashScreenView;
+ private final TransactionPool mTransactionPool;
+ private final Rect mFirstWindowFrame;
+ private final int mMainWindowShiftLength;
+
+ public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
+ SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
+ TransactionPool transactionPool, Rect firstWindowFrame,
+ int mainWindowShiftLength) {
+ mFromYDelta = fromYDelta;
+ mToYDelta = toYDelta;
+ mOccludeHoleView = occludeHoleView;
+ mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
+ mFirstWindowSurface = firstWindowSurface;
+ mSplashScreenView = splashScreenView;
+ mTransactionPool = transactionPool;
+ mFirstWindowFrame = firstWindowFrame;
+ mMainWindowShiftLength = mainWindowShiftLength;
+ }
+
+ void onAnimationProgress(float linearProgress) {
+ if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
+ || !mSplashScreenView.isAttachedToWindow()) {
+ return;
+ }
+
+ final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
+ final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
+
+ mOccludeHoleView.setTranslationY(dy);
+ mTmpTransform.setTranslate(0 /* dx */, dy);
+
+ // set the vsyncId to ensure the transaction doesn't get applied too early.
+ final SurfaceControl.Transaction tx = mTransactionPool.acquire();
+ tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+ mTmpTransform.postTranslate(mFirstWindowFrame.left,
+ mFirstWindowFrame.top + mMainWindowShiftLength);
+
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ params = new SyncRtSurfaceTransactionApplier.SurfaceParams
+ .Builder(mFirstWindowSurface)
+ .withMatrix(mTmpTransform)
+ .withMergeTransaction(tx)
+ .build();
+ mApplier.scheduleApply(params);
+
+ mTransactionPool.release(tx);
+ }
+
+ void finish() {
+ if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
+ return;
+ }
+ final SurfaceControl.Transaction tx = mTransactionPool.acquire();
+ if (mSplashScreenView.isAttachedToWindow()) {
+ tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ params = new SyncRtSurfaceTransactionApplier.SurfaceParams
+ .Builder(mFirstWindowSurface)
+ .withWindowCrop(null)
+ .withMergeTransaction(tx)
+ .build();
+ mApplier.scheduleApply(params);
+ } else {
+ tx.setWindowCrop(mFirstWindowSurface, null);
+ tx.apply();
+ }
+ mTransactionPool.release(tx);
+
+ Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
+ mFirstWindowSurface::release, null);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index 76105a3..538bbec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -22,14 +22,6 @@
* Interface to engage starting window feature.
*/
public interface StartingSurface {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate starting windows.
- */
- default IStartingWindow createExternalInterface() {
- return null;
- }
-
/**
* Returns the background color for a starting window if existing.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index 379af21..0c23f10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -23,6 +23,7 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -43,10 +44,12 @@
import com.android.internal.util.function.TriConsumer;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
/**
@@ -76,6 +79,7 @@
private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback;
private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
private final Context mContext;
+ private final ShellController mShellController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mSplashScreenExecutor;
/**
@@ -86,12 +90,14 @@
public StartingWindowController(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm,
IconProvider iconProvider,
TransactionPool pool) {
mContext = context;
+ mShellController = shellController;
mShellTaskOrganizer = shellTaskOrganizer;
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
iconProvider, pool);
@@ -107,8 +113,14 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IStartingWindowImpl(this);
+ }
+
private void onInit() {
mShellTaskOrganizer.initStartingWindow(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW,
+ this::createExternalInterface, this);
}
@Override
@@ -222,17 +234,6 @@
* The interface for calls from outside the Shell, within the host process.
*/
private class StartingSurfaceImpl implements StartingSurface {
- private IStartingWindowImpl mIStartingWindow;
-
- @Override
- public IStartingWindowImpl createExternalInterface() {
- if (mIStartingWindow != null) {
- mIStartingWindow.invalidate();
- }
- mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this);
- return mIStartingWindow;
- }
-
@Override
public int getBackgroundColor(TaskInfo taskInfo) {
synchronized (mTaskBackgroundColors) {
@@ -256,7 +257,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IStartingWindowImpl extends IStartingWindow.Stub {
+ private static class IStartingWindowImpl extends IStartingWindow.Stub
+ implements ExternalInterfaceBinder {
private StartingWindowController mController;
private SingleInstanceRemoteListener<StartingWindowController,
IStartingWindowListener> mListener;
@@ -276,7 +278,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 5799394..fdf073f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -23,23 +23,28 @@
import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Supplier;
/**
* Handles event callbacks from SysUI that can be used within the Shell.
@@ -59,6 +64,11 @@
private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
new CopyOnWriteArrayList<>();
+ private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
+ new ArrayMap<>();
+ // References to the existing interfaces, to be invalidated when they are recreated
+ private ArrayMap<String, ExternalInterfaceBinder> mExternalInterfaces = new ArrayMap<>();
+
private Configuration mLastConfiguration;
@@ -67,6 +77,11 @@
mShellInit = shellInit;
mShellCommandHandler = shellCommandHandler;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
/**
@@ -124,6 +139,47 @@
mUserChangeListeners.remove(listener);
}
+ /**
+ * Adds an interface that can be called from a remote process. This method takes a supplier
+ * because each binder reference is valid for a single process, and in multi-user mode, SysUI
+ * will request new binder instances for each instance of Launcher that it provides binders
+ * to.
+ *
+ * @param extra the key for the interface, {@see ShellSharedConstants}
+ * @param binderSupplier the supplier of the binder to pass to the external process
+ * @param callerInstance the instance of the caller, purely for logging
+ */
+ public void addExternalInterface(String extra, Supplier<ExternalInterfaceBinder> binderSupplier,
+ Object callerInstance) {
+ ProtoLog.v(WM_SHELL_INIT, "Adding external interface from %s with key %s",
+ callerInstance.getClass().getSimpleName(), extra);
+ if (mExternalInterfaceSuppliers.containsKey(extra)) {
+ throw new IllegalArgumentException("Supplier with same key already exists: "
+ + extra);
+ }
+ mExternalInterfaceSuppliers.put(extra, binderSupplier);
+ }
+
+ /**
+ * Updates the given bundle with the set of external interfaces, invalidating the old set of
+ * binders.
+ */
+ private void createExternalInterfaces(Bundle output) {
+ // Invalidate the old binders
+ for (int i = 0; i < mExternalInterfaces.size(); i++) {
+ mExternalInterfaces.valueAt(i).invalidate();
+ }
+ mExternalInterfaces.clear();
+
+ // Create new binders for each key
+ for (int i = 0; i < mExternalInterfaceSuppliers.size(); i++) {
+ final String key = mExternalInterfaceSuppliers.keyAt(i);
+ final ExternalInterfaceBinder b = mExternalInterfaceSuppliers.valueAt(i).get();
+ mExternalInterfaces.put(key, b);
+ output.putBinder(key, b.asBinder());
+ }
+ }
+
@VisibleForTesting
void onConfigurationChanged(Configuration newConfig) {
// The initial config is send on startup and doesn't trigger listener callbacks
@@ -204,6 +260,14 @@
pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size());
+
+ if (!mExternalInterfaces.isEmpty()) {
+ pw.println(innerPrefix + "mExternalInterfaces={");
+ for (String key : mExternalInterfaces.keySet()) {
+ pw.println(innerPrefix + "\t" + key + ": " + mExternalInterfaces.get(key));
+ }
+ pw.println(innerPrefix + "}");
+ }
}
/**
@@ -211,7 +275,6 @@
*/
@ExternalThread
private class ShellInterfaceImpl implements ShellInterface {
-
@Override
public void onInit() {
try {
@@ -222,28 +285,6 @@
}
@Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to dump the Shell in 2s", e);
- }
- }
-
- @Override
- public boolean handleCommand(String[] args, PrintWriter pw) {
- try {
- boolean[] result = new boolean[1];
- mMainExecutor.executeBlocking(() -> {
- result[0] = mShellCommandHandler.handleCommand(args, pw);
- });
- return result[0];
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to handle Shell command in 2s", e);
- }
- }
-
- @Override
public void onConfigurationChanged(Configuration newConfiguration) {
mMainExecutor.execute(() ->
ShellController.this.onConfigurationChanged(newConfiguration));
@@ -274,5 +315,38 @@
mMainExecutor.execute(() ->
ShellController.this.onUserProfilesChanged(profiles));
}
+
+ @Override
+ public boolean handleCommand(String[] args, PrintWriter pw) {
+ try {
+ boolean[] result = new boolean[1];
+ mMainExecutor.executeBlocking(() -> {
+ result[0] = mShellCommandHandler.handleCommand(args, pw);
+ });
+ return result[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to handle Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void createExternalInterfaces(Bundle bundle) {
+ try {
+ mMainExecutor.executeBlocking(() -> {
+ ShellController.this.createExternalInterfaces(bundle);
+ });
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to get Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ try {
+ mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to dump the Shell in 2s", e);
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index 2108c82..bc5dd11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Bundle;
import androidx.annotation.NonNull;
@@ -37,18 +38,6 @@
default void onInit() {}
/**
- * Dumps the shell state.
- */
- default void dump(PrintWriter pw) {}
-
- /**
- * Handles a shell command.
- */
- default boolean handleCommand(final String[] args, PrintWriter pw) {
- return false;
- }
-
- /**
* Notifies the Shell that the configuration has changed.
*/
default void onConfigurationChanged(Configuration newConfiguration) {}
@@ -74,4 +63,21 @@
* Notifies the Shell when a profile belonging to the user changes.
*/
default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
+
+ /**
+ * Handles a shell command.
+ */
+ default boolean handleCommand(final String[] args, PrintWriter pw) {
+ return false;
+ }
+
+ /**
+ * Updates the given {@param bundle} with the set of exposed interfaces.
+ */
+ default void createExternalInterfaces(Bundle bundle) {}
+
+ /**
+ * Dumps the shell state.
+ */
+ default void dump(PrintWriter pw) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
new file mode 100644
index 0000000..bdda6a8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+/**
+ * General shell-related constants that are shared with users of the library.
+ */
+public class ShellSharedConstants {
+ // See IPip.aidl
+ public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+ // See ISplitScreen.aidl
+ public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
+ // See IOneHanded.aidl
+ public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
+ // See IShellTransitions.aidl
+ public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
+ "extra_shell_shell_transitions";
+ // See IStartingWindow.aidl
+ public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
+ "extra_shell_starting_window";
+ // See IRecentTasks.aidl
+ public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
+ // See IBackAnimation.aidl
+ public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
+ // See IFloatingTasks.aidl
+ public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
+ // See IDesktopMode.aidl
+ public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
+}
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 dbb2948..9c2c2fa 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_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.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.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 @@
}
}
- 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/ShellTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
index b34049d..da39017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
@@ -27,14 +27,6 @@
*/
@ExternalThread
public interface ShellTransitions {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate remote transitions.
- */
- default IShellTransitions createExternalInterface() {
- return null;
- }
-
/**
* Registers a remote 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 efee6f40..b75c552 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_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 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 @@
.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 d65ac80..db1f19a 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
@@ -25,10 +25,12 @@
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -61,11 +63,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -115,6 +119,7 @@
private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final DisplayController mDisplayController;
+ private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -142,6 +147,7 @@
public Transitions(@NonNull Context context,
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull WindowOrganizer organizer,
@NonNull TransactionPool pool,
@NonNull DisplayController displayController,
@@ -156,10 +162,14 @@
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
displayController, pool, mainExecutor, mainHandler, animExecutor);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+ mShellController = shellController;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ this::createExternalInterface, this);
+
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
@@ -193,6 +203,10 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IShellTransitionsImpl(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -308,6 +322,11 @@
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();
@@ -441,31 +460,34 @@
return;
}
- // apply transfer starting window directly if there is no other task change. Since this
- // is an activity->activity situation, we can detect it by selecting transitions with only
- // 2 changes where neither are tasks and one is a starting-window recipient.
final int changeSize = info.getChanges().size();
- if (changeSize == 2) {
- boolean nonTaskChange = true;
- boolean transferStartingWindow = false;
- for (int i = changeSize - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() != null) {
- nonTaskChange = false;
- break;
- }
- if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- transferStartingWindow = true;
- }
+ boolean taskChange = false;
+ boolean transferStartingWindow = false;
+ boolean allOccluded = changeSize > 0;
+ for (int i = changeSize - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ taskChange |= change.getTaskInfo() != null;
+ transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
+ if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
+ allOccluded = false;
}
- if (nonTaskChange && transferStartingWindow) {
- t.apply();
- finishT.apply();
- // Treat this as an abort since we are bypassing any merge logic and effectively
- // finishing immediately.
- onAbort(transitionToken);
- return;
- }
+ }
+ // There does not need animation when:
+ // A. Transfer starting window. Apply transfer starting window directly if there is no other
+ // task change. Since this is an activity->activity situation, we can detect it by selecting
+ // transitions with only 2 changes where neither are tasks and one is a starting-window
+ // recipient.
+ if (!taskChange && transferStartingWindow && changeSize == 2
+ // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
+ // changes are underneath another change.
+ || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
+ && allOccluded)) {
+ t.apply();
+ finishT.apply();
+ // Treat this as an abort since we are bypassing any merge logic and effectively
+ // finishing immediately.
+ onAbort(transitionToken);
+ return;
}
final ActiveTransition active = mActiveTransitions.get(activeIdx);
@@ -542,6 +564,22 @@
"This shouldn't happen, maybe the default handler is broken.");
}
+ /**
+ * Gives every handler (in order) a chance to handle request until one consumes the transition.
+ * @return the WindowContainerTransaction given by the handler which consumed the transition.
+ */
+ public WindowContainerTransaction dispatchRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request, @Nullable TransitionHandler skip) {
+ for (int i = mHandlers.size() - 1; i >= 0; --i) {
+ if (mHandlers.get(i) == skip) continue;
+ WindowContainerTransaction wct = mHandlers.get(i).handleRequest(transition, request);
+ if (wct != null) {
+ return wct;
+ }
+ }
+ return null;
+ }
+
/** Special version of finish just for dealing with no-op/invalid transitions. */
private void onAbort(IBinder transition) {
onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
@@ -897,17 +935,6 @@
*/
@ExternalThread
private class ShellTransitionImpl implements ShellTransitions {
- private IShellTransitionsImpl mIShellTransitions;
-
- @Override
- public IShellTransitions createExternalInterface() {
- if (mIShellTransitions != null) {
- mIShellTransitions.invalidate();
- }
- mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
- return mIShellTransitions;
- }
-
@Override
public void registerRemote(@NonNull TransitionFilter filter,
@NonNull RemoteTransition remoteTransition) {
@@ -928,7 +955,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IShellTransitionsImpl extends IShellTransitions.Stub {
+ private static class IShellTransitionsImpl extends IShellTransitions.Stub
+ implements ExternalInterfaceBinder {
private Transitions mTransitions;
IShellTransitionsImpl(Transitions transitions) {
@@ -938,7 +966,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mTransitions = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 3df33f3..dca516a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,15 +19,20 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
+import android.hardware.input.InputManager;
import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -47,7 +52,9 @@
* View model for the window decoration with a caption and shadows. Works with
* {@link CaptionWindowDecoration}.
*/
+
public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
+ private static final String TAG = "CaptionViewModel";
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -107,7 +114,6 @@
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
setupWindowDecorationForTransition(taskInfo, startT, finishT);
- setupCaptionColor(taskInfo, windowDecoration);
return true;
}
@@ -117,12 +123,6 @@
if (decoration == null) return;
decoration.relayout(taskInfo);
- setupCaptionColor(taskInfo, decoration);
- }
-
- private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
- int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
- decoration.setCaptionColor(statusBarColor);
}
@Override
@@ -153,6 +153,7 @@
private final DragResizeCallback mDragResizeCallback;
private int mDragPointerId = -1;
+ private boolean mDragActive = false;
private CaptionTouchEventListener(
RunningTaskInfo taskInfo,
@@ -173,42 +174,38 @@
} else {
mSyncQueue.queue(wct);
}
- } else if (id == R.id.maximize_window) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
- ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM;
- int displayWindowingMode =
- taskInfo.configuration.windowConfiguration.getDisplayWindowingMode();
- wct.setWindowingMode(mTaskToken,
- targetWindowingMode == displayWindowingMode
- ? WINDOWING_MODE_UNDEFINED : targetWindowingMode);
- if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- wct.setBounds(mTaskToken, null);
- }
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitionStarter.startWindowingModeTransition(targetWindowingMode, wct);
- } else {
- mSyncQueue.queue(wct);
- }
- } else if (id == R.id.minimize_window) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(mTaskToken, false);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitionStarter.startMinimizedModeTransition(wct);
- } else {
- mSyncQueue.queue(wct);
- }
+ } else if (id == R.id.back_button) {
+ injectBackKey();
+ }
+ }
+ private void injectBackKey() {
+ sendBackEvent(KeyEvent.ACTION_DOWN);
+ sendBackEvent(KeyEvent.ACTION_UP);
+ }
+
+ private void sendBackEvent(int action) {
+ final long when = SystemClock.uptimeMillis();
+ final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK,
+ 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD,
+ 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ InputDevice.SOURCE_KEYBOARD);
+
+ ev.setDisplayId(mContext.getDisplay().getDisplayId());
+ if (!InputManager.getInstance()
+ .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
+ Log.e(TAG, "Inject input event fail");
}
}
@Override
public boolean onTouch(View v, MotionEvent e) {
- if (v.getId() != R.id.caption) {
+ int id = v.getId();
+ if (id != R.id.caption_handle && id != R.id.caption) {
return false;
}
- handleEventForMove(e);
-
+ if (id == R.id.caption_handle || mDragActive) {
+ handleEventForMove(e);
+ }
if (e.getAction() != MotionEvent.ACTION_DOWN) {
return false;
}
@@ -231,6 +228,7 @@
}
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ mDragActive = true;
mDragPointerId = e.getPointerId(0);
mDragResizeCallback.onDragResizeStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
@@ -243,6 +241,7 @@
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ mDragActive = false;
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
.stableInsets().top;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 733f6b7..87700ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -22,12 +22,12 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.ViewConfiguration;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.R;
@@ -38,11 +38,7 @@
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
- * {@link CaptionWindowDecorViewModel}. The caption bar contains maximize and close buttons.
- *
- * {@link CaptionWindowDecorViewModel} can change the color of the caption bar based on the foremost
- * app's request through {@link #setCaptionColor(int)}, in which it changes the foreground color of
- * caption buttons according to the luminance of the background.
+ * {@link CaptionWindowDecorViewModel}. The caption bar contains a handle, back button, and close button.
*
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
@@ -54,7 +50,10 @@
// Height of button (32dp) + 2 * margin (5dp each)
private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42;
+ // Width of buttons (64dp) + handle (128dp) + padding (24dp total)
+ private static final int DECOR_CAPTION_WIDTH_IN_DIP = 216;
private static final int RESIZE_HANDLE_IN_DIP = 30;
+ private static final int RESIZE_CORNER_IN_DIP = 44;
private static final Rect EMPTY_OUTSET = new Rect();
private static final Rect RESIZE_HANDLE_OUTSET = new Rect(
@@ -73,6 +72,8 @@
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
+ private boolean mDesktopActive;
+
CaptionWindowDecoration(
Context context,
DisplayController displayController,
@@ -87,6 +88,7 @@
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
+ mDesktopActive = DesktopModeStatus.isActive(mContext);
}
void setCaptionListeners(
@@ -123,8 +125,8 @@
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
relayout(taskInfo, R.layout.caption_window_decoration, oldRootView,
- DECOR_CAPTION_HEIGHT_IN_DIP, outset, shadowRadiusDp, startT, finishT, wct, mResult);
- taskInfo = null; // Clear it just in case we use it accidentally
+ DECOR_CAPTION_HEIGHT_IN_DIP, DECOR_CAPTION_WIDTH_IN_DIP, outset, shadowRadiusDp,
+ startT, finishT, wct, mResult);
mTaskOrganizer.applyTransaction(wct);
@@ -137,6 +139,17 @@
setupRootView();
}
+ // If this task is not focused, do not show caption.
+ setCaptionVisibility(taskInfo.isFocused);
+
+ // Only handle should show if Desktop Mode is inactive.
+ boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
+ if (mDesktopActive != desktopCurrentStatus && taskInfo.isFocused) {
+ mDesktopActive = desktopCurrentStatus;
+ setButtonVisibility();
+ }
+ taskInfo = null; // Clear it just in case we use it accidentally
+
if (!isDragResizeable) {
closeDragResizeListener();
return;
@@ -145,16 +158,19 @@
if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
closeDragResizeListener();
mDragResizeListener = new DragResizeInputListener(
- mContext,
- mHandler,
- mChoreographer,
- mDisplay.getDisplayId(),
- mDecorationContainerSurface,
- mDragResizeCallback);
+ mContext,
+ mHandler,
+ mChoreographer,
+ mDisplay.getDisplayId(),
+ mDecorationContainerSurface,
+ mDragResizeCallback);
}
+ int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+
mDragResizeListener.setGeometry(
- mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP));
+ mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP),
+ (int) (mResult.mDensity * RESIZE_CORNER_IN_DIP), touchSlop);
}
/**
@@ -163,42 +179,46 @@
private void setupRootView() {
View caption = mResult.mRootView.findViewById(R.id.caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
- View maximize = caption.findViewById(R.id.maximize_window);
- if (DesktopModeStatus.IS_SUPPORTED) {
- // Hide maximize button when desktop mode is available
- maximize.setVisibility(View.GONE);
- } else {
- maximize.setVisibility(View.VISIBLE);
- maximize.setOnClickListener(mOnCaptionButtonClickListener);
- }
View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener);
- View minimize = caption.findViewById(R.id.minimize_window);
- minimize.setOnClickListener(mOnCaptionButtonClickListener);
+ View back = caption.findViewById(R.id.back_button);
+ back.setOnClickListener(mOnCaptionButtonClickListener);
+ View handle = caption.findViewById(R.id.caption_handle);
+ handle.setOnTouchListener(mOnCaptionTouchListener);
+ setButtonVisibility();
}
- void setCaptionColor(int captionColor) {
- if (mResult.mRootView == null) {
- return;
- }
-
+ /**
+ * Sets caption visibility based on task focus.
+ *
+ * @param visible whether or not the caption should be visible
+ */
+ private void setCaptionVisibility(boolean visible) {
+ int v = visible ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
- GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
- captionDrawable.setColor(captionColor);
+ caption.setVisibility(v);
+ }
+ /**
+ * Sets the visibility of buttons and color of caption based on desktop mode status
+ *
+ */
+ public void setButtonVisibility() {
+ int v = mDesktopActive ? View.VISIBLE : View.GONE;
+ View caption = mResult.mRootView.findViewById(R.id.caption);
+ View back = caption.findViewById(R.id.back_button);
+ View close = caption.findViewById(R.id.close_window);
+ back.setVisibility(v);
+ close.setVisibility(v);
int buttonTintColorRes =
- Color.valueOf(captionColor).luminance() < 0.5
- ? R.color.decor_button_light_color
- : R.color.decor_button_dark_color;
+ mDesktopActive ? R.color.decor_button_dark_color
+ : R.color.decor_button_light_color;
ColorStateList buttonTintColor =
caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
- View maximize = caption.findViewById(R.id.maximize_window);
- VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
- maximizeBackground.setTintList(buttonTintColor);
-
- View close = caption.findViewById(R.id.close_window);
- VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
- closeBackground.setTintList(buttonTintColor);
+ View handle = caption.findViewById(R.id.caption_handle);
+ VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
+ handleBackground.setTintList(buttonTintColor);
+ caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
}
private void closeDragResizeListener() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 3d01495..b9f16b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -16,11 +16,13 @@
package com.android.wm.shell.windowdecor;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -42,8 +44,11 @@
/**
* An input event listener registered to InputDispatcher to receive input events on task edges and
- * convert them to drag resize requests.
+ * and corners. Converts them to drag resize requests.
+ * Task edges are for resizing with a mouse.
+ * Task corners are for resizing with touch input.
*/
+// TODO(b/251270585): investigate how to pass taps in corners to the tasks
class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener";
@@ -63,8 +68,15 @@
private int mWidth;
private int mHeight;
private int mResizeHandleThickness;
+ private int mCornerSize;
+
+ private Rect mLeftTopCornerBounds;
+ private Rect mRightTopCornerBounds;
+ private Rect mLeftBottomCornerBounds;
+ private Rect mRightBottomCornerBounds;
private int mDragPointerId = -1;
+ private int mTouchSlop;
DragResizeInputListener(
Context context,
@@ -118,16 +130,23 @@
* @param height The height of the drag resize handler in pixels, including resize handle
* thickness. That is task height + 2 * resize handle thickness.
* @param resizeHandleThickness The thickness of the resize handle in pixels.
+ * @param cornerSize The size of the resize handle centered in each corner.
+ * @param touchSlop The distance in pixels user has to drag with touch for it to register as
+ * a resize action.
*/
- void setGeometry(int width, int height, int resizeHandleThickness) {
+ void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize,
+ int touchSlop) {
if (mWidth == width && mHeight == height
- && mResizeHandleThickness == resizeHandleThickness) {
+ && mResizeHandleThickness == resizeHandleThickness
+ && mCornerSize == cornerSize) {
return;
}
mWidth = width;
mHeight = height;
mResizeHandleThickness = resizeHandleThickness;
+ mCornerSize = cornerSize;
+ mTouchSlop = touchSlop;
Region touchRegion = new Region();
final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
@@ -146,6 +165,40 @@
mWidth, mHeight);
touchRegion.union(bottomInputBounds);
+ // Set up touch areas in each corner.
+ int cornerRadius = mCornerSize / 2;
+ mLeftTopCornerBounds = new Rect(
+ mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness + cornerRadius,
+ mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mLeftTopCornerBounds);
+
+ mRightTopCornerBounds = new Rect(
+ mWidth - mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness - cornerRadius,
+ mWidth - mResizeHandleThickness + cornerRadius,
+ mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mRightTopCornerBounds);
+
+ mLeftBottomCornerBounds = new Rect(
+ mResizeHandleThickness - cornerRadius,
+ mHeight - mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness + cornerRadius,
+ mHeight - mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mLeftBottomCornerBounds);
+
+ mRightBottomCornerBounds = new Rect(
+ mWidth - mResizeHandleThickness - cornerRadius,
+ mHeight - mResizeHandleThickness - cornerRadius,
+ mWidth - mResizeHandleThickness + cornerRadius,
+ mHeight - mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mRightBottomCornerBounds);
+
try {
mWindowSession.updateInputChannel(
mInputChannel.getToken(),
@@ -173,6 +226,9 @@
private final Choreographer mChoreographer;
private final Runnable mConsumeBatchEventRunnable;
private boolean mConsumeBatchEventScheduled;
+ private boolean mShouldHandleEvents;
+ private boolean mDragging;
+ private final PointF mActionDownPoint = new PointF();
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -216,41 +272,101 @@
}
MotionEvent e = (MotionEvent) inputEvent;
+ boolean result = false;
+ // Check if this is a touch event vs mouse event.
+ // Touch events are tracked in four corners. Other events are tracked in resize edges.
+ boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
- mDragPointerId = e.getPointerId(0);
- mCallback.onDragResizeStart(
- calculateCtrlType(e.getX(0), e.getY(0)), e.getRawX(0), e.getRawY(0));
+ float x = e.getX(0);
+ float y = e.getY(0);
+ if (isTouch) {
+ mShouldHandleEvents = isInCornerBounds(x, y);
+ } else {
+ mShouldHandleEvents = isInResizeHandleBounds(x, y);
+ }
+ if (mShouldHandleEvents) {
+ mDragPointerId = e.getPointerId(0);
+ float rawX = e.getRawX(0);
+ float rawY = e.getRawY(0);
+ mActionDownPoint.set(rawX, rawY);
+ int ctrlType = calculateCtrlType(isTouch, x, y);
+ mCallback.onDragResizeStart(ctrlType, rawX, rawY);
+ result = true;
+ }
break;
}
case MotionEvent.ACTION_MOVE: {
+ if (!mShouldHandleEvents) {
+ break;
+ }
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
- mCallback.onDragResizeMove(
- e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ float rawX = e.getRawX(dragPointerIndex);
+ float rawY = e.getRawY(dragPointerIndex);
+ if (isTouch) {
+ // Check for touch slop for touch events
+ float dx = rawX - mActionDownPoint.x;
+ float dy = rawY - mActionDownPoint.y;
+ if (!mDragging && Math.hypot(dx, dy) > mTouchSlop) {
+ mDragging = true;
+ }
+ } else {
+ // For all other types allow immediate dragging.
+ mDragging = true;
+ }
+ if (mDragging) {
+ mCallback.onDragResizeMove(rawX, rawY);
+ result = true;
+ }
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- int dragPointerIndex = e.findPointerIndex(mDragPointerId);
- mCallback.onDragResizeEnd(
- e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ if (mDragging) {
+ int dragPointerIndex = e.findPointerIndex(mDragPointerId);
+ mCallback.onDragResizeEnd(
+ e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ }
+ mDragging = false;
+ mShouldHandleEvents = false;
+ mActionDownPoint.set(0, 0);
mDragPointerId = -1;
+ result = true;
break;
}
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
updateCursorType(e.getXCursorPosition(), e.getYCursorPosition());
+ result = true;
break;
}
case MotionEvent.ACTION_HOVER_EXIT:
mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
+ result = true;
break;
}
- return true;
+ return result;
+ }
+
+ private boolean isInCornerBounds(float xf, float yf) {
+ return calculateCornersCtrlType(xf, yf) != 0;
+ }
+
+ private boolean isInResizeHandleBounds(float x, float y) {
+ return calculateResizeHandlesCtrlType(x, y) != 0;
}
@TaskPositioner.CtrlType
- private int calculateCtrlType(float x, float y) {
+ private int calculateCtrlType(boolean isTouch, float x, float y) {
+ if (isTouch) {
+ return calculateCornersCtrlType(x, y);
+ }
+ return calculateResizeHandlesCtrlType(x, y);
+ }
+
+ @TaskPositioner.CtrlType
+ private int calculateResizeHandlesCtrlType(float x, float y) {
int ctrlType = 0;
if (x < mResizeHandleThickness) {
ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
@@ -267,8 +383,27 @@
return ctrlType;
}
+ @TaskPositioner.CtrlType
+ private int calculateCornersCtrlType(float x, float y) {
+ int xi = (int) x;
+ int yi = (int) y;
+ if (mLeftTopCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_TOP;
+ }
+ if (mLeftBottomCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_BOTTOM;
+ }
+ if (mRightTopCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_TOP;
+ }
+ if (mRightBottomCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_BOTTOM;
+ }
+ return 0;
+ }
+
private void updateCursorType(float x, float y) {
- @TaskPositioner.CtrlType int ctrlType = calculateCtrlType(x, y);
+ @TaskPositioner.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
int cursorType = PointerIcon.TYPE_DEFAULT;
switch (ctrlType) {
@@ -292,4 +427,4 @@
mInputManager.setPointerIconType(cursorType);
}
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 280569b..27c1011 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -25,9 +25,10 @@
class TaskPositioner implements DragResizeCallback {
- @IntDef({CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+ @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
@interface CtrlType {}
+ static final int CTRL_TYPE_UNDEFINED = 0;
static final int CTRL_TYPE_LEFT = 1;
static final int CTRL_TYPE_RIGHT = 2;
static final int CTRL_TYPE_TOP = 4;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 3e3a864..bf863ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -143,9 +143,9 @@
abstract void relayout(RunningTaskInfo taskInfo);
void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
- Rect outsetsDp, float shadowRadiusDp, SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT, WindowContainerTransaction wct,
- RelayoutResult<T> outResult) {
+ float captionWidthDp, Rect outsetsDp, float shadowRadiusDp,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+ WindowContainerTransaction wct, RelayoutResult<T> outResult) {
outResult.reset();
final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
@@ -249,8 +249,15 @@
}
final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity);
+ final int captionWidth = (int) Math.ceil(captionWidthDp * outResult.mDensity);
+
+ //Prevent caption from going offscreen if task is too high up
+ final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
+
startT.setPosition(
- mCaptionContainerSurface, -decorContainerOffsetX, -decorContainerOffsetY)
+ mCaptionContainerSurface, -decorContainerOffsetX
+ + taskBounds.width() / 2 - captionWidth / 2,
+ -decorContainerOffsetY - captionYPos)
.setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight)
.show(mCaptionContainerSurface);
@@ -264,7 +271,7 @@
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(taskBounds.width(), captionHeight,
+ new WindowManager.LayoutParams(captionWidth, captionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -282,7 +289,7 @@
// Caption insets
mCaptionInsetsRect.set(taskBounds);
- mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight;
+ mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight - captionYPos;
wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES);
} else {
startT.hide(mCaptionContainerSurface);
@@ -388,4 +395,4 @@
return new SurfaceControlViewHost(c, d, wmm);
}
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 98b5912..79070b1 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.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import java.util.ArrayList;
+
/**
* Tests for {@link ActivityEmbeddingAnimationRunner}.
*
@@ -62,13 +64,13 @@
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 @@
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 3792e83..54a12ab 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 @@
@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 @@
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 baecf6f..4d98b6b 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 @@
@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/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 90a3773..077e9ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -63,7 +63,9 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Ignore;
@@ -102,6 +104,9 @@
@Mock
private IBackNaviAnimationController mIBackNaviAnimationController;
+ @Mock
+ private ShellController mShellController;
+
private BackAnimationController mController;
private int mEventTime = 0;
@@ -118,7 +123,7 @@
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
- mController = new BackAnimationController(mShellInit,
+ mController = new BackAnimationController(mShellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
@@ -175,6 +180,12 @@
}
@Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION), any(), any());
+ }
+
+ @Test
@Ignore("b/207481538")
public void crossActivity_screenshotAttachedAndVisible() {
SurfaceControl screenshotSurface = new SurfaceControl();
@@ -250,7 +261,7 @@
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
ShellInit shellInit = new ShellInit(mShellExecutor);
- mController = new BackAnimationController(shellInit,
+ mController = new BackAnimationController(shellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 695550d..f6d6c03 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.common.split;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.google.common.truth.Truth.assertThat;
@@ -91,6 +92,14 @@
// Verify updateConfiguration returns true if the root bounds changed.
config.windowConfiguration.setBounds(new Rect(0, 0, 2160, 1080));
assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+ // Verify updateConfiguration returns true if the orientation changed.
+ config.orientation = ORIENTATION_LANDSCAPE;
+ assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+ // Verify updateConfiguration returns true if the density changed.
+ config.densityDpi = 123;
+ assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index dd23d97..c850a3b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -52,6 +52,7 @@
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -68,6 +69,8 @@
public class DesktopModeControllerTest extends ShellTestCase {
@Mock
+ private ShellController mShellController;
+ @Mock
private ShellTaskOrganizer mShellTaskOrganizer;
@Mock
private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@@ -94,8 +97,8 @@
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
- mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
- mRootTaskDisplayAreaOrganizer, mMockTransitions,
+ mController = new DesktopModeController(mContext, mShellInit, mShellController,
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
mDesktopModeTaskRepository, mMockHandler, mExecutor);
when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
index a88c837..d378a17 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
@@ -52,6 +52,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.After;
import org.junit.Before;
@@ -168,6 +169,18 @@
}
}
+ @Test
+ public void onInit_addExternalInterface() {
+ if (FLOATING_TASKS_ACTUALLY_ENABLED) {
+ createController();
+ setUpTabletConfig();
+ mController.onInit();
+
+ verify(mShellController, times(1)).addExternalInterface(
+ ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any());
+ }
+ }
+
//
// Tests for floating layer, which is only available for tablets.
//
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index cf8297e..8ad3d2a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -51,6 +51,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -176,6 +177,12 @@
}
@Test
+ public void testControllerRegisteresExternalInterface() {
+ verify(mMockShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED), any(), any());
+ }
+
+ @Test
public void testDefaultShouldNotInOneHanded() {
// Assert default transition state is STATE_NONE
assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 1e08f1e..d06fb55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -61,6 +62,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -152,6 +154,12 @@
}
@Test
+ public void instantiatePipController_registerExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), any());
+ }
+
+ @Test
public void instantiatePipController_registerUserChangeListener() {
verify(mShellController, times(1)).addUserChangeListener(any());
}
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 dba037d..3bd2ae7 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 @@
@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 @@
< mPipBoundsState.getBounds().width());
}
+ @Test
+ public void testUserResizeTo() {
+ // resizing the bounds to normal bounds at first
+ mPipResizeGestureHandler.userResizeTo(mPipBoundsState.getNormalBounds(),
+ DEFAULT_SNAP_FRACTION);
+
+ assertPipBoundsUserResizedTo(mPipBoundsState.getNormalBounds());
+
+ verify(mPipTaskOrganizer, times(1))
+ .scheduleUserResizePip(any(), any(), any());
+
+ verify(mPipTaskOrganizer, times(1))
+ .scheduleFinishResizePip(any(), any());
+
+ // bounds with max size
+ final Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
+
+ // resizing the bounds to maximum bounds the second time
+ mPipResizeGestureHandler.userResizeTo(maxBounds, DEFAULT_SNAP_FRACTION);
+
+ assertPipBoundsUserResizedTo(maxBounds);
+
+ // another call to scheduleUserResizePip() and scheduleFinishResizePip() makes
+ // the total number of invocations 2 for each method
+ verify(mPipTaskOrganizer, times(2))
+ .scheduleUserResizePip(any(), any(), any());
+
+ verify(mPipTaskOrganizer, times(2))
+ .scheduleFinishResizePip(any(), any());
+ }
+
+ private void assertPipBoundsUserResizedTo(Rect bounds) {
+ // check user-resized bounds
+ assertEquals(mPipResizeGestureHandler.getUserResizeBounds().width(), bounds.width());
+ assertEquals(mPipResizeGestureHandler.getUserResizeBounds().height(), bounds.height());
+
+ // check if the bounds are the same
+ assertEquals(mPipBoundsState.getBounds().width(), bounds.width());
+ assertEquals(mPipBoundsState.getBounds().height(), bounds.height());
+
+ // a flag should be set to indicate pip has been resized by the user
+ assertTrue(mPipBoundsState.hasUserResizedPip());
+ }
+
private MotionEvent obtainMotionEvent(int action, int topLeft, int bottomRight) {
final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[2];
for (int i = 0; i < 2; i++) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index b8aaaa7..f6ac3ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -57,7 +58,9 @@
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -84,6 +87,8 @@
@Mock
private TaskStackListenerImpl mTaskStackListener;
@Mock
+ private ShellController mShellController;
+ @Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
private DesktopModeTaskRepository mDesktopModeTaskRepository;
@@ -101,7 +106,7 @@
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
- mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+ mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
Optional.of(mDesktopModeTaskRepository), mMainExecutor));
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
@@ -121,6 +126,12 @@
}
@Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS), any(), any());
+ }
+
+ @Test
public void testAddRemoveSplitNotifyChange() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 5a68361..55883ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -58,6 +58,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -133,6 +134,15 @@
}
@Test
+ public void instantiateController_addExternalInterface() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any());
+ }
+
+ @Test
public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 35515e3..90165d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -36,7 +37,9 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -56,25 +59,34 @@
private @Mock Context mContext;
private @Mock DisplayManager mDisplayManager;
- private @Mock ShellInit mShellInit;
+ private @Mock ShellController mShellController;
private @Mock ShellTaskOrganizer mTaskOrganizer;
private @Mock ShellExecutor mMainExecutor;
private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm;
private @Mock IconProvider mIconProvider;
private @Mock TransactionPool mTransactionPool;
private StartingWindowController mController;
+ private ShellInit mShellInit;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
- mController = new StartingWindowController(mContext, mShellInit, mTaskOrganizer,
- mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ mShellInit = spy(new ShellInit(mMainExecutor));
+ mController = new StartingWindowController(mContext, mShellInit, mShellController,
+ mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ mShellInit.init();
}
@Test
- public void instantiate_addInitCallback() {
+ public void instantiateController_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
+
+ @Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index d6ddba9..fbc50c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -16,12 +16,16 @@
package com.android.wm.shell.sysui;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -30,6 +34,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import org.junit.After;
@@ -49,6 +54,7 @@
public class ShellControllerTest extends ShellTestCase {
private static final int TEST_USER_ID = 100;
+ private static final String EXTRA_TEST_BINDER = "test_binder";
@Mock
private ShellInit mShellInit;
@@ -81,6 +87,47 @@
}
@Test
+ public void testAddExternalInterface_ensureCallback() {
+ Binder callback = new Binder();
+ ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+ @Override
+ public void invalidate() {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return callback;
+ }
+ };
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+
+ Bundle b = new Bundle();
+ mController.asShell().createExternalInterfaces(b);
+ assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
+ }
+
+ @Test
+ public void testAddExternalInterface_disallowDuplicateKeys() {
+ Binder callback = new Binder();
+ ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+ @Override
+ public void invalidate() {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return callback;
+ }
+ };
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+ assertThrows(IllegalArgumentException.class, () -> {
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+ });
+ }
+
+ @Test
public void testAddUserChangeListener_ensureCallback() {
mController.addUserChangeListener(mUserChangeListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index db9136d..c764741 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -87,7 +87,9 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -124,12 +126,25 @@
@Test
public void instantiate_addInitCallback() {
ShellInit shellInit = mock(ShellInit.class);
- final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
- createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
verify(shellInit, times(1)).addInitCallback(any(), eq(t));
}
@Test
+ public void instantiateController_addExternalInterface() {
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ ShellController shellController = mock(ShellController.class);
+ final Transitions t = new Transitions(mContext, shellInit, shellController,
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
+ shellInit.init();
+ verify(shellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
+ }
+
+ @Test
public void testBasicTransitionFlow() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -1063,8 +1078,9 @@
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
- final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
- createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
shellInit.init();
return t;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index ab6ac94..fa62b9c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -77,6 +77,7 @@
@RunWith(AndroidTestingRunner.class)
public class WindowDecorationTests extends ShellTestCase {
private static final int CAPTION_HEIGHT_DP = 32;
+ private static final int CAPTION_WIDTH_DP = 216;
private static final int SHADOW_RADIUS_DP = 5;
private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
@@ -220,7 +221,7 @@
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+ verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -410,7 +411,7 @@
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP,
- mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT,
+ CAPTION_WIDTH_DP, mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT,
mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mRelayoutResult);
}
}
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index db54ae3..d4439f9 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 5c9ab7b..4e7e367 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -350,7 +350,7 @@
}
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 @@
});
}
- 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 dd776a9..8405335 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/PrintSpooler/res/values-te/strings.xml b/packages/PrintSpooler/res/values-te/strings.xml
index e00e28c..3ab43f1 100644
--- a/packages/PrintSpooler/res/values-te/strings.xml
+++ b/packages/PrintSpooler/res/values-te/strings.xml
@@ -21,8 +21,8 @@
<string name="label_destination" msgid="9132510997381599275">"గమ్యం"</string>
<string name="label_copies" msgid="3634531042822968308">"కాపీలు"</string>
<string name="label_copies_summary" msgid="3861966063536529540">"కాపీలు:"</string>
- <string name="label_paper_size" msgid="908654383827777759">"కాగితపు పరిమాణం"</string>
- <string name="label_paper_size_summary" msgid="5668204981332138168">"కాగితపు పరిమాణం:"</string>
+ <string name="label_paper_size" msgid="908654383827777759">"కాగితపు సైజ్"</string>
+ <string name="label_paper_size_summary" msgid="5668204981332138168">"కాగితపు సైజ్:"</string>
<string name="label_color" msgid="1108690305218188969">"రంగు"</string>
<string name="label_duplex" msgid="5370037254347072243">"రెండు వైపుల"</string>
<string name="label_orientation" msgid="2853142581990496477">"ఓరియంటేషన్"</string>
@@ -40,7 +40,7 @@
<string name="print_dialog" msgid="32628687461331979">"ముద్రణ డైలాగ్"</string>
<string name="current_page_template" msgid="5145005201131935302">"<xliff:g id="CURRENT_PAGE">%1$d</xliff:g>/<xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
<string name="page_description_template" msgid="6831239682256197161">"<xliff:g id="PAGE_COUNT">%2$d</xliff:g>లో <xliff:g id="CURRENT_PAGE">%1$d</xliff:g>వ పేజీ"</string>
- <string name="summary_template" msgid="8899734908625669193">"సారాంశం, కాపీలు <xliff:g id="COPIES">%1$s</xliff:g>, కాగితం పరిమాణం <xliff:g id="PAPER_SIZE">%2$s</xliff:g>"</string>
+ <string name="summary_template" msgid="8899734908625669193">"సారాంశం, కాపీలు <xliff:g id="COPIES">%1$s</xliff:g>, కాగితం సైజ్ <xliff:g id="PAPER_SIZE">%2$s</xliff:g>"</string>
<string name="expand_handle" msgid="7282974448109280522">"విస్తరణ హ్యాండిల్"</string>
<string name="collapse_handle" msgid="6886637989442507451">"కుదింపు హ్యాండిల్"</string>
<string name="print_button" msgid="645164566271246268">"ప్రింట్ చేయండి"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 734eb33..6bb7595 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-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 0a64da3..b024a87 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -116,7 +116,7 @@
<string name="bluetooth_profile_headset" msgid="5395952236133499331">"المكالمات الهاتفية"</string>
<string name="bluetooth_profile_opp" msgid="6692618568149493430">"نقل الملف"</string>
<string name="bluetooth_profile_hid" msgid="2969922922664315866">"جهاز الإرسال"</string>
- <string name="bluetooth_profile_pan" msgid="1006235139308318188">"استخدام الإنترنت"</string>
+ <string name="bluetooth_profile_pan" msgid="1006235139308318188">"الوصول إلى الإنترنت"</string>
<string name="bluetooth_profile_pbap" msgid="4262303387989406171">"مشاركة جهات الاتصال وسجل المكالمات"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"استخدام إعدادات بلوتوث لمشاركة جهات الاتصال وسجل المكالمات"</string>
<string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"مشاركة اتصال الإنترنت"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index d11ed3c..976949c 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -75,7 +75,7 @@
<string name="wifi_limited_connection" msgid="1184778285475204682">"ইণ্টাৰনেট সংযোগ সীমিত"</string>
<string name="wifi_status_no_internet" msgid="3799933875988829048">"ইণ্টাৰনেট সংযোগ নাই"</string>
<string name="wifi_status_sign_in_required" msgid="2236267500459526855">"ছাইন ইন কৰা দৰকাৰী"</string>
- <string name="wifi_ap_unable_to_handle_new_sta" msgid="5885145407184194503">"একচেছ পইণ্ট কিছু সময়ৰ বাবে পূৰ্ণ হৈ আছে"</string>
+ <string name="wifi_ap_unable_to_handle_new_sta" msgid="5885145407184194503">"এক্সেছ পইণ্ট সাময়িকভাৱে পূৰ্ণ হৈ আছে"</string>
<string name="connected_via_carrier" msgid="1968057009076191514">"%1$sৰ যোগেৰে সংযোজিত"</string>
<string name="available_via_carrier" msgid="465598683092718294">"%1$sৰ মাধ্যমেৰে উপলব্ধ"</string>
<string name="osu_opening_provider" msgid="4318105381295178285">"<xliff:g id="PASSPOINTPROVIDER">%1$s</xliff:g> খুলি থকা হৈছে"</string>
@@ -98,7 +98,7 @@
<string name="bluetooth_pairing" msgid="4269046942588193600">"যোৰা লগোৱা হৈছে…"</string>
<string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"সংযোগ কৰা হ’ল (ফ\'ন নাই)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"সংযোগ কৰা হ’ল (মিডিয়া নাই)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
- <string name="bluetooth_connected_no_map" msgid="3381860077002724689">"সংযোগ কৰা হ’ল (বাৰ্তাত প্ৰৱেশাধিকাৰ নাই)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
+ <string name="bluetooth_connected_no_map" msgid="3381860077002724689">"সংযোগ কৰা হ’ল (বাৰ্তাৰ এক্সেছ নাই)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="2893204819854215433">"সংযোগ কৰা হ’ল (কোনো ফ\'ন বা মিডিয়া নাই)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_battery_level" msgid="5410325759372259950">"সংযোগ কৰা হ’ল, বেটাৰীৰ স্তৰ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
<string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"সংযোগ কৰা হ’ল (ফ\'ন নাই), বেটাৰীৰ স্তৰ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
@@ -139,7 +139,7 @@
<string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"ডিভাইচৰ সৈতে স্থানীয় ইণ্টাৰনেট সংযোগ শ্বেয়াৰ কৰা হৈছে"</string>
<string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"ইণ্টাৰনেট চলাবলৈ ব্যৱহাৰ কৰক"</string>
<string name="bluetooth_map_profile_summary_use_for" msgid="4453622103977592583">"মেপৰ বাবে ব্যৱহাৰ কৰক"</string>
- <string name="bluetooth_sap_profile_summary_use_for" msgid="6204902866176714046">"ছিমত প্ৰৱেশৰ বাবে ব্যৱহাৰ কৰক"</string>
+ <string name="bluetooth_sap_profile_summary_use_for" msgid="6204902866176714046">"ছিমৰ এক্সেছৰ বাবে ব্যৱহাৰ কৰক"</string>
<string name="bluetooth_a2dp_profile_summary_use_for" msgid="7324694226276491807">"মিডিয়া অডিঅ\'ৰ বাবে ব্যৱহাৰ কৰক"</string>
<string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ফ\'ন অডিঅ\'ৰ বাবে ব্যৱহাৰ কৰক"</string>
<string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ফাইল স্থানান্তৰ কৰিবলৈ ব্যৱহাৰ কৰক"</string>
@@ -149,7 +149,7 @@
<string name="bluetooth_pairing_accept" msgid="2054232610815498004">"পেয়াৰ কৰক"</string>
<string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"পেয়াৰ কৰক"</string>
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"বাতিল কৰক"</string>
- <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"যোৰা লগালে ইয়ে সংযোজিত কৰাৰ সময়ত আপোনাৰ সম্পৰ্কসমূহ আৰু কলৰ ইতিহাস চাবলৈ অনুমতি দিব।"</string>
+ <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"পেয়াৰিঙে সংযোজিত হ\'লে আপোনাৰ সম্পৰ্ক আৰু কলৰ ইতিহাসৰ এক্সেছ প্ৰদান কৰে।"</string>
<string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ৰ সৈতে পেয়াৰ কৰিব পৰা নগ’ল।"</string>
<string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"এটা ভুল পিন বা পাছকীৰ কাৰণে <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ৰ সৈতে পেয়াৰ কৰিব পৰা নাই।"</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ৰ সৈতে যোগাযোগ কৰিব পৰা নগ\'ল"</string>
@@ -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>
@@ -342,7 +342,7 @@
<string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ব্লুটুথ Gabeldorche সুবিধাৰ সমষ্টিটো সক্ষম কৰে।"</string>
<string name="enhanced_connectivity_summary" msgid="1576414159820676330">"উন্নত সংযোগ সুবিধাটো সক্ষম কৰে।"</string>
<string name="enable_terminal_title" msgid="3834790541986303654">"স্থানীয় টাৰ্মিনেল"</string>
- <string name="enable_terminal_summary" msgid="2481074834856064500">"স্থানীয় শ্বেল প্ৰৱেশাধিকাৰ দিয়া টাৰ্মিনেল এপ্ সক্ষম কৰক"</string>
+ <string name="enable_terminal_summary" msgid="2481074834856064500">"স্থানীয় শ্বেলৰ এক্সেছ দিয়া টাৰ্মিনেল এপ্ সক্ষম কৰক"</string>
<string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP পৰীক্ষণ"</string>
<string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"HDCP পৰীক্ষণ আচৰণ ছেট কৰক"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"ডিবাগিং"</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 98cc18e..b6eb82e 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-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index a7618df..16ee8b8 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -54,7 +54,7 @@
<string name="wifi_disabled_generic" msgid="2651916945380294607">"Slått av"</string>
<string name="wifi_disabled_network_failure" msgid="2660396183242399585">"IP-konfigurasjonsfeil"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="1302938248432705534">"Ikke tilkoblet på grunn av nettverk av lav kvalitet"</string>
- <string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"Wi-Fi-tilkoblingsfeil"</string>
+ <string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"Wifi-tilkoblingsfeil"</string>
<string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Autentiseringsproblem"</string>
<string name="wifi_cant_connect" msgid="5718417542623056783">"Kan ikke koble til"</string>
<string name="wifi_cant_connect_to_ap" msgid="3099667989279700135">"Kan ikke koble til «<xliff:g id="AP_NAME">%1$s</xliff:g>»"</string>
@@ -161,12 +161,12 @@
<string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Øretelefoner"</string>
<string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Inndata fra ytre utstyrsenheter"</string>
<string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
- <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi er av."</string>
- <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi er frakoblet."</string>
- <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wi-Fi-signal med én stolpe."</string>
- <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi-signal med to stolper."</string>
- <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi-signal med tre stolper."</string>
- <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi-signalet er ved full styrke."</string>
+ <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi er av."</string>
+ <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi er frakoblet."</string>
+ <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wifi-signal med én stolpe."</string>
+ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi-signal med to stolper."</string>
+ <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi-signal med tre stolper."</string>
+ <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi-signalet er ved full styrke."</string>
<string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Åpent nettverk"</string>
<string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sikkert nettverk"</string>
<string name="process_kernel_label" msgid="950292573930336765">"Android-operativsystem"</string>
@@ -236,7 +236,7 @@
<string name="enable_adb_summary" msgid="3711526030096574316">"Feilsøkingsmodus når USB kobles til"</string>
<string name="clear_adb_keys" msgid="3010148733140369917">"USB-feilsøking – opphev autorisasjon"</string>
<string name="enable_adb_wireless" msgid="6973226350963971018">"Trådløs feilsøking"</string>
- <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Feilsøkingsmodus når Wi-Fi er tilkoblet"</string>
+ <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Feilsøkingsmodus når Wifi er tilkoblet"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Feil"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Trådløs feilsøking"</string>
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"For å se og bruke tilgjengelige enheter, slå på trådløs feilsøking"</string>
@@ -255,13 +255,13 @@
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Wi‑Fi-tilkoblingskode"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Tilkoblingen mislyktes"</string>
<string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Sørg for at enheten er koblet til samme nettverk."</string>
- <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Koble til enheten via Wi-Fi ved å skanne en QR-kode"</string>
+ <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Koble til enheten via Wifi ved å skanne en QR-kode"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Kobler til enheten …"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Kunne ikke koble til enheten. Enten var QR-koden feil, eller enheten er ikke koblet til samme nettverk."</string>
<string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP-adresse og port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Skann QR-koden"</string>
- <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Koble til enheten via Wi-Fi ved å skanne en QR-kode"</string>
- <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Koble til et Wi-Fi-nettverk"</string>
+ <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Koble til enheten via Wifi ved å skanne en QR-kode"</string>
+ <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Koble til et Wifi-nettverk"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, feilsøking, utvikler"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"Snarvei til feilrapport"</string>
<string name="bugreport_in_power_summary" msgid="1885529649381831775">"Vis en knapp for generering av feilrapport i batterimenyen"</string>
@@ -278,7 +278,7 @@
<string name="mock_location_app_set" msgid="4706722469342913843">"App for fiktiv posisjon: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"Nettverk"</string>
<string name="wifi_display_certification" msgid="1805579519992520381">"Trådløs skjerm-sertifisering"</string>
- <string name="wifi_verbose_logging" msgid="1785910450009679371">"Slå på detaljert Wi-Fi-loggføring"</string>
+ <string name="wifi_verbose_logging" msgid="1785910450009679371">"Slå på detaljert Wifi-loggføring"</string>
<string name="wifi_scan_throttling" msgid="2985624788509913617">"Begrensning av Wi‑Fi-skanning"</string>
<string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Ikke-vedvarende tilfeldiggjøring av MAC-adresse for Wi‑Fi"</string>
<string name="mobile_data_always_on" msgid="8275958101875563572">"Mobildata er alltid aktiv"</string>
@@ -310,7 +310,7 @@
<string name="private_dns_mode_provider_hostname_hint" msgid="6564868953748514595">"Skriv inn DNS-leverandørens vertsnavn"</string>
<string name="private_dns_mode_provider_failure" msgid="8356259467861515108">"Kunne ikke koble til"</string>
<string name="wifi_display_certification_summary" msgid="8111151348106907513">"Vis alternativer for sertifisering av trådløs skjerm"</string>
- <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Øk nivået av Wi-Fi-logging – vis per SSID RSSI i Wi-Fi-velgeren"</string>
+ <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Øk nivået av Wifi-logging – vis per SSID RSSI i Wifi-velgeren"</string>
<string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduserer batteriforbruket og forbedrer nettverksytelsen"</string>
<string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"Når denne modusen er slått på, kan MAC-adressen til denne enheten endres hver gang den kobler seg til et nettverk som har tilfeldiggjøring av MAC-adresse slått på."</string>
<string name="wifi_metered_label" msgid="8737187690304098638">"Med datamåling"</string>
@@ -326,7 +326,7 @@
<string name="allow_mock_location" msgid="2102650981552527884">"Tillat simulert posisjon"</string>
<string name="allow_mock_location_summary" msgid="179780881081354579">"Tillat bruk av simulerte GPS-koordinater"</string>
<string name="debug_view_attributes" msgid="3539609843984208216">"Slå på inspeksjon av visningsattributt"</string>
- <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Ha alltid mobildata slått på, selv når Wi-Fi er aktiv (for hurtig nettverksbytting)."</string>
+ <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Ha alltid mobildata slått på, selv når Wifi er aktiv (for hurtig nettverksbytting)."</string>
<string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Bruk maskinvareakselerasjon for internettdeling hvis det er tilgjengelig"</string>
<string name="adb_warning_title" msgid="7708653449506485728">"Tillate USB-feilsøking?"</string>
<string name="adb_warning_message" msgid="8145270656419669221">"USB-feilsøking er bare ment for utviklingsformål. Bruk det til å kopiere data mellom datamaskinen og enheten, installere apper på enheten uten varsel og lese loggdata."</string>
@@ -571,7 +571,7 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"Bruker"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Begrenset profil"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Vil du legge til en ny bruker?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"Du kan dele denne enheten med andre folk ved å opprette flere brukere. Hver bruker har sin egen plass de kan tilpasse med apper, bakgrunner og annet. Brukere kan også justere enhetsinnstillinger, for eksempel Wi-Fi, som påvirker alle.\n\nNår du legger til en ny bruker, må vedkommende angi innstillinger for plassen sin.\n\nAlle brukere kan oppdatere apper for alle andre brukere. Innstillinger og tjenester for tilgjengelighet overføres kanskje ikke til den nye brukeren."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"Du kan dele denne enheten med andre folk ved å opprette flere brukere. Hver bruker har sin egen plass de kan tilpasse med apper, bakgrunner og annet. Brukere kan også justere enhetsinnstillinger, for eksempel Wifi, som påvirker alle.\n\nNår du legger til en ny bruker, må vedkommende angi innstillinger for plassen sin.\n\nAlle brukere kan oppdatere apper for alle andre brukere. Innstillinger og tjenester for tilgjengelighet overføres kanskje ikke til den nye brukeren."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Når du legger til en ny bruker, må hen konfigurere sitt eget område.\n\nAlle brukere kan oppdatere apper for alle andre brukere."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Konfigurere brukeren nå?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Sørg for at brukeren er tilgjengelig for å konfigurere området sitt på enheten"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index ca46d50d2..180b1fb 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -95,7 +95,7 @@
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Kopplar ifrån…"</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"Ansluter…"</string>
<string name="bluetooth_connected" msgid="8065345572198502293">"Ansluten<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
- <string name="bluetooth_pairing" msgid="4269046942588193600">"Parkoppling…"</string>
+ <string name="bluetooth_pairing" msgid="4269046942588193600">"Parkopplar…"</string>
<string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"Ansluten (ingen mobil)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"Ansluten (inga medier)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_map" msgid="3381860077002724689">"Ansluten (ingen meddelandeåtkomst)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 5d5ebe7..c17a1d5 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -509,7 +509,7 @@
<string name="screen_zoom_summary_extremely_large" msgid="1438045624562358554">"అతి పెద్దగా"</string>
<string name="screen_zoom_summary_custom" msgid="3468154096832912210">"అనుకూలం (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
<string name="content_description_menu_button" msgid="6254844309171779931">"మెనూ"</string>
- <string name="retail_demo_reset_message" msgid="5392824901108195463">"డెమో మోడ్లో ఫ్యాక్టరీ రీసెట్ను నిర్వహించడానికి పాస్వర్డ్ను నమోదు చేయండి"</string>
+ <string name="retail_demo_reset_message" msgid="5392824901108195463">"డెమో మోడ్లో ఫ్యాక్టరీ రీసెట్ను మేనేజ్ చేయడానికి పాస్వర్డ్ను నమోదు చేయండి"</string>
<string name="retail_demo_reset_next" msgid="3688129033843885362">"తర్వాత"</string>
<string name="retail_demo_reset_title" msgid="1866911701095959800">"పాస్వర్డ్ అవసరం"</string>
<string name="active_input_method_subtypes" msgid="4232680535471633046">"సక్రియ ఇన్పుట్ పద్ధతులు"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index eff9e74..ee65ef4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -22,8 +22,11 @@
public class AccessibilityContentDescriptions {
private AccessibilityContentDescriptions() {}
+
+ public static final int PHONE_SIGNAL_STRENGTH_NONE = R.string.accessibility_no_phone;
+
public static final int[] PHONE_SIGNAL_STRENGTH = {
- R.string.accessibility_no_phone,
+ PHONE_SIGNAL_STRENGTH_NONE,
R.string.accessibility_phone_one_bar,
R.string.accessibility_phone_two_bars,
R.string.accessibility_phone_three_bars,
diff --git a/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcon.kt b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcon.kt
new file mode 100644
index 0000000..3daf8c2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcon.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.settingslib
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+
+/**
+ * A specification for the icon displaying the mobile network type -- 4G, 5G, LTE, etc. (aka "RAT
+ * icon" or "data type icon"). This is *not* the signal strength triangle.
+ *
+ * This is intended to eventually replace [SignalIcon.MobileIconGroup]. But for now,
+ * [MobileNetworkTypeIcons] just reads from the existing set of [SignalIcon.MobileIconGroup]
+ * instances to not duplicate data.
+ *
+ * TODO(b/238425913): Remove [SignalIcon.MobileIconGroup] and replace it with this class so that we
+ * don't need to fill in the superfluous fields from its parent [SignalIcon.IconGroup] class. Then
+ * this class can become either a sealed class or an enum with parameters.
+ */
+data class MobileNetworkTypeIcon(
+ /** A human-readable name for this network type, used for logging. */
+ val name: String,
+
+ /** The resource ID of the icon drawable to use. */
+ @DrawableRes val iconResId: Int,
+
+ /** The resource ID of the content description to use. */
+ @StringRes val contentDescriptionResId: Int,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcons.kt b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcons.kt
new file mode 100644
index 0000000..2c5ee89
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcons.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib
+
+import com.android.settingslib.mobile.TelephonyIcons.ICON_NAME_TO_ICON
+
+/**
+ * A utility class to fetch instances of [MobileNetworkTypeIcon] given a
+ * [SignalIcon.MobileIconGroup].
+ *
+ * Use [getNetworkTypeIcon] to fetch the instances.
+ */
+class MobileNetworkTypeIcons {
+ companion object {
+ /**
+ * A map from a [SignalIcon.MobileIconGroup.name] to an instance of [MobileNetworkTypeIcon],
+ * which is the preferred class going forward.
+ */
+ private val MOBILE_NETWORK_TYPE_ICONS: Map<String, MobileNetworkTypeIcon>
+
+ init {
+ // Build up the mapping from the old implementation to the new one.
+ val tempMap: MutableMap<String, MobileNetworkTypeIcon> = mutableMapOf()
+
+ ICON_NAME_TO_ICON.forEach { (_, mobileIconGroup) ->
+ tempMap[mobileIconGroup.name] = mobileIconGroup.toNetworkTypeIcon()
+ }
+
+ MOBILE_NETWORK_TYPE_ICONS = tempMap
+ }
+
+ /**
+ * A converter function between the old mobile network type icon implementation and the new
+ * one. Given an instance of the old class [mobileIconGroup], outputs an instance of the
+ * new class [MobileNetworkTypeIcon].
+ */
+ @JvmStatic
+ fun getNetworkTypeIcon(
+ mobileIconGroup: SignalIcon.MobileIconGroup
+ ): MobileNetworkTypeIcon {
+ return MOBILE_NETWORK_TYPE_ICONS[mobileIconGroup.name]
+ ?: mobileIconGroup.toNetworkTypeIcon()
+ }
+
+ private fun SignalIcon.MobileIconGroup.toNetworkTypeIcon(): MobileNetworkTypeIcon {
+ return MobileNetworkTypeIcon(
+ name = this.name,
+ iconResId = this.dataType,
+ contentDescriptionResId = this.dataContentDescription
+ )
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
index 280e407..6aaab3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
+++ b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
@@ -15,6 +15,9 @@
*/
package com.android.settingslib;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.StringRes;
+
/**
* Icons for SysUI and Settings.
*/
@@ -66,34 +69,31 @@
}
/**
- * Holds icons for a given MobileState.
+ * Holds RAT icons for a given MobileState.
*/
public static class MobileIconGroup extends IconGroup {
- public final int dataContentDescription; // mContentDescriptionDataType
- public final int dataType;
+ @StringRes public final int dataContentDescription;
+ @DrawableRes public final int dataType;
public MobileIconGroup(
String name,
- int[][] sbIcons,
- int[][] qsIcons,
- int[] contentDesc,
- int sbNullState,
- int qsNullState,
- int sbDiscState,
- int qsDiscState,
- int discContentDesc,
int dataContentDesc,
int dataType
) {
super(name,
- sbIcons,
- qsIcons,
- contentDesc,
- sbNullState,
- qsNullState,
- sbDiscState,
- qsDiscState,
- discContentDesc);
+ // The rest of the values are the same for every type of MobileIconGroup, so
+ // just provide them here.
+ // TODO(b/238425913): Eventually replace with {@link MobileNetworkTypeIcon} so
+ // that we don't have to fill in these superfluous fields.
+ /* sbIcons= */ null,
+ /* qsIcons= */ null,
+ /* contentDesc= */ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ /* sbNullState= */ 0,
+ /* qsNullState= */ 0,
+ /* sbDiscState= */ 0,
+ /* qsDiscState= */ 0,
+ /* discContentDesc= */
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE);
this.dataContentDescription = dataContentDesc;
this.dataType = dataType;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 5662ce6..6bc1160 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -356,7 +356,7 @@
* @return {@code true}, if the device should pair automatically; Otherwise, return
* {@code false}.
*/
- public synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
+ private synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
boolean isOngoingSetMemberPair = mOngoingSetMemberPair != null;
int bondState = device.getBondState();
if (isOngoingSetMemberPair || bondState != BluetoothDevice.BOND_NONE
@@ -365,13 +365,47 @@
+ " , device.getBondState: " + bondState);
return false;
}
-
- Log.d(TAG, "Bond " + device.getName() + " by CSIP");
- mOngoingSetMemberPair = device;
return true;
}
/**
+ * Called when we found a set member of a group. The function will check the {@code groupId} if
+ * it exists and the bond state of the device is BOND_NONE, and if there isn't any ongoing pair
+ * , and then pair the device automatically.
+ *
+ * @param device The found device
+ * @param groupId The group id of the found device
+ */
+ public synchronized void pairDeviceByCsip(BluetoothDevice device, int groupId) {
+ if (!shouldPairByCsip(device, groupId)) {
+ return;
+ }
+ Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " by CSIP");
+ mOngoingSetMemberPair = device;
+ syncConfigFromMainDevice(device, groupId);
+ device.createBond(BluetoothDevice.TRANSPORT_LE);
+ }
+
+ private void syncConfigFromMainDevice(BluetoothDevice device, int groupId) {
+ if (!isOngoingPairByCsip(device)) {
+ return;
+ }
+ CachedBluetoothDevice memberDevice = findDevice(device);
+ CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(memberDevice);
+ if (mainDevice == null) {
+ mainDevice = mCsipDeviceManager.getCachedDevice(groupId);
+ }
+
+ if (mainDevice == null || mainDevice.equals(memberDevice)) {
+ Log.d(TAG, "no mainDevice");
+ return;
+ }
+
+ // The memberDevice set PhonebookAccessPermission
+ device.setPhonebookAccessPermission(mainDevice.getDevice().getPhonebookAccessPermission());
+ }
+
+ /**
* Called when the bond state change. If the bond state change is related with the
* ongoing set member pair, the cachedBluetoothDevice will be created but the UI
* would not be updated. For the other case, return {@code false} to go through the normal
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index d5de3f0..20a6cd8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -101,7 +101,14 @@
return groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
}
- private CachedBluetoothDevice getCachedDevice(int groupId) {
+ /**
+ * To find the device with {@code groupId}.
+ *
+ * @param groupId The group id
+ * @return if we could find a device with this {@code groupId} return this device. Otherwise,
+ * return null.
+ */
+ public CachedBluetoothDevice getCachedDevice(int groupId) {
log("getCachedDevice: groupId: " + groupId);
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index 23e0923..094567c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -16,7 +16,6 @@
package com.android.settingslib.mobile;
-import com.android.settingslib.AccessibilityContentDescriptions;
import com.android.settingslib.R;
import com.android.settingslib.SignalIcon.MobileIconGroup;
@@ -49,297 +48,129 @@
public static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
"CARRIER_NETWORK_CHANGE",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.carrier_network_change_mode,
- 0
+ /* dataType= */ 0
);
public static final MobileIconGroup THREE_G = new MobileIconGroup(
"3G",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_3g,
TelephonyIcons.ICON_3G
);
public static final MobileIconGroup WFC = new MobileIconGroup(
"WFC",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
- 0,
- 0);
+ /* dataContentDescription= */ 0,
+ /* dataType= */ 0);
public static final MobileIconGroup UNKNOWN = new MobileIconGroup(
"Unknown",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
- 0,
- 0);
+ /* dataContentDescription= */ 0,
+ /* dataType= */ 0);
public static final MobileIconGroup E = new MobileIconGroup(
"E",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_edge,
TelephonyIcons.ICON_E
);
public static final MobileIconGroup ONE_X = new MobileIconGroup(
"1X",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_cdma,
TelephonyIcons.ICON_1X
);
public static final MobileIconGroup G = new MobileIconGroup(
"G",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_gprs,
TelephonyIcons.ICON_G
);
public static final MobileIconGroup H = new MobileIconGroup(
"H",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_3_5g,
TelephonyIcons.ICON_H
);
public static final MobileIconGroup H_PLUS = new MobileIconGroup(
"H+",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_3_5g_plus,
TelephonyIcons.ICON_H_PLUS
);
public static final MobileIconGroup FOUR_G = new MobileIconGroup(
"4G",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_4g,
TelephonyIcons.ICON_4G
);
public static final MobileIconGroup FOUR_G_PLUS = new MobileIconGroup(
"4G+",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_4g_plus,
TelephonyIcons.ICON_4G_PLUS
);
public static final MobileIconGroup LTE = new MobileIconGroup(
"LTE",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_lte,
TelephonyIcons.ICON_LTE
);
public static final MobileIconGroup LTE_PLUS = new MobileIconGroup(
"LTE+",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_lte_plus,
TelephonyIcons.ICON_LTE_PLUS
);
public static final MobileIconGroup FOUR_G_LTE = new MobileIconGroup(
"4G LTE",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_4g_lte,
TelephonyIcons.ICON_4G_LTE
);
public static final MobileIconGroup FOUR_G_LTE_PLUS = new MobileIconGroup(
"4G LTE+",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_4g_lte_plus,
TelephonyIcons.ICON_4G_LTE_PLUS
);
public static final MobileIconGroup LTE_CA_5G_E = new MobileIconGroup(
"5Ge",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_5ge_html,
TelephonyIcons.ICON_5G_E
);
public static final MobileIconGroup NR_5G = new MobileIconGroup(
"5G",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_5g,
TelephonyIcons.ICON_5G
);
public static final MobileIconGroup NR_5G_PLUS = new MobileIconGroup(
"5G_PLUS",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_5g_plus,
TelephonyIcons.ICON_5G_PLUS
);
public static final MobileIconGroup DATA_DISABLED = new MobileIconGroup(
"DataDisabled",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.cell_data_off_content_description,
0
);
public static final MobileIconGroup NOT_DEFAULT_DATA = new MobileIconGroup(
"NotDefaultData",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.not_default_data_content_description,
- 0
+ /* dataType= */ 0
);
public static final MobileIconGroup CARRIER_MERGED_WIFI = new MobileIconGroup(
"CWF",
- /* sbIcons= */ null,
- /* qsIcons= */ null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- /* sbNullState= */ 0,
- /* qsNullState= */ 0,
- /* sbDiscState= */ 0,
- /* qsDiscState= */ 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_carrier_wifi,
TelephonyIcons.ICON_CWF
);
- // When adding a new MobileIconGround, check if the dataContentDescription has to be filtered
+ // When adding a new MobileIconGroup, check if the dataContentDescription has to be filtered
// in QSCarrier#hasValidTypeContentDescription
/** Mapping icon name(lower case) to the icon object. */
@@ -368,14 +199,6 @@
ICON_NAME_TO_ICON.put("notdefaultdata", NOT_DEFAULT_DATA);
}
- public static final int[] WIFI_CALL_STRENGTH_ICONS = {
- R.drawable.ic_wifi_call_strength_0,
- R.drawable.ic_wifi_call_strength_1,
- R.drawable.ic_wifi_call_strength_2,
- R.drawable.ic_wifi_call_strength_3,
- R.drawable.ic_wifi_call_strength_4
- };
-
public static final int[] MOBILE_CALL_STRENGTH_ICONS = {
R.drawable.ic_mobile_call_strength_0,
R.drawable.ic_mobile_call_strength_1,
@@ -384,4 +207,3 @@
R.drawable.ic_mobile_call_strength_4
};
}
-
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
new file mode 100644
index 0000000..39977df
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.settingslib;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settingslib.mobile.TelephonyIcons;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class MobileNetworkTypeIconsTest {
+
+ @Test
+ public void getNetworkTypeIcon_hPlus_returnsHPlus() {
+ MobileNetworkTypeIcon icon =
+ MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.H_PLUS);
+
+ assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name);
+ assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_H_PLUS);
+ }
+
+ @Test
+ public void getNetworkTypeIcon_fourG_returnsFourG() {
+ MobileNetworkTypeIcon icon =
+ MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.FOUR_G);
+
+ assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name);
+ assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_4G);
+ }
+
+ @Test
+ public void getNetworkTypeIcon_unknown_returnsUnknown() {
+ SignalIcon.MobileIconGroup unknownGroup =
+ new SignalIcon.MobileIconGroup("testUnknownNameHere", 45, 6);
+
+ MobileNetworkTypeIcon icon = MobileNetworkTypeIcons.getNetworkTypeIcon(unknownGroup);
+
+ assertThat(icon.getName()).isEqualTo("testUnknownNameHere");
+ assertThat(icon.getIconResId()).isEqualTo(45);
+ assertThat(icon.getContentDescriptionResId()).isEqualTo(6);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 62552f91..61802a8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -582,4 +582,24 @@
assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();
assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();
}
+
+ @Test
+ public void pairDeviceByCsip_device2AndCapGroup1_device2StartsPairing() {
+ doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
+ when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mDevice1.getPhonebookAccessPermission()).thenReturn(BluetoothDevice.ACCESS_ALLOWED);
+ CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+ assertThat(cachedDevice1).isNotNull();
+ when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+ CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+ assertThat(cachedDevice2).isNotNull();
+
+ int groupId = CAP_GROUP1.keySet().stream().findFirst().orElse(
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+ assertThat(groupId).isNotEqualTo(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+ mCachedDeviceManager.pairDeviceByCsip(mDevice2, groupId);
+
+ verify(mDevice2).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ verify(mDevice2).createBond(BluetoothDevice.TRANSPORT_LE);
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index fd7554f..528af2e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -376,9 +376,11 @@
Setting newSetting = new Setting(name, oldSetting.getValue(), null,
oldSetting.getPackageName(), oldSetting.getTag(), false,
oldSetting.getId());
- mSettings.put(name, newSetting);
- updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
+ int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue());
+ checkNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
+ mSettings.put(name, newSetting);
+ updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
scheduleWriteIfNeededLocked();
}
}
@@ -410,6 +412,12 @@
Setting oldState = mSettings.get(name);
String oldValue = (oldState != null) ? oldState.value : null;
String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
+ String newDefaultValue = makeDefault ? value : oldDefaultValue;
+
+ int newSize = getNewMemoryUsagePerPackageLocked(packageName, oldValue, value,
+ oldDefaultValue, newDefaultValue);
+ checkNewMemoryUsagePerPackageLocked(packageName, newSize);
+
Setting newState;
if (oldState != null) {
@@ -430,8 +438,7 @@
addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState);
- updateMemoryUsagePerPackageLocked(packageName, oldValue, value,
- oldDefaultValue, newState.getDefaultValue());
+ updateMemoryUsagePerPackageLocked(packageName, newSize);
scheduleWriteIfNeededLocked();
@@ -552,13 +559,14 @@
}
Setting oldState = mSettings.remove(name);
+ int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
+ null, oldState.defaultValue, null);
FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "",
/* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey),
FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED);
- updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
- null, oldState.defaultValue, null);
+ updateMemoryUsagePerPackageLocked(oldState.packageName, newSize);
addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState);
@@ -579,16 +587,18 @@
Setting oldSetting = new Setting(setting);
String oldValue = setting.getValue();
String oldDefaultValue = setting.getDefaultValue();
+ String newValue = oldDefaultValue;
+ String newDefaultValue = oldDefaultValue;
+
+ int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, oldValue,
+ newValue, oldDefaultValue, newDefaultValue);
+ checkNewMemoryUsagePerPackageLocked(setting.packageName, newSize);
if (!setting.reset()) {
return false;
}
- String newValue = setting.getValue();
- String newDefaultValue = setting.getDefaultValue();
-
- updateMemoryUsagePerPackageLocked(setting.packageName, oldValue,
- newValue, oldDefaultValue, newDefaultValue);
+ updateMemoryUsagePerPackageLocked(setting.packageName, newSize);
addHistoricalOperationLocked(HISTORICAL_OPERATION_RESET, oldSetting);
@@ -696,38 +706,49 @@
}
@GuardedBy("mLock")
- private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue,
+ private boolean isExemptFromMemoryUsageCap(String packageName) {
+ return mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED
+ || SYSTEM_PACKAGE_NAME.equals(packageName);
+ }
+
+ @GuardedBy("mLock")
+ private void checkNewMemoryUsagePerPackageLocked(String packageName, int newSize)
+ throws IllegalStateException {
+ if (isExemptFromMemoryUsageCap(packageName)) {
+ return;
+ }
+ if (newSize > mMaxBytesPerAppPackage) {
+ throw new IllegalStateException("You are adding too many system settings. "
+ + "You should stop using system settings for app specific data"
+ + " package: " + packageName);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private int getNewMemoryUsagePerPackageLocked(String packageName, String oldValue,
String newValue, String oldDefaultValue, String newDefaultValue) {
- if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) {
- return;
+ if (isExemptFromMemoryUsageCap(packageName)) {
+ return 0;
}
-
- if (SYSTEM_PACKAGE_NAME.equals(packageName)) {
- return;
- }
-
+ final Integer currentSize = mPackageToMemoryUsage.get(packageName);
final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
final int newValueSize = (newValue != null) ? newValue.length() : 0;
final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0;
final int deltaSize = newValueSize + newDefaultValueSize
- oldValueSize - oldDefaultValueSize;
+ return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0);
+ }
- Integer currentSize = mPackageToMemoryUsage.get(packageName);
- final int newSize = Math.max((currentSize != null)
- ? currentSize + deltaSize : deltaSize, 0);
-
- if (newSize > mMaxBytesPerAppPackage) {
- throw new IllegalStateException("You are adding too many system settings. "
- + "You should stop using system settings for app specific data"
- + " package: " + packageName);
+ @GuardedBy("mLock")
+ private void updateMemoryUsagePerPackageLocked(String packageName, int newSize) {
+ if (isExemptFromMemoryUsageCap(packageName)) {
+ return;
}
-
if (DEBUG) {
Slog.i(LOG_TAG, "Settings for package: " + packageName
+ " size: " + newSize + " bytes.");
}
-
mPackageToMemoryUsage.put(packageName, newSize);
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 69eb713..66b809a 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -20,6 +20,8 @@
import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.google.common.base.Strings;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -276,4 +278,40 @@
settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
return settingsState;
}
+
+ public void testInsertSetting_memoryUsage() {
+ SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ // No exception should be thrown when there is no cap
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, "p1");
+ settingsState.deleteSettingLocked(SETTING_NAME);
+
+ settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+ // System package doesn't have memory usage limit
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, SYSTEM_PACKAGE);
+ settingsState.deleteSettingLocked(SETTING_NAME);
+
+ // Should not throw if usage is under the cap
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19999),
+ null, false, "p1");
+ settingsState.deleteSettingLocked(SETTING_NAME);
+ try {
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, "p1");
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("p1"));
+ }
+ try {
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, "p1");
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("p1"));
+ }
+ assertTrue(settingsState.getSettingLocked(SETTING_NAME).isNull());
+ }
}
diff --git a/packages/SoundPicker/res/values-am/strings.xml b/packages/SoundPicker/res/values-am/strings.xml
index 07aee8a..85206c0 100644
--- a/packages/SoundPicker/res/values-am/strings.xml
+++ b/packages/SoundPicker/res/values-am/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="ringtone_default" msgid="798836092118824500">"ነባሪ የስልክ ላይ ጥሪ"</string>
- <string name="notification_sound_default" msgid="8133121186242636840">"ነባሪ የማሳወቂያ ድምጽ"</string>
+ <string name="notification_sound_default" msgid="8133121186242636840">"ነባሪ የማሳወቂያ ድምፅ"</string>
<string name="alarm_sound_default" msgid="4787646764557462649">"ነባሪ የማንቂያ ድምፅ"</string>
<string name="add_ringtone_text" msgid="6642389991738337529">"የጥሪ ቅላጼ አክል"</string>
<string name="add_alarm_text" msgid="3545497316166999225">"የማንቂያ ደውል አክል"</string>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index df6f08d..2f5b5f4 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -294,5 +294,6 @@
dxflags: ["--multi-dex"],
required: [
"privapp_whitelist_com.android.systemui",
+ "wmshell.protolog.json.gz",
],
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2737ecf..b5145f9 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 a65f9be..6d61fd8 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -5,23 +5,32 @@
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 @@
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 @@
vadimt@google.com
victortulias@google.com
winsonc@google.com
+wleshner@google.com
+xilei@google.com
xuqiu@google.com
+yeinj@google.com
yuandizhou@google.com
yurilin@google.com
zakcohen@google.com
-
-#Android Auto
-hseog@google.com
+zoepage@google.com
#Android TV
rgl@google.com
diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/res/values/ids.xml
index f7150ab..2d82307a 100644
--- a/packages/SystemUI/animation/res/values/ids.xml
+++ b/packages/SystemUI/animation/res/values/ids.xml
@@ -16,7 +16,6 @@
-->
<resources>
<!-- DialogLaunchAnimator -->
- <item type="id" name="tag_launch_animation_running"/>
<item type="id" name="tag_dialog_background"/>
<!-- ViewBoundsAnimator -->
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 9656b8a..ca36fa4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -29,12 +29,12 @@
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewRootImpl
import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
import android.widget.FrameLayout
import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.jank.InteractionJankMonitor.Configuration
import com.android.internal.jank.InteractionJankMonitor.CujType
import kotlin.math.roundToInt
@@ -46,6 +46,7 @@
*
* This animator also allows to easily animate a dialog into an activity.
*
+ * @see show
* @see showFromView
* @see showFromDialog
* @see createActivityLaunchController
@@ -67,8 +68,81 @@
ActivityLaunchAnimator.INTERPOLATORS.copy(
positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
)
+ }
- private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.tag_launch_animation_running
+ /**
+ * A controller that takes care of applying the dialog launch and exit animations to the source
+ * that triggered the animation.
+ */
+ interface Controller {
+ /** The [ViewRootImpl] of this controller. */
+ val viewRoot: ViewRootImpl
+
+ /**
+ * The identity object of the source animated by this controller. This animator will ensure
+ * that 2 animations with the same source identity are not going to run at the same time, to
+ * avoid flickers when a dialog is shown from the same source more or less at the same time
+ * (for instance if the user clicks an expandable button twice).
+ */
+ val sourceIdentity: Any
+
+ /**
+ * Move the drawing of the source in the overlay of [viewGroup].
+ *
+ * Once this method is called, and until [stopDrawingInOverlay] is called, the source
+ * controlled by this Controller should be drawn in the overlay of [viewGroup] so that it is
+ * drawn above all other elements in the same [viewRoot].
+ */
+ fun startDrawingInOverlayOf(viewGroup: ViewGroup)
+
+ /**
+ * Move the drawing of the source back in its original location.
+ *
+ * @see startDrawingInOverlayOf
+ */
+ fun stopDrawingInOverlay()
+
+ /**
+ * Create the [LaunchAnimator.Controller] that will be called to animate the source
+ * controlled by this [Controller] during the dialog launch animation.
+ *
+ * At the end of this animation, the source should *not* be visible anymore (until the
+ * dialog is closed and is animated back into the source).
+ */
+ fun createLaunchController(): LaunchAnimator.Controller
+
+ /**
+ * Create the [LaunchAnimator.Controller] that will be called to animate the source
+ * controlled by this [Controller] during the dialog exit animation.
+ *
+ * At the end of this animation, the source should be visible again.
+ */
+ fun createExitController(): LaunchAnimator.Controller
+
+ /**
+ * Whether we should animate the dialog back into the source when it is dismissed. If this
+ * methods returns `false`, then the dialog will simply fade out and
+ * [onExitAnimationCancelled] will be called.
+ *
+ * Note that even when this returns `true`, the exit animation might still be cancelled (in
+ * which case [onExitAnimationCancelled] will also be called).
+ */
+ fun shouldAnimateExit(): Boolean
+
+ /**
+ * Called if we decided to *not* animate the dialog into the source for some reason. This
+ * means that [createExitController] will *not* be called and this implementation should
+ * make sure that the source is back in its original state, before it was animated into the
+ * dialog. In particular, the source should be visible again.
+ */
+ fun onExitAnimationCancelled()
+
+ /**
+ * Return the [InteractionJankMonitor.Configuration.Builder] to be used for animations
+ * controlled by this controller.
+ */
+ // TODO(b/252723237): Make this non-nullable
+ fun jankConfigurationBuilder(cuj: Int): InteractionJankMonitor.Configuration.Builder?
}
/**
@@ -96,7 +170,28 @@
dialog: Dialog,
view: View,
cuj: DialogCuj? = null,
- animateBackgroundBoundsChange: Boolean = false,
+ animateBackgroundBoundsChange: Boolean = false
+ ) {
+ show(dialog, createController(view), cuj, animateBackgroundBoundsChange)
+ }
+
+ /**
+ * Show [dialog] by expanding it from a source controlled by [controller].
+ *
+ * If [animateBackgroundBoundsChange] is true, then the background of the dialog will be
+ * animated when the dialog bounds change.
+ *
+ * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
+ * animated.
+ *
+ * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
+ * made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
+ */
+ fun show(
+ dialog: Dialog,
+ controller: Controller,
+ cuj: DialogCuj? = null,
+ animateBackgroundBoundsChange: Boolean = false
) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw IllegalStateException(
@@ -109,9 +204,10 @@
// intent is to launch a dialog from another dialog.
val animatedParent =
openedDialogs.firstOrNull {
- it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
+ it.dialog.window.decorView.viewRootImpl == controller.viewRoot
}
- val animateFrom = animatedParent?.dialogContentWithBackground ?: view
+ val animateFrom =
+ animatedParent?.dialogContentWithBackground?.let { createController(it) } ?: controller
if (animatedParent == null && animateFrom !is LaunchableView) {
// Make sure the View we launch from implements LaunchableView to avoid visibility
@@ -126,15 +222,17 @@
)
}
- // Make sure we don't run the launch animation from the same view twice at the same time.
- if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) {
- Log.e(TAG, "Not running dialog launch animation as there is already one running")
+ // Make sure we don't run the launch animation from the same source twice at the same time.
+ if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) {
+ Log.e(
+ TAG,
+ "Not running dialog launch animation from source as it is already expanded into a" +
+ " dialog"
+ )
dialog.show()
return
}
- animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
-
val animatedDialog =
AnimatedDialog(
launchAnimator,
@@ -146,16 +244,99 @@
animateBackgroundBoundsChange,
animatedParent,
isForTesting,
- cuj
+ cuj,
)
openedDialogs.add(animatedDialog)
animatedDialog.start()
}
+ /** Create a [Controller] that can animate [source] to & from a dialog. */
+ private fun createController(source: View): Controller {
+ return object : Controller {
+ override val viewRoot: ViewRootImpl
+ get() = source.viewRootImpl
+
+ override val sourceIdentity: Any = source
+
+ override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+ // Create a temporary ghost of the source (which will make it invisible) and add it
+ // to the host dialog.
+ GhostView.addGhost(source, viewGroup)
+
+ // The ghost of the source was just created, so the source is currently invisible.
+ // We need to make sure that it stays invisible as long as the dialog is shown or
+ // animating.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+ }
+
+ override fun stopDrawingInOverlay() {
+ // Note: here we should remove the ghost from the overlay, but in practice this is
+ // already done by the launch controllers created below.
+
+ // Make sure we allow the source to change its visibility again.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+ source.visibility = View.VISIBLE
+ }
+
+ override fun createLaunchController(): LaunchAnimator.Controller {
+ val delegate = GhostedViewLaunchAnimatorController(source)
+ return object : LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
+ // ghost (that ghosts only the source content, and not its background) will
+ // be added right after this by the delegate and will be animated.
+ GhostView.removeGhost(source)
+ delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+ // We hide the source when the dialog is showing. We will make this view
+ // visible again when dismissing the dialog. This does nothing if the source
+ // implements [LaunchableView], as it's already INVISIBLE in that case.
+ source.visibility = View.INVISIBLE
+ }
+ }
+ }
+
+ override fun createExitController(): LaunchAnimator.Controller {
+ return GhostedViewLaunchAnimatorController(source)
+ }
+
+ override fun shouldAnimateExit(): Boolean {
+ // The source should be invisible by now, if it's not then something else changed
+ // its visibility and we probably don't want to run the animation.
+ if (source.visibility != View.INVISIBLE) {
+ return false
+ }
+
+ return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
+ }
+
+ override fun onExitAnimationCancelled() {
+ // Make sure we allow the source to change its visibility again.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+
+ // If the view is invisible it's probably because of us, so we make it visible
+ // again.
+ if (source.visibility == View.INVISIBLE) {
+ source.visibility = View.VISIBLE
+ }
+ }
+
+ override fun jankConfigurationBuilder(
+ cuj: Int
+ ): InteractionJankMonitor.Configuration.Builder? {
+ return InteractionJankMonitor.Configuration.Builder.withView(cuj, source)
+ }
+ }
+ }
+
/**
- * Launch [dialog] from [another dialog][animateFrom] that was shown using [showFromView]. This
- * will allow for dismissing the whole stack.
+ * Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will
+ * allow for dismissing the whole stack.
*
* @see dismissStack
*/
@@ -181,32 +362,55 @@
/**
* Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from the
- * dialog that contains [View]. Note that the dialog must have been show using [showFromView]
- * and be currently showing, otherwise this will return null.
+ * dialog that contains [View]. Note that the dialog must have been shown using this animator,
+ * otherwise this method will return null.
*
* The returned controller will take care of dismissing the dialog at the right time after the
* activity started, when the dialog to app animation is done (or when it is cancelled). If this
* method returns null, then the dialog won't be dismissed.
*
- * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
- * animated.
- *
* @param view any view inside the dialog to animate.
*/
@JvmOverloads
fun createActivityLaunchController(
view: View,
- cujType: Int? = null
+ cujType: Int? = null,
): ActivityLaunchAnimator.Controller? {
val animatedDialog =
openedDialogs.firstOrNull {
it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
}
?: return null
+ return createActivityLaunchController(animatedDialog, cujType)
+ }
+ /**
+ * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from
+ * [dialog]. Note that the dialog must have been shown using this animator, otherwise this
+ * method will return null.
+ *
+ * The returned controller will take care of dismissing the dialog at the right time after the
+ * activity started, when the dialog to app animation is done (or when it is cancelled). If this
+ * method returns null, then the dialog won't be dismissed.
+ *
+ * @param dialog the dialog to animate.
+ */
+ @JvmOverloads
+ fun createActivityLaunchController(
+ dialog: Dialog,
+ cujType: Int? = null,
+ ): ActivityLaunchAnimator.Controller? {
+ val animatedDialog = openedDialogs.firstOrNull { it.dialog == dialog } ?: return null
+ return createActivityLaunchController(animatedDialog, cujType)
+ }
+
+ private fun createActivityLaunchController(
+ animatedDialog: AnimatedDialog,
+ cujType: Int? = null
+ ): ActivityLaunchAnimator.Controller? {
// At this point, we know that the intent of the caller is to dismiss the dialog to show
- // an app, so we disable the exit animation into the touch surface because we will never
- // want to run it anyways.
+ // an app, so we disable the exit animation into the source because we will never want to
+ // run it anyways.
animatedDialog.exitAnimationDisabled = true
val dialog = animatedDialog.dialog
@@ -252,7 +456,7 @@
// If this dialog was shown from a cascade of other dialogs, make sure those ones
// are dismissed too.
- animatedDialog.touchSurface = animatedDialog.prepareForStackDismiss()
+ animatedDialog.prepareForStackDismiss()
// Remove the dim.
dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
@@ -283,12 +487,11 @@
}
/**
- * Ensure that all dialogs currently shown won't animate into their touch surface when
- * dismissed.
+ * Ensure that all dialogs currently shown won't animate into their source when dismissed.
*
* This is a temporary API meant to be called right before we both dismiss a dialog and start an
- * activity, which currently does not look good if we animate the dialog into the touch surface
- * at the same time as the activity starts.
+ * activity, which currently does not look good if we animate the dialog into their source at
+ * the same time as the activity starts.
*
* TODO(b/193634619): Remove this function and animate dialog into opening activity instead.
*/
@@ -297,13 +500,11 @@
}
/**
- * Dismiss [dialog]. If it was launched from another dialog using [showFromView], also dismiss
- * the stack of dialogs, animating back to the original touchSurface.
+ * Dismiss [dialog]. If it was launched from another dialog using this animator, also dismiss
+ * the stack of dialogs and simply fade out [dialog].
*/
fun dismissStack(dialog: Dialog) {
- openedDialogs
- .firstOrNull { it.dialog == dialog }
- ?.let { it.touchSurface = it.prepareForStackDismiss() }
+ openedDialogs.firstOrNull { it.dialog == dialog }?.prepareForStackDismiss()
dialog.dismiss()
}
@@ -337,8 +538,11 @@
private val callback: DialogLaunchAnimator.Callback,
private val interactionJankMonitor: InteractionJankMonitor,
- /** The view that triggered the dialog after being tapped. */
- var touchSurface: View,
+ /**
+ * The controller of the source that triggered the dialog and that will animate into/from the
+ * dialog.
+ */
+ val controller: DialogLaunchAnimator.Controller,
/**
* A callback that will be called with this [AnimatedDialog] after the dialog was dismissed and
@@ -383,17 +587,18 @@
private var originalDialogBackgroundColor = Color.BLACK
/**
- * Whether we are currently launching/showing the dialog by animating it from [touchSurface].
+ * Whether we are currently launching/showing the dialog by animating it from its source
+ * controlled by [controller].
*/
private var isLaunching = true
- /** Whether we are currently dismissing/hiding the dialog by animating into [touchSurface]. */
+ /** Whether we are currently dismissing/hiding the dialog by animating into its source. */
private var isDismissing = false
private var dismissRequested = false
var exitAnimationDisabled = false
- private var isTouchSurfaceGhostDrawn = false
+ private var isSourceDrawnInDialog = false
private var isOriginalDialogViewLaidOut = false
/** A layout listener to animate the dialog height change. */
@@ -410,13 +615,19 @@
*/
private var decorViewLayoutListener: View.OnLayoutChangeListener? = null
+ private var hasInstrumentedJank = false
+
fun start() {
if (cuj != null) {
- val config = Configuration.Builder.withView(cuj.cujType, touchSurface)
- if (cuj.tag != null) {
- config.setTag(cuj.tag)
+ val config = controller.jankConfigurationBuilder(cuj.cujType)
+ if (config != null) {
+ if (cuj.tag != null) {
+ config.setTag(cuj.tag)
+ }
+
+ interactionJankMonitor.begin(config)
+ hasInstrumentedJank = true
}
- interactionJankMonitor.begin(config)
}
// Create the dialog so that its onCreate() method is called, which usually sets the dialog
@@ -618,47 +829,45 @@
// Show the dialog.
dialog.show()
- addTouchSurfaceGhost()
+ moveSourceDrawingToDialog()
}
- private fun addTouchSurfaceGhost() {
+ private fun moveSourceDrawingToDialog() {
if (decorView.viewRootImpl == null) {
- // Make sure that we have access to the dialog view root to synchronize the creation of
- // the ghost.
- decorView.post(::addTouchSurfaceGhost)
+ // Make sure that we have access to the dialog view root to move the drawing to the
+ // dialog overlay.
+ decorView.post(::moveSourceDrawingToDialog)
return
}
- // Create a ghost of the touch surface (which will make the touch surface invisible) and add
- // it to the host dialog. We trigger a one off synchronization to make sure that this is
- // done in sync between the two different windows.
+ // Move the drawing of the source in the overlay of this dialog, then animate. We trigger a
+ // one-off synchronization to make sure that this is done in sync between the two different
+ // windows.
synchronizeNextDraw(
then = {
- isTouchSurfaceGhostDrawn = true
+ isSourceDrawnInDialog = true
maybeStartLaunchAnimation()
}
)
- GhostView.addGhost(touchSurface, decorView)
-
- // The ghost of the touch surface was just created, so the touch surface is currently
- // invisible. We need to make sure that it stays invisible as long as the dialog is shown or
- // animating.
- (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+ controller.startDrawingInOverlayOf(decorView)
}
/**
- * Synchronize the next draw of the touch surface and dialog view roots so that they are
- * performed at the same time, in the same transaction. This is necessary to make sure that the
- * ghost of the touch surface is drawn at the same time as the touch surface is made invisible
- * (or inversely, removed from the UI when the touch surface is made visible).
+ * Synchronize the next draw of the source and dialog view roots so that they are performed at
+ * the same time, in the same transaction. This is necessary to make sure that the source is
+ * drawn in the overlay at the same time as it is removed from its original position (or
+ * inversely, removed from the overlay when the source is moved back to its original position).
*/
private fun synchronizeNextDraw(then: () -> Unit) {
if (forceDisableSynchronization) {
+ // Don't synchronize when inside an automated test.
then()
return
}
- ViewRootSync.synchronizeNextDraw(touchSurface, decorView, then)
+ ViewRootSync.synchronizeNextDraw(controller.viewRoot.view, decorView, then)
+ decorView.invalidate()
+ controller.viewRoot.view.invalidate()
}
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
@@ -681,7 +890,7 @@
}
private fun maybeStartLaunchAnimation() {
- if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) {
+ if (!isSourceDrawnInDialog || !isOriginalDialogViewLaidOut) {
return
}
@@ -690,19 +899,7 @@
startAnimation(
isLaunching = true,
- onLaunchAnimationStart = {
- // Remove the temporary ghost. Another ghost (that ghosts only the touch surface
- // content, and not its background) will be added right after this and will be
- // animated.
- GhostView.removeGhost(touchSurface)
- },
onLaunchAnimationEnd = {
- touchSurface.setTag(R.id.tag_launch_animation_running, null)
-
- // We hide the touch surface when the dialog is showing. We will make this view
- // visible again when dismissing the dialog.
- touchSurface.visibility = View.INVISIBLE
-
isLaunching = false
// dismiss was called during the animation, dismiss again now to actually dismiss.
@@ -718,7 +915,10 @@
backgroundLayoutListener
)
}
- cuj?.run { interactionJankMonitor.end(cujType) }
+
+ if (hasInstrumentedJank) {
+ interactionJankMonitor.end(cuj!!.cujType)
+ }
}
)
}
@@ -753,8 +953,8 @@
}
/**
- * Hide the dialog into the touch surface and call [onAnimationFinished] when the animation is
- * done (passing animationRan=true) or if it's skipped (passing animationRan=false) to actually
+ * Hide the dialog into the source and call [onAnimationFinished] when the animation is done
+ * (passing animationRan=true) or if it's skipped (passing animationRan=false) to actually
* dismiss the dialog.
*/
private fun hideDialogIntoView(onAnimationFinished: (Boolean) -> Unit) {
@@ -763,17 +963,9 @@
decorView.removeOnLayoutChangeListener(decorViewLayoutListener)
}
- if (!shouldAnimateDialogIntoView()) {
- Log.i(TAG, "Skipping animation of dialog into the touch surface")
-
- // Make sure we allow the touch surface to change its visibility again.
- (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
- // If the view is invisible it's probably because of us, so we make it visible again.
- if (touchSurface.visibility == View.INVISIBLE) {
- touchSurface.visibility = View.VISIBLE
- }
-
+ if (!shouldAnimateDialogIntoSource()) {
+ Log.i(TAG, "Skipping animation of dialog into the source")
+ controller.onExitAnimationCancelled()
onAnimationFinished(false /* instantDismiss */)
onDialogDismissed(this@AnimatedDialog)
return
@@ -786,10 +978,6 @@
dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
},
onLaunchAnimationEnd = {
- // Make sure we allow the touch surface to change its visibility again.
- (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
- touchSurface.visibility = View.VISIBLE
val dialogContentWithBackground = this.dialogContentWithBackground!!
dialogContentWithBackground.visibility = View.INVISIBLE
@@ -799,14 +987,11 @@
)
}
- // Make sure that the removal of the ghost and making the touch surface visible is
- // done at the same time.
- synchronizeNextDraw(
- then = {
- onAnimationFinished(true /* instantDismiss */)
- onDialogDismissed(this@AnimatedDialog)
- }
- )
+ controller.stopDrawingInOverlay()
+ synchronizeNextDraw {
+ onAnimationFinished(true /* instantDismiss */)
+ onDialogDismissed(this@AnimatedDialog)
+ }
}
)
}
@@ -816,27 +1001,34 @@
onLaunchAnimationStart: () -> Unit = {},
onLaunchAnimationEnd: () -> Unit = {}
) {
- // Create 2 ghost controllers to animate both the dialog and the touch surface in the
- // dialog.
- val startView = if (isLaunching) touchSurface else dialogContentWithBackground!!
- val endView = if (isLaunching) dialogContentWithBackground!! else touchSurface
- val startViewController = GhostedViewLaunchAnimatorController(startView)
- val endViewController = GhostedViewLaunchAnimatorController(endView)
- startViewController.launchContainer = decorView
- endViewController.launchContainer = decorView
+ // Create 2 controllers to animate both the dialog and the source.
+ val startController =
+ if (isLaunching) {
+ controller.createLaunchController()
+ } else {
+ GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
+ }
+ val endController =
+ if (isLaunching) {
+ GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
+ } else {
+ controller.createExitController()
+ }
+ startController.launchContainer = decorView
+ endController.launchContainer = decorView
- val endState = endViewController.createAnimatorState()
+ val endState = endController.createAnimatorState()
val controller =
object : LaunchAnimator.Controller {
override var launchContainer: ViewGroup
- get() = startViewController.launchContainer
+ get() = startController.launchContainer
set(value) {
- startViewController.launchContainer = value
- endViewController.launchContainer = value
+ startController.launchContainer = value
+ endController.launchContainer = value
}
override fun createAnimatorState(): LaunchAnimator.State {
- return startViewController.createAnimatorState()
+ return startController.createAnimatorState()
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -845,15 +1037,29 @@
// onLaunchAnimationStart on the controller (which will create its own ghost).
onLaunchAnimationStart()
- startViewController.onLaunchAnimationStart(isExpandingFullyAbove)
- endViewController.onLaunchAnimationStart(isExpandingFullyAbove)
+ startController.onLaunchAnimationStart(isExpandingFullyAbove)
+ endController.onLaunchAnimationStart(isExpandingFullyAbove)
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- startViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
- endViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
+ // onLaunchAnimationEnd is called by an Animator at the end of the animation,
+ // on a Choreographer animation tick. The following calls will move the animated
+ // content from the dialog overlay back to its original position, and this
+ // change must be reflected in the next frame given that we then sync the next
+ // frame of both the content and dialog ViewRoots. However, in case that content
+ // is rendered by Compose, whose compositions are also scheduled on a
+ // Choreographer frame, any state change made *right now* won't be reflected in
+ // the next frame given that a Choreographer frame can't schedule another and
+ // have it happen in the same frame. So we post the forwarded calls to
+ // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
+ // that the move of the content back to its original window will be reflected in
+ // the next frame right after [onLaunchAnimationEnd] is called.
+ dialog.context.mainExecutor.execute {
+ startController.onLaunchAnimationEnd(isExpandingFullyAbove)
+ endController.onLaunchAnimationEnd(isExpandingFullyAbove)
- onLaunchAnimationEnd()
+ onLaunchAnimationEnd()
+ }
}
override fun onLaunchAnimationProgress(
@@ -861,11 +1067,11 @@
progress: Float,
linearProgress: Float
) {
- startViewController.onLaunchAnimationProgress(state, progress, linearProgress)
+ startController.onLaunchAnimationProgress(state, progress, linearProgress)
// The end view is visible only iff the starting view is not visible.
state.visible = !state.visible
- endViewController.onLaunchAnimationProgress(state, progress, linearProgress)
+ endController.onLaunchAnimationProgress(state, progress, linearProgress)
// If the dialog content is complex, its dimension might change during the
// launch animation. The animation end position might also change during the
@@ -873,14 +1079,16 @@
// Therefore we update the end state to the new position/size. Usually the
// dialog dimension or position will change in the early frames, so changing the
// end state shouldn't really be noticeable.
- endViewController.fillGhostedViewState(endState)
+ if (endController is GhostedViewLaunchAnimatorController) {
+ endController.fillGhostedViewState(endState)
+ }
}
}
launchAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
}
- private fun shouldAnimateDialogIntoView(): Boolean {
+ private fun shouldAnimateDialogIntoSource(): Boolean {
// Don't animate if the dialog was previously hidden using hide() or if we disabled the exit
// animation.
if (exitAnimationDisabled || !dialog.isShowing) {
@@ -888,24 +1096,12 @@
}
// If we are dreaming, the dialog was probably closed because of that so we don't animate
- // into the touchSurface.
+ // into the source.
if (callback.isDreaming()) {
return false
}
- // The touch surface should be invisible by now, if it's not then something else changed its
- // visibility and we probably don't want to run the animation.
- if (touchSurface.visibility != View.INVISIBLE) {
- return false
- }
-
- // If the touch surface is not attached or one of its ancestors is not visible, then we
- // don't run the animation either.
- if (!touchSurface.isAttachedToWindow) {
- return false
- }
-
- return (touchSurface.parent as? View)?.isShown ?: true
+ return controller.shouldAnimateExit()
}
/** A layout listener to animate the change of bounds of the dialog background. */
@@ -988,17 +1184,13 @@
}
}
- fun prepareForStackDismiss(): View {
+ fun prepareForStackDismiss() {
if (parentAnimatedDialog == null) {
- return touchSurface
+ return
}
parentAnimatedDialog.exitAnimationDisabled = true
parentAnimatedDialog.dialog.hide()
- val view = parentAnimatedDialog.prepareForStackDismiss()
+ parentAnimatedDialog.prepareForStackDismiss()
parentAnimatedDialog.dialog.dismiss()
- // Make the touch surface invisible, so we end up animating to it when we actually
- // dismiss the stack
- view.visibility = View.INVISIBLE
- return view
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index eb000ad..0028d13 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -199,6 +199,10 @@
// the content before fading out the background.
ghostView = GhostView.addGhost(ghostedView, launchContainer)
+ // The ghost was just created, so ghostedView is currently invisible. We need to make sure
+ // that it stays invisible as long as we are animating.
+ (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+
val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
matrix.getValues(initialGhostViewMatrixValues)
@@ -293,6 +297,7 @@
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
GhostView.removeGhost(ghostedView)
+ (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
launchContainerOverlay.remove(backgroundView)
// Make sure that the view is considered VISIBLE by accessibility by first making it
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index dc2c6356..58ffef2 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -360,14 +360,21 @@
* [interpolator] and [duration].
*
* The end state of the animation is controlled by [destination]. This value can be any of
- * the four corners, any of the four edges, or the center of the view.
+ * the four corners, any of the four edges, or the center of the view. If any margins are
+ * added on the side(s) of the [destination], the translation of those margins can be
+ * included by specifying [includeMargins].
+ *
+ * @param onAnimationEnd an optional runnable that will be run once the animation finishes
+ * successfully. Will not be run if the animation is cancelled.
*/
@JvmOverloads
fun animateRemoval(
rootView: View,
destination: Hotspot = Hotspot.CENTER,
interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR,
- duration: Long = DEFAULT_DURATION
+ duration: Long = DEFAULT_DURATION,
+ includeMargins: Boolean = false,
+ onAnimationEnd: Runnable? = null,
): Boolean {
if (
!occupiesSpace(
@@ -391,13 +398,28 @@
addListener(child, listener, recursive = false)
}
- // Remove the view so that a layout update is triggered for the siblings and they
- // animate to their next position while the view's removal is also animating.
- parent.removeView(rootView)
- // By adding the view to the overlay, we can animate it while it isn't part of the view
- // hierarchy. It is correctly positioned because we have its previous bounds, and we set
- // them manually during the animation.
- parent.overlay.add(rootView)
+ val viewHasSiblings = parent.childCount > 1
+ if (viewHasSiblings) {
+ // Remove the view so that a layout update is triggered for the siblings and they
+ // animate to their next position while the view's removal is also animating.
+ parent.removeView(rootView)
+ // By adding the view to the overlay, we can animate it while it isn't part of the
+ // view hierarchy. It is correctly positioned because we have its previous bounds,
+ // and we set them manually during the animation.
+ parent.overlay.add(rootView)
+ }
+ // If this view has no siblings, the parent view may shrink to (0,0) size and mess
+ // up the animation if we immediately remove the view. So instead, we just leave the
+ // view in the real hierarchy until the animation finishes.
+
+ val endRunnable = Runnable {
+ if (viewHasSiblings) {
+ parent.overlay.remove(rootView)
+ } else {
+ parent.removeView(rootView)
+ }
+ onAnimationEnd?.run()
+ }
val startValues =
mapOf(
@@ -409,10 +431,12 @@
val endValues =
processEndValuesForRemoval(
destination,
+ rootView,
rootView.left,
rootView.top,
rootView.right,
- rootView.bottom
+ rootView.bottom,
+ includeMargins,
)
val boundsToAnimate = mutableSetOf<Bound>()
@@ -430,7 +454,8 @@
endValues,
interpolator,
duration,
- ephemeral = true
+ ephemeral = true,
+ endRunnable,
)
if (rootView is ViewGroup) {
@@ -463,7 +488,6 @@
.alpha(0f)
.setInterpolator(Interpolators.ALPHA_OUT)
.setDuration(duration / 2)
- .withEndAction { parent.overlay.remove(rootView) }
.start()
}
}
@@ -477,7 +501,6 @@
.setInterpolator(Interpolators.ALPHA_OUT)
.setDuration(duration / 2)
.setStartDelay(duration / 2)
- .withEndAction { parent.overlay.remove(rootView) }
.start()
}
@@ -700,70 +723,111 @@
* | | -> | | -> | | -> x---x -> x
* | | x-------x x-----x
* x---------x
+ * 4) destination=TOP, includeMargins=true (and view has large top margin)
+ * x---------x
+ * x---------x
+ * x---------x x---------x
+ * x---------x | |
+ * x---------x | | x---------x
+ * | | | |
+ * | | -> x---------x -> -> ->
+ * | |
+ * x---------x
* ```
*/
private fun processEndValuesForRemoval(
destination: Hotspot,
+ rootView: View,
left: Int,
top: Int,
right: Int,
- bottom: Int
+ bottom: Int,
+ includeMargins: Boolean = false,
): Map<Bound, Int> {
- val endLeft =
- when (destination) {
- Hotspot.CENTER -> (left + right) / 2
- Hotspot.BOTTOM,
- Hotspot.BOTTOM_LEFT,
- Hotspot.LEFT,
- Hotspot.TOP_LEFT,
- Hotspot.TOP -> left
- Hotspot.TOP_RIGHT,
- Hotspot.RIGHT,
- Hotspot.BOTTOM_RIGHT -> right
- }
- val endTop =
- when (destination) {
- Hotspot.CENTER -> (top + bottom) / 2
- Hotspot.LEFT,
- Hotspot.TOP_LEFT,
- Hotspot.TOP,
- Hotspot.TOP_RIGHT,
- Hotspot.RIGHT -> top
- Hotspot.BOTTOM_RIGHT,
- Hotspot.BOTTOM,
- Hotspot.BOTTOM_LEFT -> bottom
- }
- val endRight =
- when (destination) {
- Hotspot.CENTER -> (left + right) / 2
- Hotspot.TOP,
- Hotspot.TOP_RIGHT,
- Hotspot.RIGHT,
- Hotspot.BOTTOM_RIGHT,
- Hotspot.BOTTOM -> right
- Hotspot.BOTTOM_LEFT,
- Hotspot.LEFT,
- Hotspot.TOP_LEFT -> left
- }
- val endBottom =
- when (destination) {
- Hotspot.CENTER -> (top + bottom) / 2
- Hotspot.RIGHT,
- Hotspot.BOTTOM_RIGHT,
- Hotspot.BOTTOM,
- Hotspot.BOTTOM_LEFT,
- Hotspot.LEFT -> bottom
- Hotspot.TOP_LEFT,
- Hotspot.TOP,
- Hotspot.TOP_RIGHT -> top
- }
+ val marginAdjustment =
+ if (includeMargins &&
+ (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
+ val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams
+ DimenHolder(
+ left = marginLp.leftMargin,
+ top = marginLp.topMargin,
+ right = marginLp.rightMargin,
+ bottom = marginLp.bottomMargin
+ )
+ } else {
+ DimenHolder(0, 0, 0, 0)
+ }
- return mapOf(
- Bound.LEFT to endLeft,
- Bound.TOP to endTop,
- Bound.RIGHT to endRight,
- Bound.BOTTOM to endBottom
- )
+ // These are the end values to use *if* this bound is part of the destination.
+ val endLeft = left - marginAdjustment.left
+ val endTop = top - marginAdjustment.top
+ val endRight = right + marginAdjustment.right
+ val endBottom = bottom + marginAdjustment.bottom
+
+ // For the below calculations: We need to ensure that the destination bound and the
+ // bound *opposite* to the destination bound end at the same value, to ensure that the
+ // view has size 0 for that dimension.
+ // For example,
+ // - If destination=TOP, then endTop == endBottom. Left and right stay the same.
+ // - If destination=RIGHT, then endRight == endLeft. Top and bottom stay the same.
+ // - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight.
+
+ return when (destination) {
+ Hotspot.TOP -> mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.LEFT to left,
+ Bound.RIGHT to right,
+ )
+ Hotspot.TOP_RIGHT -> mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ )
+ Hotspot.RIGHT -> mapOf(
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ Bound.TOP to top,
+ Bound.BOTTOM to bottom,
+ )
+ Hotspot.BOTTOM_RIGHT -> mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ )
+ Hotspot.BOTTOM -> mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.LEFT to left,
+ Bound.RIGHT to right,
+ )
+ Hotspot.BOTTOM_LEFT -> mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ )
+ Hotspot.LEFT -> mapOf(
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ Bound.TOP to top,
+ Bound.BOTTOM to bottom,
+ )
+ Hotspot.TOP_LEFT -> mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ )
+ Hotspot.CENTER -> mapOf(
+ Bound.LEFT to (endLeft + endRight) / 2,
+ Bound.RIGHT to (endLeft + endRight) / 2,
+ Bound.TOP to (endTop + endBottom) / 2,
+ Bound.BOTTOM to (endTop + endBottom) / 2,
+ )
+ }
}
/**
@@ -1043,4 +1107,12 @@
abstract fun setValue(view: View, value: Int)
abstract fun getValue(view: View): Int
}
+
+ /** Simple data class to hold a set of dimens for left, top, right, bottom. */
+ private data class DimenHolder(
+ val left: Int,
+ val top: Int,
+ val right: Int,
+ val bottom: Int,
+ )
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
new file mode 100644
index 0000000..1d808ba
--- /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/BindServiceViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
deleted file mode 100644
index 925fae0e..0000000
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
+++ /dev/null
@@ -1,67 +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 BindServiceViaContextDetector : Detector(), SourceCodeScanner {
-
- override fun getApplicableMethodNames(): List<String> {
- return listOf("bindService", "bindServiceAsUser", "unbindService")
- }
-
- override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
- if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
- context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Binding or unbinding services are synchronous calls, please make " +
- "sure you're on a @Background Executor."
- )
- }
- }
-
- companion object {
- @JvmField
- val ISSUE: Issue =
- Issue.create(
- id = "BindServiceViaContextDetector",
- briefDescription = "Service bound/unbound via Context, please make sure " +
- "you're on a background thread.",
- 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,
- priority = 8,
- severity = Severity.WARNING,
- implementation =
- Implementation(BindServiceViaContextDetector::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 8d48f09..1129929 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 @@
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 @@
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 a629eee..0000000
--- 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/NonInjectedMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
new file mode 100644
index 0000000..bab76ab
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
@@ -0,0 +1,69 @@
+/*
+ * 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 org.jetbrains.uast.UCallExpression
+
+@Suppress("UnstableApiUsage")
+class NonInjectedMainThreadDetector : 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, CLASS_CONTEXT)) {
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "Replace with injected `@Main Executor`."
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "NonInjectedMainThread",
+ briefDescription = "Main thread usage without dependency injection",
+ explanation =
+ """
+ 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(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 4eb7c7d..b622900 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 @@
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 @@
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 @@
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 eb71d32..4ba3afc 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 @@
}
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 @@
@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 b006615..7be21a5 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 @@
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 @@
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 @@
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 @@
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 a584894..4eeeb85 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 @@
ISSUE,
referenced,
context.getNameLocation(referenced),
- "Usage of Config.HARDWARE is highly encouraged."
+ "Replace software bitmap with `Config.HARDWARE`"
)
}
}
@@ -56,12 +56,12 @@
@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 312810b..cf7c1b5 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 @@
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 26bd8d0..486af9d 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 @@
}
"""
),
- 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 @@
}
"""
),
- 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 @@
}
"""
),
- java(
+ indentedJava(
"""
package android.app;
import android.content.Context;
@@ -116,7 +130,7 @@
public class Activity extends Context {}
"""
),
- java(
+ indentedJava(
"""
package android.graphics;
@@ -132,17 +146,17 @@
}
"""
),
- 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 @@
}
"""
),
+ 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 0000000..6ae8fd3
--- /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 564afcb..0000000
--- 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 06aee8e..7d42280 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 @@
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 @@
)
.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 @@
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 @@
)
.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 @@
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,14 +121,44 @@
)
.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
fun testNoopIfNoCall() {
lint()
.files(
@@ -131,7 +167,7 @@
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
similarity index 64%
rename from packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index c55f399..c468af8 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 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 @@
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 @@
.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 @@
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 @@
.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 @@
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 @@
.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 6b9f88f..c83a35b 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 @@
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 @@
)
.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 @@
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 @@
)
.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 802ceba..ebcddeb 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 @@
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 @@
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 @@
)
.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 @@
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 @@
)
.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 @@
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 @@
)
.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 e265837..b03a11c 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 @@
package test.pkg;
import android.app.ActivityManager;
- public class TestClass1 {
+ public class TestClass {
public void slewlyGetCurrentUser() {
ActivityManager.getCurrentUser();
}
@@ -59,10 +59,13 @@
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 @@
package test.pkg;
import android.os.UserManager;
- public class TestClass2 {
+ public class TestClass {
public void slewlyGetUserInfo(UserManager userManager) {
userManager.getUserInfo();
}
@@ -90,9 +93,13 @@
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 @@
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 @@
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 fd6ab09..fb6537e 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 @@
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 @@
"""
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 @@
)
.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 @@
"""
import android.graphics.Bitmap;
- public class TestClass1 {
+ public class TestClass {
public void test() {
Bitmap.createBitmap(300, 300, Bitmap.Config.HARDWARE);
}
@@ -78,7 +85,7 @@
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
- .expectWarningCount(0)
+ .expectClean()
}
private val stubs = androidStubs
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index 4cfe392..fbdb526 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -30,8 +30,11 @@
],
static_libs: [
+ "SystemUIAnimationLib",
+
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
+ "androidx.savedstate_savedstate",
],
kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
new file mode 100644
index 0000000..8f9a4da
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.animation
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroupOverlay
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCompositionContext
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.scale
+import androidx.compose.ui.layout.boundsInRoot
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.Density
+import androidx.lifecycle.ViewTreeLifecycleOwner
+import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import com.android.systemui.animation.LaunchAnimator
+import kotlin.math.min
+
+/**
+ * Create an expandable shape that can launch into an Activity or a Dialog.
+ *
+ * Example:
+ * ```
+ * Expandable(
+ * color = MaterialTheme.colorScheme.primary,
+ * shape = RoundedCornerShape(16.dp),
+ * ) { controller ->
+ * Row(
+ * Modifier
+ * // For activities:
+ * .clickable { activityStarter.startActivity(intent, controller.forActivity()) }
+ *
+ * // For dialogs:
+ * .clickable { dialogLaunchAnimator.show(dialog, controller.forDialog()) }
+ * ) { ... }
+ * }
+ * ```
+ *
+ * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
+ * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
+ */
+@Composable
+fun Expandable(
+ color: Color,
+ shape: Shape,
+ modifier: Modifier = Modifier,
+ contentColor: Color = contentColorFor(color),
+ content: @Composable (ExpandableController) -> Unit,
+) {
+ Expandable(
+ rememberExpandableController(color, shape, contentColor),
+ modifier,
+ content,
+ )
+}
+
+/**
+ * Create an expandable shape that can launch into an Activity or a Dialog.
+ *
+ * This overload can be used in cases where you need to create the [ExpandableController] before
+ * composing this [Expandable], for instance if something outside of this Expandable can trigger a
+ * launch animation
+ *
+ * Example:
+ * ```
+ * // The controller that you can use to trigger the animations from anywhere.
+ * val controller =
+ * rememberExpandableController(
+ * color = MaterialTheme.colorScheme.primary,
+ * shape = RoundedCornerShape(16.dp),
+ * )
+ *
+ * Expandable(controller) {
+ * ...
+ * }
+ * ```
+ *
+ * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
+ * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
+ */
+@Composable
+fun Expandable(
+ controller: ExpandableController,
+ modifier: Modifier = Modifier,
+ content: @Composable (ExpandableController) -> Unit,
+) {
+ val controller = controller as ExpandableControllerImpl
+ val color = controller.color
+ val contentColor = controller.contentColor
+ val shape = controller.shape
+
+ // TODO(b/230830644): Use movableContentOf to preserve the content state instead once the
+ // Compose libraries have been updated and include aosp/2163631.
+ val wrappedContent =
+ @Composable { controller: ExpandableController ->
+ CompositionLocalProvider(
+ LocalContentColor provides contentColor,
+ ) {
+ content(controller)
+ }
+ }
+
+ val thisExpandableSize by remember {
+ derivedStateOf { controller.boundsInComposeViewRoot.value.size }
+ }
+
+ // Make sure we don't read animatorState directly here to avoid recomposition every time the
+ // state changes (i.e. every frame of the animation).
+ val isAnimating by remember {
+ derivedStateOf {
+ controller.animatorState.value != null && controller.overlay.value != null
+ }
+ }
+
+ when {
+ isAnimating -> {
+ // Don't compose the movable content during the animation, as it should be composed only
+ // once at all times. We make this spacer exactly the same size as this Expandable when
+ // it is visible.
+ Spacer(
+ modifier
+ .clip(shape)
+ .requiredSize(with(controller.density) { thisExpandableSize.toDpSize() })
+ )
+
+ // The content and its animated background in the overlay. We draw it only when we are
+ // animating.
+ AnimatedContentInOverlay(
+ color,
+ thisExpandableSize,
+ controller.animatorState,
+ controller.overlay.value
+ ?: error("AnimatedContentInOverlay shouldn't be composed with null overlay."),
+ controller,
+ wrappedContent,
+ controller.composeViewRoot,
+ { controller.currentComposeViewInOverlay.value = it },
+ controller.density,
+ )
+ }
+ controller.isDialogShowing.value -> {
+ Box(
+ modifier
+ .drawWithContent { /* Don't draw anything when the dialog is shown. */}
+ .onGloballyPositioned {
+ controller.boundsInComposeViewRoot.value = it.boundsInRoot()
+ }
+ ) { wrappedContent(controller) }
+ }
+ else -> {
+ Box(
+ modifier.clip(shape).background(color, shape).onGloballyPositioned {
+ controller.boundsInComposeViewRoot.value = it.boundsInRoot()
+ }
+ ) { wrappedContent(controller) }
+ }
+ }
+}
+
+/** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */
+@Composable
+private fun AnimatedContentInOverlay(
+ color: Color,
+ sizeInOriginalLayout: Size,
+ animatorState: State<LaunchAnimator.State?>,
+ overlay: ViewGroupOverlay,
+ controller: ExpandableController,
+ content: @Composable (ExpandableController) -> Unit,
+ composeViewRoot: View,
+ onOverlayComposeViewChanged: (View?) -> Unit,
+ density: Density,
+) {
+ val compositionContext = rememberCompositionContext()
+ val context = LocalContext.current
+
+ // Create the ComposeView and force its content composition so that the movableContent is
+ // composed exactly once when we start animating.
+ val composeViewInOverlay =
+ remember(context, density) {
+ val startWidth = sizeInOriginalLayout.width
+ val startHeight = sizeInOriginalLayout.height
+ val contentModifier =
+ Modifier
+ // Draw the content with the same size as it was at the start of the animation
+ // so that its content is laid out exactly the same way.
+ .requiredSize(with(density) { sizeInOriginalLayout.toDpSize() })
+ .drawWithContent {
+ val animatorState = animatorState.value ?: return@drawWithContent
+
+ // Scale the content with the background while keeping its aspect ratio.
+ val widthRatio =
+ if (startWidth != 0f) {
+ animatorState.width.toFloat() / startWidth
+ } else {
+ 1f
+ }
+ val heightRatio =
+ if (startHeight != 0f) {
+ animatorState.height.toFloat() / startHeight
+ } else {
+ 1f
+ }
+ val scale = min(widthRatio, heightRatio)
+ scale(scale) { this@drawWithContent.drawContent() }
+ }
+
+ val composeView =
+ ComposeView(context).apply {
+ setContent {
+ Box(
+ Modifier.fillMaxSize().drawWithContent {
+ val animatorState = animatorState.value ?: return@drawWithContent
+ if (!animatorState.visible) {
+ return@drawWithContent
+ }
+
+ val topRadius = animatorState.topCornerRadius
+ val bottomRadius = animatorState.bottomCornerRadius
+ if (topRadius == bottomRadius) {
+ // Shortcut to avoid Outline calculation and allocation.
+ val cornerRadius = CornerRadius(topRadius)
+ drawRoundRect(color, cornerRadius = cornerRadius)
+ } else {
+ val shape =
+ RoundedCornerShape(
+ topStart = topRadius,
+ topEnd = topRadius,
+ bottomStart = bottomRadius,
+ bottomEnd = bottomRadius,
+ )
+ val outline = shape.createOutline(size, layoutDirection, this)
+ drawOutline(outline, color = color)
+ }
+
+ drawContent()
+ },
+ // We center the content in the expanding container.
+ contentAlignment = Alignment.Center,
+ ) {
+ Box(contentModifier) { content(controller) }
+ }
+ }
+ }
+
+ // Set the owners.
+ val overlayViewGroup =
+ getOverlayViewGroup(
+ context,
+ overlay,
+ )
+ ViewTreeLifecycleOwner.set(
+ overlayViewGroup,
+ ViewTreeLifecycleOwner.get(composeViewRoot),
+ )
+ ViewTreeViewModelStoreOwner.set(
+ overlayViewGroup,
+ ViewTreeViewModelStoreOwner.get(composeViewRoot),
+ )
+ ViewTreeSavedStateRegistryOwner.set(
+ overlayViewGroup,
+ ViewTreeSavedStateRegistryOwner.get(composeViewRoot),
+ )
+
+ composeView.setParentCompositionContext(compositionContext)
+
+ composeView
+ }
+
+ DisposableEffect(overlay, composeViewInOverlay) {
+ // Add the ComposeView to the overlay.
+ overlay.add(composeViewInOverlay)
+
+ val startState =
+ animatorState.value
+ ?: throw IllegalStateException(
+ "AnimatedContentInOverlay shouldn't be composed with null animatorState."
+ )
+ measureAndLayoutComposeViewInOverlay(composeViewInOverlay, startState)
+ onOverlayComposeViewChanged(composeViewInOverlay)
+
+ onDispose {
+ composeViewInOverlay.disposeComposition()
+ overlay.remove(composeViewInOverlay)
+ onOverlayComposeViewChanged(null)
+ }
+ }
+}
+
+internal fun measureAndLayoutComposeViewInOverlay(
+ view: View,
+ state: LaunchAnimator.State,
+) {
+ val exactWidth = state.width
+ val exactHeight = state.height
+ view.measure(
+ View.MeasureSpec.makeSafeMeasureSpec(exactWidth, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeSafeMeasureSpec(exactHeight, View.MeasureSpec.EXACTLY),
+ )
+
+ val parent = view.parent as ViewGroup
+ val parentLocation = parent.locationOnScreen
+ val offsetX = parentLocation[0]
+ val offsetY = parentLocation[1]
+ view.layout(
+ state.left - offsetX,
+ state.top - offsetY,
+ state.right - offsetX,
+ state.bottom - offsetY,
+ )
+}
+
+// TODO(b/230830644): Add hidden API to ViewGroupOverlay to access this ViewGroup directly?
+private fun getOverlayViewGroup(context: Context, overlay: ViewGroupOverlay): ViewGroup {
+ val view = View(context)
+ overlay.add(view)
+ var current = view.parent
+ while (current.parent != null) {
+ current = current.parent
+ }
+ overlay.remove(view)
+ return current as ViewGroup
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
new file mode 100644
index 0000000..065c314
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.animation
+
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroupOverlay
+import android.view.ViewRootImpl
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.LaunchAnimator
+import kotlin.math.roundToInt
+
+/** A controller that can control animated launches. */
+interface ExpandableController {
+ /** Create an [ActivityLaunchAnimator.Controller] to animate into an Activity. */
+ fun forActivity(): ActivityLaunchAnimator.Controller
+
+ /** Create a [DialogLaunchAnimator.Controller] to animate into a Dialog. */
+ fun forDialog(): DialogLaunchAnimator.Controller
+}
+
+/**
+ * Create an [ExpandableController] to control an [Expandable]. This is useful if you need to create
+ * the controller before the [Expandable], for instance to handle clicks outside of the Expandable
+ * that would still trigger a dialog/activity launch animation.
+ */
+@Composable
+fun rememberExpandableController(
+ color: Color,
+ shape: Shape,
+ contentColor: Color = contentColorFor(color),
+): ExpandableController {
+ val composeViewRoot = LocalView.current
+ val density = LocalDensity.current
+ val layoutDirection = LocalLayoutDirection.current
+
+ // The current animation state, if we are currently animating a dialog or activity.
+ val animatorState = remember { mutableStateOf<LaunchAnimator.State?>(null) }
+
+ // Whether a dialog controlled by this ExpandableController is currently showing.
+ val isDialogShowing = remember { mutableStateOf(false) }
+
+ // The overlay in which we should animate the launch.
+ val overlay = remember { mutableStateOf<ViewGroupOverlay?>(null) }
+
+ // The current [ComposeView] being animated in the [overlay], if any.
+ val currentComposeViewInOverlay = remember { mutableStateOf<View?>(null) }
+
+ // The bounds in [composeViewRoot] of the expandable controlled by this controller.
+ val boundsInComposeViewRoot = remember { mutableStateOf(Rect.Zero) }
+
+ // Whether this composable is still composed. We only do the dialog exit animation if this is
+ // true.
+ val isComposed = remember { mutableStateOf(true) }
+ DisposableEffect(Unit) { onDispose { isComposed.value = false } }
+
+ return remember(color, contentColor, shape, composeViewRoot, density, layoutDirection) {
+ ExpandableControllerImpl(
+ color,
+ contentColor,
+ shape,
+ composeViewRoot,
+ density,
+ animatorState,
+ isDialogShowing,
+ overlay,
+ currentComposeViewInOverlay,
+ boundsInComposeViewRoot,
+ layoutDirection,
+ isComposed,
+ )
+ }
+}
+
+internal class ExpandableControllerImpl(
+ internal val color: Color,
+ internal val contentColor: Color,
+ internal val shape: Shape,
+ internal val composeViewRoot: View,
+ internal val density: Density,
+ internal val animatorState: MutableState<LaunchAnimator.State?>,
+ internal val isDialogShowing: MutableState<Boolean>,
+ internal val overlay: MutableState<ViewGroupOverlay?>,
+ internal val currentComposeViewInOverlay: MutableState<View?>,
+ internal val boundsInComposeViewRoot: MutableState<Rect>,
+ private val layoutDirection: LayoutDirection,
+ private val isComposed: State<Boolean>,
+) : ExpandableController {
+ override fun forActivity(): ActivityLaunchAnimator.Controller {
+ return activityController()
+ }
+
+ override fun forDialog(): DialogLaunchAnimator.Controller {
+ return dialogController()
+ }
+
+ /**
+ * Create a [LaunchAnimator.Controller] that is going to be used to drive an activity or dialog
+ * animation. This controller will:
+ * 1. Compute the start/end animation state using [boundsInComposeViewRoot] and the location of
+ * composeViewRoot on the screen.
+ * 2. Update [animatorState] with the current animation state if we are animating, or null
+ * otherwise.
+ */
+ private fun launchController(): LaunchAnimator.Controller {
+ return object : LaunchAnimator.Controller {
+ private val rootLocationOnScreen = intArrayOf(0, 0)
+
+ override var launchContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ animatorState.value = null
+ }
+
+ override fun onLaunchAnimationProgress(
+ state: LaunchAnimator.State,
+ progress: Float,
+ linearProgress: Float
+ ) {
+ // We copy state given that it's always the same object that is mutated by
+ // ActivityLaunchAnimator.
+ animatorState.value =
+ LaunchAnimator.State(
+ state.top,
+ state.bottom,
+ state.left,
+ state.right,
+ state.topCornerRadius,
+ state.bottomCornerRadius,
+ )
+ .apply { visible = state.visible }
+
+ // Force measure and layout the ComposeView in the overlay whenever the animation
+ // state changes.
+ currentComposeViewInOverlay.value?.let {
+ measureAndLayoutComposeViewInOverlay(it, state)
+ }
+ }
+
+ override fun createAnimatorState(): LaunchAnimator.State {
+ val boundsInRoot = boundsInComposeViewRoot.value
+ val outline =
+ shape.createOutline(
+ Size(boundsInRoot.width, boundsInRoot.height),
+ layoutDirection,
+ density,
+ )
+
+ val (topCornerRadius, bottomCornerRadius) =
+ when (outline) {
+ is Outline.Rectangle -> 0f to 0f
+ is Outline.Rounded -> {
+ val roundRect = outline.roundRect
+
+ // TODO(b/230830644): Add better support different corner radii.
+ val topCornerRadius =
+ maxOf(
+ roundRect.topLeftCornerRadius.x,
+ roundRect.topLeftCornerRadius.y,
+ roundRect.topRightCornerRadius.x,
+ roundRect.topRightCornerRadius.y,
+ )
+ val bottomCornerRadius =
+ maxOf(
+ roundRect.bottomLeftCornerRadius.x,
+ roundRect.bottomLeftCornerRadius.y,
+ roundRect.bottomRightCornerRadius.x,
+ roundRect.bottomRightCornerRadius.y,
+ )
+
+ topCornerRadius to bottomCornerRadius
+ }
+ else ->
+ error(
+ "ExpandableState only supports (rounded) rectangles at the " +
+ "moment."
+ )
+ }
+
+ val rootLocation = rootLocationOnScreen()
+ return LaunchAnimator.State(
+ top = rootLocation.y.roundToInt(),
+ bottom = (rootLocation.y + boundsInRoot.height).roundToInt(),
+ left = rootLocation.x.roundToInt(),
+ right = (rootLocation.x + boundsInRoot.width).roundToInt(),
+ topCornerRadius = topCornerRadius,
+ bottomCornerRadius = bottomCornerRadius,
+ )
+ }
+
+ private fun rootLocationOnScreen(): Offset {
+ composeViewRoot.getLocationOnScreen(rootLocationOnScreen)
+ val boundsInRoot = boundsInComposeViewRoot.value
+ val x = rootLocationOnScreen[0] + boundsInRoot.left
+ val y = rootLocationOnScreen[1] + boundsInRoot.top
+ return Offset(x, y)
+ }
+ }
+ }
+
+ /** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
+ private fun activityController(): ActivityLaunchAnimator.Controller {
+ val delegate = launchController()
+ return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ overlay.value = null
+ }
+ }
+ }
+
+ private fun dialogController(): DialogLaunchAnimator.Controller {
+ return object : DialogLaunchAnimator.Controller {
+ override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
+ override val sourceIdentity: Any = this@ExpandableControllerImpl
+
+ override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+ val newOverlay = viewGroup.overlay as ViewGroupOverlay
+ if (newOverlay != overlay.value) {
+ overlay.value = newOverlay
+ }
+ }
+
+ override fun stopDrawingInOverlay() {
+ if (overlay.value != null) {
+ overlay.value = null
+ }
+ }
+
+ override fun createLaunchController(): LaunchAnimator.Controller {
+ val delegate = launchController()
+ return object : LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+ // Make sure we don't draw this expandable when the dialog is showing.
+ isDialogShowing.value = true
+ }
+ }
+ }
+
+ override fun createExitController(): LaunchAnimator.Controller {
+ val delegate = launchController()
+ return object : LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ isDialogShowing.value = false
+ }
+ }
+ }
+
+ override fun shouldAnimateExit(): Boolean = isComposed.value
+
+ override fun onExitAnimationCancelled() {
+ isDialogShowing.value = false
+ }
+
+ override fun jankConfigurationBuilder(
+ cuj: Int
+ ): InteractionJankMonitor.Configuration.Builder? {
+ // TODO(b/252723237): Add support for jank monitoring when animating from a
+ // Composable.
+ return null
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
index 3175dcf..4d94bab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
@@ -17,8 +17,6 @@
package com.android.systemui.user.ui.compose
-import android.graphics.Bitmap
-import android.graphics.Canvas
import android.graphics.drawable.Drawable
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.foundation.Image
@@ -50,10 +48,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -62,6 +58,7 @@
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
import com.android.systemui.common.ui.compose.load
import com.android.systemui.compose.SysUiOutlinedButton
import com.android.systemui.compose.SysUiTextButton
@@ -356,10 +353,11 @@
remember(viewModel.iconResourceId) {
val drawable =
checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
+ val size = with(density) { 20.dp.toPx() }.toInt()
drawable
.toBitmap(
- size = with(density) { 20.dp.toPx() }.toInt(),
- tintColor = Color.White,
+ width = size,
+ height = size,
)
.asImageBitmap()
}
@@ -392,32 +390,3 @@
),
)
}
-
-/**
- * Converts the [Drawable] to a [Bitmap].
- *
- * Note that this is a relatively memory-heavy operation as it allocates a whole bitmap and draws
- * the `Drawable` onto it. Use sparingly and with care.
- */
-private fun Drawable.toBitmap(
- size: Int? = null,
- tintColor: Color? = null,
-): Bitmap {
- val bitmap =
- if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
- Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
- } else {
- Bitmap.createBitmap(
- size ?: intrinsicWidth,
- size ?: intrinsicHeight,
- Bitmap.Config.ARGB_8888
- )
- }
- val canvas = Canvas(bitmap)
- setBounds(0, 0, canvas.width, canvas.height)
- if (tintColor != null) {
- setTint(tintColor.toArgb())
- }
- draw(canvas)
- return bitmap
-}
diff --git a/packages/SystemUI/docs/device-entry/doze.md b/packages/SystemUI/docs/device-entry/doze.md
index 6b6dce5..10bd367 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 @@
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 f3d12c2..7f19b16 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/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 9f275af..a850238 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -246,8 +246,6 @@
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt
-packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -493,7 +491,7 @@
-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/ShadeStateListener.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
@@ -528,6 +526,8 @@
-packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
-packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
-packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+-packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
-packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
-packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
-packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
@@ -678,7 +678,6 @@
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
-packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
@@ -812,7 +811,7 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/ShadeExpansionStateManagerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -833,6 +832,7 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
-packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
-packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 01e5d86..1e74c3d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -39,19 +39,19 @@
fun getClocks(): List<ClockMetadata>
/** Initializes and returns the target clock design */
- fun createClock(id: ClockId): Clock
+ fun createClock(id: ClockId): ClockController
/** A static thumbnail for rendering in some examples */
fun getClockThumbnail(id: ClockId): Drawable?
}
/** Interface for controlling an active clock */
-interface Clock {
+interface ClockController {
/** A small version of the clock, appropriate for smaller viewports */
- val smallClock: View
+ val smallClock: ClockFaceController
/** A large version of the clock, appropriate when a bigger viewport is available */
- val largeClock: View
+ val largeClock: ClockFaceController
/** Events that clocks may need to respond to */
val events: ClockEvents
@@ -61,7 +61,7 @@
/** Initializes various rendering parameters. If never called, provides reasonable defaults. */
fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
- events.onColorPaletteChanged(resources, true, true)
+ events.onColorPaletteChanged(resources)
animations.doze(dozeFraction)
animations.fold(foldFraction)
events.onTimeTick()
@@ -71,10 +71,19 @@
fun dump(pw: PrintWriter) { }
}
+/** Interface for a specific clock face version rendered by the clock */
+interface ClockFaceController {
+ /** View that renders the clock face */
+ val view: View
+
+ /** Events specific to this clock face */
+ val events: ClockFaceEvents
+}
+
/** Events that should call when various rendering parameters change */
interface ClockEvents {
/** Call every time tick */
- fun onTimeTick()
+ fun onTimeTick() { }
/** Call whenever timezone changes */
fun onTimeZoneChanged(timeZone: TimeZone) { }
@@ -89,11 +98,7 @@
fun onFontSettingChanged() { }
/** Call whenever the color palette should update */
- fun onColorPaletteChanged(
- resources: Resources,
- smallClockIsDark: Boolean,
- largeClockIsDark: Boolean
- ) { }
+ fun onColorPaletteChanged(resources: Resources) { }
}
/** Methods which trigger various clock animations */
@@ -111,6 +116,12 @@
fun charge() { }
}
+/** Events that have specific data about the related face */
+interface ClockFaceEvents {
+ /** Region Darkness specific to the clock face */
+ fun onRegionDarknessChanged(isDark: Boolean) { }
+}
+
/** Some data about a clock design */
data class ClockMetadata(
val clockId: ClockId,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
index 506ccf3..5f6f11c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
@@ -67,7 +67,6 @@
*
* @param triggerBack if back will be triggered in current state.
*/
- // TODO(b/247883311): Remove default impl once SwipeBackGestureHandler overrides this.
- default void setTriggerBack(boolean triggerBack) {}
+ void setTriggerBack(boolean triggerBack);
}
}
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index 716c4fe..2261ae8 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -46,7 +46,7 @@
>
<com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
+ android:id="@id/multi_user_switch"
android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
android:background="@drawable/qs_footer_action_circle"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 5486adb..6ec65ce 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -24,7 +24,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
androidprv:layout_maxWidth="@dimen/keyguard_security_width"
- androidprv:layout_maxHeight="@dimen/keyguard_security_height"
android:layout_gravity="center_horizontal|bottom"
android:gravity="bottom"
>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
new file mode 100644
index 0000000..29832a0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto" >
+
+ <com.android.keyguard.AlphaOptimizedLinearLayout
+ android:id="@+id/mobile_group"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" >
+
+ <FrameLayout
+ android:id="@+id/inout_container"
+ android:layout_height="17dp"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical">
+ <ImageView
+ android:id="@+id/mobile_in"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_activity_down"
+ android:visibility="gone"
+ android:paddingEnd="2dp"
+ />
+ <ImageView
+ android:id="@+id/mobile_out"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_activity_up"
+ android:paddingEnd="2dp"
+ android:visibility="gone"
+ />
+ </FrameLayout>
+ <ImageView
+ android:id="@+id/mobile_type"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingStart="2.5dp"
+ android:paddingEnd="1dp"
+ android:visibility="gone" />
+ <Space
+ android:id="@+id/mobile_roaming_space"
+ android:layout_height="match_parent"
+ android:layout_width="@dimen/roaming_icon_start_padding"
+ android:visibility="gone"
+ />
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical">
+ <com.android.systemui.statusbar.AnimatedImageView
+ android:id="@+id/mobile_signal"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ systemui:hasOverlappingRendering="false"
+ />
+ <ImageView
+ android:id="@+id/mobile_roaming"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/stat_sys_roaming"
+ android:contentDescription="@string/data_connection_roaming"
+ android:visibility="gone" />
+ </FrameLayout>
+ <ImageView
+ android:id="@+id/mobile_roaming_large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/stat_sys_roaming_large"
+ android:contentDescription="@string/data_connection_roaming"
+ android:visibility="gone" />
+ </com.android.keyguard.AlphaOptimizedLinearLayout>
+</merge>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
new file mode 100644
index 0000000..1b38fd2
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/mobile_combo"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical" >
+
+ <include layout="@layout/status_bar_mobile_signal_group_inner" />
+
+</com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView>
+
diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml
index b22655a..cbfb325 100644
--- a/packages/SystemUI/res-keyguard/values-as/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-as/strings.xml
@@ -64,9 +64,9 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"ছিম কার্ড আনলক কৰি থকা হৈছে…"</string>
<string name="kg_invalid_sim_pin_hint" msgid="2762202646949552978">"৪টাৰ পৰা ৮টা সংখ্যাযুক্ত এটা পিন লিখক।"</string>
<string name="kg_invalid_sim_puk_hint" msgid="5319756880543857694">"PUK ক\'ডটো ৮টা বা তাতকৈ অধিক সংখ্যা থকা হ\'ব লাগিব।"</string>
- <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+ <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
<string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
- <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"আপুনি আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+ <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"আপুনি আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
<string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"ছিমৰ ভুল পিন ক\'ড, আপোনাৰ ডিভাইচটো আনলক কৰিবলৈ আপুনি এতিয়া আপোনাৰ বাহকৰ সৈতে যোগাযোগ কৰিবই লাগিব।"</string>
<string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ডিভাইচ আনলক কৰিবলৈ আপোনাৰ বাহকৰ লগত যোগাযোগ কৰিবই লগা হোৱাৰ পূৰ্বে আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে।}one{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে। }other{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে। }}"</string>
<string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"ছিম ব্যৱহাৰযোগ্য নহয়। আপোনাৰ বাহকৰ সৈতে যোগাযোগ কৰক।"</string>
@@ -75,9 +75,9 @@
<string name="kg_password_puk_failed" msgid="6778867411556937118">"ছিম PUKৰ জৰিয়তে আনলক কৰিব পৰা নগ\'ল!"</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ইনপুট পদ্ধতি সলনি কৰক"</string>
<string name="airplane_mode" msgid="2528005343938497866">"এয়াৰপ্লে’ন ম’ড"</string>
- <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত পিন দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string>
+ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string>
+ <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পিন দিয়াটো বাধ্যতামূলক"</string>
+ <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string>
<string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"অতিৰিক্ত সুৰক্ষাৰ বাবে আর্হি দিয়াটো বাধ্যতামূলক"</string>
<string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"অতিৰিক্ত সুৰক্ষাৰ বাবে পিন দিয়াটো বাধ্যতামূলক"</string>
<string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"অতিৰিক্ত সুৰক্ষাৰ বাবে পাছৱর্ড দিয়াটো বাধ্যতামূলক"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index 62379f8..45dadc1 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -29,7 +29,7 @@
<string name="keyguard_plugged_in_dock" msgid="2122073051904360987">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge"</string>
<string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge…"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge rapide…"</string>
- <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge lente…"</string>
+ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge lente"</string>
<string name="keyguard_plugged_in_charging_limited" msgid="1709413803451065875">"<xliff:g id="PERCENTAGE">%s</xliff:g> • La recharge est en pause pour protéger la batterie"</string>
<string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Appuyez sur \"Menu\" pour déverrouiller le clavier."</string>
<string name="keyguard_network_locked_message" msgid="407096292844868608">"Réseau verrouillé"</string>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index d90156d..a129fb6 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-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index a1d1266..b86929e 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -25,7 +25,7 @@
</style>
<style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
<item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
- <item name="android:textSize">14dp</item>
+ <item name="android:textSize">14sp</item>
<item name="android:background">@drawable/kg_emergency_button_background</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:paddingLeft">12dp</item>
diff --git a/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml b/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml
index 6939084..33c68bf1 100644
--- a/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml
+++ b/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml
@@ -16,6 +16,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <solid android:color="@android:color/white" />
+ <solid android:color="@android:color/transparent" />
<corners android:radius="@dimen/qs_media_album_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/chipbar.xml
similarity index 96%
rename from packages/SystemUI/res/layout/media_ttt_chip.xml
rename to packages/SystemUI/res/layout/chipbar.xml
index ae8e38e..4da7711 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -16,7 +16,7 @@
<!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view
is the root view of a window, we cannot change the root view's margins.) -->
<!-- Alphas start as 0 because the view will be animated in. -->
-<com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView
+<com.android.systemui.temporarydisplay.chipbar.ChipbarRootView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/media_ttt_sender_chip"
@@ -97,4 +97,4 @@
/>
</LinearLayout>
-</com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView>
+</com.android.systemui.temporarydisplay.chipbar.ChipbarRootView>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 8df8c49..6120863 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -59,7 +59,7 @@
</LinearLayout>
- <ImageView
+ <com.android.systemui.common.ui.view.LaunchableImageView
android:id="@+id/start_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
@@ -71,7 +71,7 @@
android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
android:visibility="gone" />
- <ImageView
+ <com.android.systemui.common.ui.view.LaunchableImageView
android:id="@+id/end_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index 226bc6a..e474938 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -49,6 +49,8 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
+ android:focusable="false"
+ android:clickable="false"
android:gravity="center"
android:paddingBottom="@*android:dimen/chooser_view_spacing"
android:paddingLeft="24dp"
diff --git a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
index a2b3c40..31baf26 100644
--- a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
+++ b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
@@ -23,8 +23,9 @@
>
<FrameLayout
+ android:id="@+id/media_projection_recent_tasks_container"
android:layout_width="match_parent"
- android:layout_height="256dp">
+ android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/media_projection_recent_tasks_recycler"
diff --git a/packages/SystemUI/res/layout/media_projection_task_item.xml b/packages/SystemUI/res/layout/media_projection_task_item.xml
index 75f73cd..cfa586f 100644
--- a/packages/SystemUI/res/layout/media_projection_task_item.xml
+++ b/packages/SystemUI/res/layout/media_projection_task_item.xml
@@ -18,7 +18,9 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:clickable="true"
+ >
<ImageView
android:id="@+id/task_icon"
@@ -27,12 +29,12 @@
android:layout_margin="8dp"
android:importantForAccessibility="no" />
- <!-- TODO(b/240924926) use a custom view that will handle thumbnail cropping correctly -->
- <!-- TODO(b/240924926) dynamically change the view size based on the screen size -->
- <ImageView
+ <!-- This view size will be calculated in runtime -->
+ <com.android.systemui.mediaprojection.appselector.view.MediaProjectionTaskView
android:id="@+id/task_thumbnail"
- android:layout_width="100dp"
- android:layout_height="216dp"
- android:scaleType="centerCrop"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ android:focusable="false"
/>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index e079fd3..21d12c2 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -29,6 +29,7 @@
<com.android.internal.widget.CachingIconView
android:id="@+id/app_icon"
+ android:background="@drawable/media_ttt_chip_background_receiver"
android:layout_width="@dimen/media_ttt_icon_size_receiver"
android:layout_height="@dimen/media_ttt_icon_size_receiver"
android:layout_gravity="center|bottom"
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
index c29e11b..c134c8e 100644
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ b/packages/SystemUI/res/layout/screenshot.xml
@@ -35,12 +35,6 @@
android:visibility="gone"
android:elevation="7dp"
android:src="@android:color/white"/>
- <com.android.systemui.screenshot.ScreenshotSelectorView
- android:id="@+id/screenshot_selector"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- android:pointerIcon="crosshair"/>
<include layout="@layout/screenshot_static"
android:id="@+id/screenshot_static"/>
</com.android.systemui.screenshot.ScreenshotView>
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
index 10d49b3..d6c63eb 100644
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
@@ -18,80 +18,12 @@
-->
<com.android.systemui.statusbar.StatusBarMobileView
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/mobile_combo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical" >
- <com.android.keyguard.AlphaOptimizedLinearLayout
- android:id="@+id/mobile_group"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:orientation="horizontal" >
+ <include layout="@layout/status_bar_mobile_signal_group_inner" />
- <FrameLayout
- android:id="@+id/inout_container"
- android:layout_height="17dp"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical">
- <ImageView
- android:id="@+id/mobile_in"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_down"
- android:visibility="gone"
- android:paddingEnd="2dp"
- />
- <ImageView
- android:id="@+id/mobile_out"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_up"
- android:paddingEnd="2dp"
- android:visibility="gone"
- />
- </FrameLayout>
- <ImageView
- android:id="@+id/mobile_type"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical"
- android:paddingStart="2.5dp"
- android:paddingEnd="1dp"
- android:visibility="gone" />
- <Space
- android:id="@+id/mobile_roaming_space"
- android:layout_height="match_parent"
- android:layout_width="@dimen/roaming_icon_start_padding"
- android:visibility="gone"
- />
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical">
- <com.android.systemui.statusbar.AnimatedImageView
- android:id="@+id/mobile_signal"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- systemui:hasOverlappingRendering="false"
- />
- <ImageView
- android:id="@+id/mobile_roaming"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/stat_sys_roaming"
- android:contentDescription="@string/data_connection_roaming"
- android:visibility="gone" />
- </FrameLayout>
- <ImageView
- android:id="@+id/mobile_roaming_large"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/stat_sys_roaming_large"
- android:contentDescription="@string/data_connection_roaming"
- android:visibility="gone" />
- </com.android.keyguard.AlphaOptimizedLinearLayout>
</com.android.systemui.statusbar.StatusBarMobileView>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 5771547..167885e 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Deurlopende kennisgewing vir \'n skermopnamesessie"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Begin opname?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Terwyl dit opneem, kan die Android-stelsel enige sensitiewe inligting wat op jou skerm sigbaar is of wat op jou toestel gespeel word, vasvang. Dit sluit wagwoorde, betalinginligting, foto\'s, boodskappe en oudio in."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Neem hele skerm op"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Neem ’n enkele program op"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Terwyl jy opneem, het Android toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Terwyl jy ’n program opneem, het Android toegang tot enigiets wat in daardie program gewys of gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Begin opneem"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Neem oudio op"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Toesteloudio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Klank vanaf jou toestel, soos musiek, oproepe en luitone"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Die diens wat hierdie funksie verskaf, sal toegang hê tot al die inligting wat op jou skerm sigbaar is of wat op jou toestel gespeel word terwyl dit opneem of uitsaai. Dit sluit in inligting soos wagwoorde, betalingbesonderhede, foto\'s, boodskappe en oudio wat jy speel."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Begin opneem of uitsaai?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Begin opneem of uitsaai met <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Laat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toe om te deel of op te neem?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hele skerm"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"’n Enkele program"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Wanneer jy deel, opneem of uitsaai, het <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Wanneer jy ’n program deel, opneem of uitsaai, het <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot enigiets wat in daardie program sigbaar is of daarin gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Gaan voort"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deel of neem ’n program op"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Bestuur"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Geskiedenis"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 0f6410a..eccce2f 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -94,9 +94,14 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ለአንድ የማያ ገጽ ቀረጻ ክፍለ-ጊዜ በመካሄድ ያለ ማሳወቂያ"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"መቅረጽ ይጀመር?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"እየቀረጹ ሳለ የAndroid ስርዓት በማያ ገጽዎ ላይ የሚታይ ወይም በመሣሪያዎ ላይ የሚጫወት ማንኛውም ሚስጥራዊነት ያለው መረጃን መያዝ ይችላል። ይህ የይለፍ ቃላትን፣ የክፍያ መረጃን፣ ፎቶዎችን፣ መልዕክቶችን እና ኦዲዮን ያካትታል።"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"መላው ማያ ገጹን ቅረጽ"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"አንድ ነጠላ መተግበሪያን ቅረጽ"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"እየቀረጹ እያለ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"አንድን መተግበሪያ እየቀረጹ ሳለ Android በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"መቅረጽ ጀምር"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ኦዲዮን ቅረጽ"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"የመሣሪያ ኦዲዮ"</string>
- <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"እንደ ሙዚቃ፣ ጥሪዎች እና የጥሪ ቅላጼዎች ያሉ የመሣሪያዎ ድምጽ"</string>
+ <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"እንደ ሙዚቃ፣ ጥሪዎች እና የጥሪ ቅላጼዎች ያሉ የመሣሪያዎ ድምፅ"</string>
<string name="screenrecord_mic_label" msgid="2111264835791332350">"ማይክሮፎን"</string>
<string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"የመሣሪያ ኦዲዮ እና ማይክሮፎን"</string>
<string name="screenrecord_start" msgid="330991441575775004">"ጀምር"</string>
@@ -264,14 +269,14 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"የ<xliff:g id="DATA_LIMIT">%s</xliff:g> ማስጠንቀቂያ"</string>
<string name="quick_settings_work_mode_label" msgid="6440531507319809121">"የሥራ መተግበሪያዎች"</string>
<string name="quick_settings_night_display_label" msgid="8180030659141778180">"የምሽት ብርሃን"</string>
- <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"ጸሐይ ስትጠልቅ ይበራል"</string>
- <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"ጸሐይ እስክትወጣ ድረስ"</string>
+ <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"ፀሐይ ስትጠልቅ ይበራል"</string>
+ <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"ፀሐይ እስክትወጣ ድረስ"</string>
<string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"<xliff:g id="TIME">%s</xliff:g> ላይ ይበራል"</string>
<string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"እስከ <xliff:g id="TIME">%s</xliff:g> ድረስ"</string>
<string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"ጨለማ ገጽታ"</string>
<string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ባትሪ ቆጣቢ"</string>
- <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ጸሐይ ስትጠልቅ ይበራል"</string>
- <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ጸሐይ እስክትወጣ ድረስ"</string>
+ <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ፀሐይ ስትጠልቅ ይበራል"</string>
+ <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ፀሐይ እስክትወጣ ድረስ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> ላይ ይበራል"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"እስከ <xliff:g id="TIME">%s</xliff:g> ድረስ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"በመኝታ ሰዓት ላይ"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ይህን ተግባር የሚያቀርበው አገልግሎት በእርስዎ ማያ ገጽ ላይ ያለን ወይም በእርስዎ መሣሪያ ላይ በመጫወት ላይ ያለን ሁሉንም መረጃ በቀረጻ ወይም casting ላይ እያለ መዳረሻ ይኖረዋል። ይህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ ፎቶዎች፣ መልዕክቶች እና እርስዎ የሚጫውቱት ኦዲዮን የመሳሰለ መረጃን ያካትታል።"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ቀረጻ ወይም cast ማድረግ ይጀምር?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"ከ<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ጋር ቀረጻ ወይም casting ይጀምር?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> እንዲያጋራ ወይም እንዲቀርጽ ይፈቀድለት?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"መላው ማያ ገጽ"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"አንድ ነጠላ መተግበሪያ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"አንድን መተግበሪያ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ቀጥል"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"መተግበሪያ ያጋሩ ወይም ይቅረጹ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ያቀናብሩ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ታሪክ"</string>
@@ -421,7 +433,7 @@
<string name="volume_odi_captions_content_description" msgid="4172765742046013630">"የሥዕል መግለጫ ጽሑፎች ንብርብር"</string>
<string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"አንቃ"</string>
<string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"አሰናክል"</string>
- <string name="sound_settings" msgid="8874581353127418308">"ድምጽ እና ንዝረት"</string>
+ <string name="sound_settings" msgid="8874581353127418308">"ድምፅ እና ንዝረት"</string>
<string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"ቅንብሮች"</string>
<string name="screen_pinning_title" msgid="9058007390337841305">"መተግበሪያ ተሰክቷል"</string>
<string name="screen_pinning_description" msgid="8699395373875667743">"ይሄ እስኪነቅሉት ድረስ በእይታ ውስጥ ያስቀምጠዋል። ለመንቀል ተመለስ እና አጠቃላይ ዕይታ የሚለውን ይጫኑ እና ይያዙ።"</string>
@@ -503,11 +515,11 @@
<string name="notification_silence_title" msgid="8608090968400832335">"ፀጥ ያለ"</string>
<string name="notification_alert_title" msgid="3656229781017543655">"ነባሪ"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"ራስ-ሰር"</string>
- <string name="notification_channel_summary_low" msgid="4860617986908931158">"ምንም ድምጽ ወይም ንዝረት የለም"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ምንም ድምጽ ወይም ንዝረት የለም እና በውይይት ክፍል ላይ አይታይም"</string>
+ <string name="notification_channel_summary_low" msgid="4860617986908931158">"ምንም ድምፅ ወይም ንዝረት የለም"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ምንም ድምፅ ወይም ንዝረት የለም እና በውይይት ክፍል ላይ አይታይም"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"በእርስዎ የስልክ ቅንብሮች የሚወሰን ሆኖ ሊደውል ወይም ሊነዝር ይችላል"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"በእርስዎ የስልክ ቅንብሮች የሚወሰን ሆኖ ሊደውል ወይም ሊነዝር ይችላል። የ<xliff:g id="APP_NAME">%1$s</xliff:g> አረፋ ውይይቶች በነባሪነት።"</string>
- <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ይህ ማሳወቂያ ድምጽ ወይም ንዝረት መደረግ ካለበት ስርዓቱ እንዲወሰን ያድርጉት"</string>
+ <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ይህ ማሳወቂያ ድምፅ ወይም ንዝረት መደረግ ካለበት ስርዓቱ እንዲወሰን ያድርጉት"</string>
<string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>ሁኔታ:</b> ለነባሪ ከፍ ተዋውቋል።"</string>
<string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>ሁኔታ:</b> ወደ ዝምታ ዝቅ ተደርጓል"</string>
<string name="notification_channel_summary_automatic_promoted" msgid="1301710305149590426">"<b>ሁኔታ:</b> ክፍተኛ ደረጃ ተሰጥቶታል"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 2724aee..8f996fb 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"إشعار مستمر لجلسة تسجيل شاشة"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"هل تريد بدء التسجيل؟"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"أثناء التسجيل، يمكن أن يسجّل نظام Android أي معلومات حساسة مرئية على شاشتك أو يتم تشغيلها على جهازك. ويشمل ذلك كلمات المرور ومعلومات الدفع والصور والرسائل والمقاطع الصوتية."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"تسجيل الشاشة بالكامل"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"تسجيل محتوى تطبيق واحد"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"أثناء التسجيل، يمكن لنظام Android الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"أثناء تسجيل محتوى أحد التطبيقات، يمكن لنظام Android الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"بدء التسجيل"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"تسجيل الصوت"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"صوت الجهاز"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"الصوت من جهازك، مثلاً الموسيقى والمكالمات ونغمات الرنين"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ستتمكن الخدمة التي تقدّم هذه الوظيفة من الوصول إلى كل المعلومات المرئية لك على الشاشة أو التي يتم تشغيلها على جهازك أثناء التسجيل أو الإرسال. ويشمل ذلك معلومات مثل كلمات المرور وتفاصيل الدفع والصور والرسائل والمقاطع الصوتية التي تشغِّلها."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"هل تريد بدء التسجيل أو الإرسال؟"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"هل تريد بدء التسجيل أو الإرسال باستخدام <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>؟"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"هل تريد السماح لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>بالمشاركة أو التسجيل؟"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"الشاشة بالكامل"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"تطبيق واحد"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"أثناء المشاركة أو التسجيل أو البث، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثه، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"متابعة"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"مشاركة محتوى تطبيق أو تسجيله"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"إدارة"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"السجلّ"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 5cc6d00..fe95fa0 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রীন ৰেকৰ্ডিং ছেশ্বন চলি থকা সময়ত পোৱা জাননী"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ৰেকৰ্ড কৰা আৰম্ভ কৰিবনে?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ৰেকৰ্ড কৰি থাকোঁতে, Android Systemএ আপোনাৰ স্ক্রীনত দৃশ্যমান হোৱা অথবা আপোনাৰ ডিভাইচত প্লে’ হৈ থকা যিকোনো সংবেনদশীল তথ্য কেপচাৰ কৰিব পাৰে। এইটোত পাছৱর্ড, পৰিশোধৰ তথ্য, ফট’, বার্তাসমূহ আৰু অডিঅ’ অন্তর্ভুক্ত হয়।"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"গোটেই স্ক্ৰীনখন ৰেকৰ্ড কৰক"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"এটা এপ্ ৰেকৰ্ড কৰক"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"আপুনি ৰেকৰ্ড কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"আপুনি এপ এপ্ ৰেকৰ্ড কৰাৰ সময়ত সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ৰেকৰ্ডিং আৰম্ভ কৰক"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"অডিঅ’ ৰেকৰ্ড কৰক"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ডিভাইচৰ অডিঅ’"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"সংগীত, কল আৰু ৰিংট’নসমূহৰ দৰে আপোনাৰ ডিভাইচৰ পৰা কেপচাৰ কৰিব পৰা ধ্বনি"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"এই সুবিধাটো প্ৰদান কৰা সেৱাটোৱে আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিঙৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা আটাইবোৰ তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ জৰিয়তে ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে ?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ক শ্বেয়াৰ অথবা ৰেকৰ্ড কৰিবলৈ অনুমতি দিবনে?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"সম্পূৰ্ণ স্ক্ৰীন"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"এটা একক এপ্"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"অব্যাহত ৰাখক"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"এটা এপ্ শ্বেয়াৰ অথবা ৰেকৰ্ড কৰক"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
@@ -551,7 +563,7 @@
<string name="keyboard_key_dpad_down" msgid="2110172278574325796">"তললৈ"</string>
<string name="keyboard_key_dpad_left" msgid="8329738048908755640">"বাওঁফালে"</string>
<string name="keyboard_key_dpad_right" msgid="6282105433822321767">"সোঁফালে"</string>
- <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"স্ক্ৰীণৰ মাজত"</string>
+ <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"স্ক্ৰীনৰ মাজত"</string>
<string name="keyboard_key_tab" msgid="4592772350906496730">"Tab"</string>
<string name="keyboard_key_space" msgid="6980847564173394012">"স্পেচ"</string>
<string name="keyboard_key_enter" msgid="8633362970109751646">"এণ্টাৰ"</string>
@@ -572,7 +584,7 @@
<string name="keyboard_key_numpad_template" msgid="7316338238459991821">"নামপেড <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"সংলগ্নক আঁতৰাওক"</string>
<string name="keyboard_shortcut_group_system" msgid="1583416273777875970">"ছিষ্টেম"</string>
- <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"গৃহ স্ক্ৰীণ"</string>
+ <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"গৃহ স্ক্ৰীন"</string>
<string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"শেহতীয়াসমূহ"</string>
<string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"উভতি যাওক"</string>
<string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"জাননীসমূহ"</string>
@@ -669,7 +681,7 @@
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
<string name="high_temp_title" msgid="2218333576838496100">"ফ\'নটো গৰম হ\'বলৈ ধৰিছে"</string>
<string name="high_temp_notif_message" msgid="1277346543068257549">"ফ’নটো ঠাণ্ডা হৈ থকাৰ সময়ত কিছুমান সুবিধা উপলব্ধ নহয়।\nঅধিক তথ্যৰ বাবে টিপক"</string>
- <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপোনাৰ ফ\'নটোৱে নিজে নিজে ঠাণ্ডা হ\'বলৈ স্বয়ংক্ৰিয়ভাৱে চেষ্টা কৰিব। আপুনি ফ\'নটো ব্যৱহাৰ কৰি থাকিব পাৰে কিন্তু ই লাহে লাহে চলিব পাৰে।\n\nফ\'নটো সম্পূৰ্ণভাৱে ঠাণ্ডা হোৱাৰ পিছত ই আগৰ নিচিনাকৈয়েই চলিব।"</string>
+ <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপোনাৰ ফ\'নটোৱে নিজে নিজে ঠাণ্ডা হ\'বলৈ স্বয়ংক্ৰিয়ভাৱে চেষ্টা কৰিব। আপুনি ফ\'নটো ব্যৱহাৰ কৰি থাকিব পাৰে কিন্তু ই লাহে লাহে চলিব পাৰে।\n\nফ\'নটো সম্পূৰ্ণভাৱে ঠাণ্ডা হোৱাৰ পাছত ই আগৰ নিচিনাকৈয়েই চলিব।"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"আপোনাৰ ডিভাইচটো আনপ্লাগ কৰক"</string>
<string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"আপোনাৰ ডিভাইচটো চাৰ্জিং প’ৰ্টৰ ওচৰত গৰম হৈছে। যদি এইটো কোনো চার্জাৰ অথবা ইউএছবিৰ সহায়ক সামগ্ৰীৰ সৈতে সংযুক্ত হৈ আছে, ইয়াক আনপ্লাগ কৰক আৰু কে’বলডালো গৰম হ\'ব পাৰে, গতিকে যত্ন লওক।"</string>
@@ -698,7 +710,7 @@
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> চলি আছে"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"এপ্টো ইনষ্ট\'ল নকৰাকৈ খোলা হৈছে।"</string>
- <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ইনষ্ট\'ল নকৰাকৈয়েই এপটো খোলা হৈছে। অধিক জানিবলৈ টিপক।"</string>
+ <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ইনষ্ট\'ল নকৰাকৈয়েই এপ্টো খোলা হৈছে। অধিক জানিবলৈ টিপক।"</string>
<string name="app_info" msgid="5153758994129963243">"এপৰ তথ্য"</string>
<string name="go_to_web" msgid="636673528981366511">"ব্ৰাউজাৰলৈ যাওক"</string>
<string name="mobile_data" msgid="4564407557775397216">"ম’বাইল ডেটা"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index f91ad01..0beb0ba 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekranın video çəkimi ərzində silinməyən bildiriş"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Yazmağa başlanılsın?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Ekranda görünən və ya cihazda oxudulan şəxsi məlumat (parol, bank hesabı, mesaj, fotoşəkil və sair) videoyazıya düşə bilər."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Bütün ekranı qeydə alın"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Bir tətbiqi qeydə alın"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Qeydə aldığınız zaman Android ekranınızda görünən və ya cihazınızda oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Tətbiqi qeydə aldığınız zaman Android həmin tətbiqdə göstərilən və ya oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Qeydə almağa başlayın"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Audio yazın"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Cihaz audiosu"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Cihazınızdan gələn musiqi, zənglər və zəng melodiyaları kimi səslər"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu funksiyanı təmin edən xidmətin yazma və ya yayım zamanı ekranda görünən və ya cihazdan oxudulan bütün bilgilərə girişi olacaq. Buraya parollar, ödəniş detalları, fotolar, mesajlar və oxudulan audio kimi məlumatlar daxildir."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Yazma və ya yayımlama başladılsın?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ilə yazma və ya yayımlama başladılsın?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tətbiqinə paylaşmaq və ya qeydə almaq üçün icazə verilsin?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Bütün ekran"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Vahid tətbiq"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Paylaşdığınız, qeydə aldığınız və ya yayımladığınız zaman <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tətbiqi ekranınızda görünən və ya cihazınızda oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Paylaşdığınız, qeydə aldığınız və ya yayımladığınız zaman <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tətbiqi həmin tətbiqdə göstərilən və ya oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Davam edin"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Tətbiqi paylaşın və ya qeydə alın"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"İdarə edin"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Tarixçə"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 41633f7..5be177f 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Obaveštenje o sesiji snimanja ekrana je aktivno"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Želite da započnete snimanje?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Tokom snimanja Android sistem može da snimi osetljive informacije koje su vidljive na ekranu ili koje se puštaju na uređaju. To obuhvata lozinke, informacije o plaćanju, slike, poruke i zvuk."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Snimaj ceo ekran"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Snimaj jednu aplikaciju"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju dok snimate. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Kada snimate aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Započni snimanje"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Snimaj zvuk"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk uređaja"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvuk sa uređaja, na primer, muzika, pozivi i melodije zvona"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Usluga koja pruža ovu funkciju će imati pristup svim informacijama koje se prikazuju na ekranu ili reprodukuju sa uređaja tokom snimanja ili prebacivanja. To obuhvata informacije poput lozinki, informacija o plaćanju, slika, poruka i zvuka koji puštate."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Želite da počnete snimanje ili prebacivanje?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Želite da počnete snimanje ili prebacivanje pomoću aplikacije <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Želite da dozvolite deljenje i snimanje za <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ceo ekran"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Jedna aplikacija"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kada delite, snimate ili prebacujete, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Delite ili snimite aplikaciju"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 5c61919..881b59f 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Бягучае апавяшчэнне для сеанса запісу экрана"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Пачаць запіс?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Падчас запісу сістэма Android можа збіраць канфідэнцыяльную інфармацыю, якая адлюстроўваецца на экране вашай прылады ці прайграецца на ёй. Гэта могуць быць паролі, плацежная інфармацыя, фота, паведамленні і аўдыяданыя."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Запісаць змесціва ўсяго экрана"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Запісаць змесціва праграмы"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Калі адбываецца запіс, Android мае доступ да ўсяго змесціва, якое паказваецца на экране ці прайграецца на прыладзе. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Калі адбываецца запіс змесціва праграмы, Android мае доступ да ўсяго змесціва, якое паказваецца ці прайграецца ў праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Пачаць запіс"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Запісаць аўдыя"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Аўдыя з прылады"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Гук на вашай прыладзе, напрыклад музыка, выклікі і рынгтоны"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Падчас запісу ці трансляцыі служба, якая забяспечвае работу гэтай функцыі, будзе мець доступ да ўсёй інфармацыі, адлюстраванай на экране вашай прылады, ці той, якая праз яе прайграецца. Гэта інфармацыя ўключае паролі, звесткі пра аплату, фота, паведамленні і аўдыя, якое вы прайграяце."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Пачаць запіс або трансляцыю?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Пачаць запіс або трансляцыю з дапамогай праграмы \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\"?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Дазволіць праграме \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" абагульваць ці запісваць змесціва?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Увесь экран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Адна праграма"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Калі пачынаецца абагульванне, запіс ці трансляцыя, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> атрымлівае доступ да ўсяго змесціва, якое паказваецца на экране ці прайграецца на прыладзе. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Калі пачынаецца абагульванне, запіс ці трансляцыя змесціва праграмы, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> атрымлівае доступ да ўсяго змесціва, якое паказваецца ці прайграецца ў праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далей"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Абагульванне або запіс праграмы"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Кіраваць"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Гісторыя"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index c95e86e..cbc7c8a 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущо известие за сесия за записване на екрана"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Да се стартира ли записът?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"По време на записване системата Android може да запише и поверителна информация, която е показана на екрана или възпроизвеждана на устройството ви. Това включва пароли, данни за плащане, снимки, съобщения и аудио."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записване на целия екран"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записване на едно приложение"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Когато записвате, Android има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Когато записвате приложение, Android има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Стартиране на записа"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Записване на звук"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Аудио от устройството"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук от устройството ви, като например музика, обаждания и мелодии"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Услугата, предоставяща тази функция, ще има достъп до цялата информация, която е видима на екрана или възпроизвеждана от устройството ви по време на записване или предаване. Това включва различна информация, като например пароли, данни за плащане, снимки, съобщения и възпроизвеждано аудио."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Да се стартира ли записване или предаване?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Да се стартира ли записване или предаване чрез <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Разрешавате ли на <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> да споделя и записва?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Цял екран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Едно приложение"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Напред"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделяне или записване на приложение"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Управление"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 0f7ddfc..44fb0e6 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রিন রেকর্ডিং সেশন চলার বিজ্ঞপ্তি"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"রেকর্ডিং শুরু করবেন?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"রেকর্ড করার সময়, আপনার স্ক্রিনে দেখানো বা ডিভাইসে চালানো যেকোনও ধরনের সংবেদনশীল তথ্য Android সিস্টেম ক্যাপচার করতে পারে। এর মধ্যে পাসওয়ার্ড, পেমেন্টের তথ্য, ফটো, মেসেজ এবং অডিও সম্পর্কিত তথ্য থাকে।"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"সম্পূর্ণ স্ক্রিন রেকর্ড করুন"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"একটিমাত্র অ্যাপ রেকর্ড করুন"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"আপনার রেকর্ড করার সময়, স্ক্রিনে দেখা যায় বা ডিভাইসে খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি Android-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"কোনও অ্যাপ আপনার রেকর্ড করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি Android-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"রেকর্ড করা শুরু করুন"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"অডিও রেকর্ড করুন"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ডিভাইস অডিও"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"মিউজিক, কল এবং রিংটোনগুলির মতো আপনার ডিভাইস থেকে সাউন্ড"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"রেকর্ড করা বা কাস্টিং করার সময় আপনার স্ক্রিনে দেখানো বা ডিভাইসে চালানো হয়েছে এমন সমস্ত তথ্যের অ্যাক্সেস এই ফাংশন প্রদানকারী পরিষেবার কাছে থাকবে। এর মধ্যে আপনার পাসওয়ার্ড, পেমেন্টের বিবরণ, ফটো, মেসেজ এবং যে অডিও আপনি চালান সেগুলি সম্পর্কিত তথ্য রয়েছে।"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"রেকর্ড অথবা কাস্টিং শুরু করতে চান?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> দিয়ে রেকর্ড করা বা কাস্টিং শুরু করবেন?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-কে শেয়ার বা রেকর্ড করার অনুমতি দেবেন?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"সম্পূর্ণ স্ক্রিন"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"একটি মাত্র অ্যাপ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, স্ক্রিনে দেখা যায় বা ডিভাইসে খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"কোনও অ্যাপ আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"চালিয়ে যান"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"অ্যাপ শেয়ার বা রেকর্ড করা"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"সবকিছু সাফ করুন"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"পরিচালনা করুন"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 19bbca6..ca0ff52 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Obavještenje za sesiju snimanja ekrana je u toku"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Započeti snimanje?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Prilikom snimanja, Android sistem može snimiti sve osjetljive informacije koje su vidljive na vašem ekranu ili koje reproducirate na uređaju. To uključuje lozinke, informacije za plaćanje, fotografije, poruke i zvuk."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Snimaj cijeli ekran"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Snimaj jednu aplikaciju"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Dok snimate, Android ima pristup svemu što se vidi na ekranu ili reproducira na uređaju. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Dok snimate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Započni snimanje"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Snimi zvučni zapis"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk na uređaju"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvuk s vašeg uređaja, naprimjer muzika, pozivi i melodije zvona"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Usluga koja pruža ovu funkciju će imati pristup svim informacijama koje se prikazuju na ekranu ili koje se reproduciraju s vašeg uređaja za vrijeme snimanja ili emitiranja. To obuhvata informacije kao što su lozinke, detalji o plaćanju, fotografije, poruke i zvuk koji reproducirate."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Započeti snimanje ili emitiranje?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Započeti snimanje ili emitiranje s aplikacijom <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Dozvoliti aplikaciji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> da dijeli ili snima?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Cijeli ekran"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Jedna aplikacija"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kada dijelite, snimate ili emitirate, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se vidi na ekranu ili što se reproducira na uređaju. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kada aplikaciju dijelite, snimate ili emitirate, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijelite ili snimite aplikaciju"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historija"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 839fa89..172b14d 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificació en curs d\'una sessió de gravació de la pantalla"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vols iniciar la gravació?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durant la gravació, el sistema Android pot capturar qualsevol informació sensible que es mostri a la pantalla o que es reprodueixi al dispositiu. Això inclou contrasenyes, informació de pagament, fotos, missatges i àudio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grava la pantalla completa"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grava una sola aplicació"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mentre graves, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mentre graves una aplicació, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Inicia la gravació"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Grava l\'àudio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Àudio del dispositiu"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"So del dispositiu, com ara música, trucades i sons de trucada"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servei que ofereix aquesta funció tindrà accés a tota la informació visible a la teva pantalla o que es reprodueix al dispositiu mentre graves o emets contingut, com ara contrasenyes, detalls dels pagaments, fotos, missatges i àudio que reprodueixis."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vols començar a gravar o emetre contingut?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vols començar a gravar o emetre contingut amb <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vols permetre que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparteixi o gravi?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tota la pantalla"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola aplicació"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Comparteix o grava una aplicació"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestiona"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index aa6d812..034fe9a 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Trvalé oznámení o relaci nahrávání"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Spustit nahrávání?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Při nahrávání může systém Android zaznamenávat citlivé údaje, které jsou viditelné na obrazovce nebo které jsou přehrávány na zařízení. Týká se to hesel, údajů o platbě, fotek, zpráv a zvuků."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Nahrát celou obrazovku"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Nahrát samostatnou aplikaci"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Během nahrávání má Android přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Během nahrávání aplikace má Android přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Spustit nahrávání"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Nahrávat zvuk"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk zařízení"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvuk ze zařízení, například hudba, hovory a vyzvánění"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Služba, která tuto funkci poskytuje, bude mít při nahrávání nebo odesílání přístup ke všem informacím, které jsou viditelné na obrazovce nebo které jsou přehrávány ze zařízení. Týká se to i hesel, údajů o platbě, fotek, zpráv a přehrávaných zvuků."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Začít nahrávat nebo odesílat?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Začít nahrávat nebo odesílat s aplikací <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Povolit aplikaci <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sdílení nebo nahrávání?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Celá obrazovka"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Samostatná aplikace"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Když sdílíte, nahráváte nebo odesíláte obsah, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Když sdílíte, nahráváte nebo odesíláte aplikaci, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovat"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Sdílení nebo nahrání aplikace"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Spravovat"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historie"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index c4afde1..03d12af 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Konstant notifikation om skærmoptagelse"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vil du starte optagelse?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Når du optager, kan Android-systemet registrere følsomme oplysninger, der er synlige på din skærm, eller som afspilles på din enhed. Dette inkluderer adgangskoder, betalingsoplysninger, fotos, meddelelser og lyd."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Optag hele skærmen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Optag én app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mens du optager, har Android adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mens du optager en app, har Android adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Start optagelse"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Optag lyd"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Enhedslyd"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Lyd fra din enhed såsom musik, opkald og ringetoner"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Tjenesten, der tilbyder denne funktion, får adgang til alle de oplysninger, der er synlige på din skærm, eller som afspilles på din enhed, når du optager eller caster. Dette omfatter oplysninger som f.eks. adgangskoder, betalingsoplysninger, billeder, beskeder og afspillet lyd."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vil du begynde at optage eller caste?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vil du begynde at optage eller caste via <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vil du tillade, at <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> deler eller optager?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hele skærmen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Én app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Når du deler, optager eller caster, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, optager eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsæt"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller optag en app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index fe3582a..1d13c87 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Fortlaufende Benachrichtigung für eine Bildschirmaufzeichnung"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Aufzeichnung starten?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Beim Aufnehmen kann das Android-System vertrauliche Informationen erfassen, die auf deinem Bildschirm angezeigt oder von deinem Gerät wiedergegeben werden. Das können Passwörter, Zahlungsinformationen, Fotos, Nachrichten und Audioinhalte sein."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gesamten Bildschirm aufnehmen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Eine einzelne App aufnehmen"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Während der Aufnahme hat Android Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Während der Aufnahme einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Aufnahme starten"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Audio aufnehmen"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio des Geräts"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Audioinhalte auf deinem Gerät, wie Musik, Anrufe und Klingeltöne"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Der Anbieter dieser App erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise angezeigte Passwörter, Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aufnahme oder Stream starten?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Aufnehmen oder Streamen mit der App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" starten?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Zulassen, dass <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Inhalte teilt oder aufnimmt?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Gesamter Bildschirm"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Eine einzelne App"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Beim Teilen, Aufnehmen oder Übertragen hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Beim Teilen, Aufnehmen oder Übertragen einer App hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Weiter"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App teilen oder aufnehmen"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Verwalten"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Verlauf"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 028f681..acaeddc 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ειδοποίηση σε εξέλιξη για μια περίοδο λειτουργίας εγγραφής οθόνης"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Έναρξη εγγραφής;"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Κατά την εγγραφή, το σύστημα Android μπορεί να καταγράψει τυχόν ευαίσθητες πληροφορίες που είναι ορατές στην οθόνη ή αναπαράγονται στη συσκευή σας. Σε αυτές περιλαμβάνονται οι κωδικοί πρόσβασης, οι πληροφορίες πληρωμής, οι φωτογραφίες, τα μηνύματα και ο ήχος."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Εγγραφή ολόκληρης οθόνης"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Εγγραφή μίας εφαρμογής"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Όταν κάνετε εγγραφή, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό στην οθόνη ή αναπαράγεται στη συσκευή σας. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Όταν κάνετε εγγραφή μιας εφαρμογής, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Έναρξη εγγραφής"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Ηχογράφηση"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Ήχος συσκευής"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Ήχος από τη συσκευή σας, όπως μουσική, κλήσεις και ήχοι κλήσης"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Η υπηρεσία που παρέχει αυτήν τη λειτουργία θα έχει πρόσβαση σε όλες τις πληροφορίες που εμφανίζονται στην οθόνη σας ή που αναπαράγονται από τη συσκευή σας κατά την εγγραφή ή τη μετάδοση. Αυτό περιλαμβάνει πληροφορίες όπως κωδικούς πρόσβασης, στοιχεία πληρωμής, φωτογραφίες, μηνύματα και ήχο που αναπαράγετε."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Έναρξη εγγραφής ή μετάδοσης;"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Έναρξη εγγραφής ή μετάδοσης με <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>;"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Να επιτρέπεται στην εφαρμογή <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> η κοινοποίηση ή η εγγραφή;"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ολόκληρη την οθόνη"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Μία εφαρμογή"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Όταν κάνετε κοινοποίηση, εγγραφή ή μετάδοση, η εφαρμογή <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> έχει πρόσβαση σε οτιδήποτε είναι ορατό στην οθόνη σας ή αναπαράγεται στη συσκευή σας. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Όταν κάνετε κοινοποίηση, εγγραφή ή μετάδοση μιας εφαρμογής, η εφαρμογή <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Συνέχεια"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Κοινοποίηση ή εγγραφή εφαρμογής"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Διαχείριση"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ιστορικό"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index a067d96..7c58eab 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"While recording, the Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Record entire screen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Record a single app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"While you\'re recording an app, Android has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Start recording"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Allow <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> to share or record?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Entire screen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"A single app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"When you\'re sharing, recording or casting, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 30183eb..c05b90e 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"While recording, the Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Record entire screen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Record a single app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"While you\'re recording an app, Android has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Start recording"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Allow <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> to share or record?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Entire screen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"A single app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"When you\'re sharing, recording or casting, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index a067d96..7c58eab 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"While recording, the Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Record entire screen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Record a single app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"While you\'re recording an app, Android has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Start recording"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Allow <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> to share or record?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Entire screen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"A single app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"When you\'re sharing, recording or casting, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index a067d96..7c58eab 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"While recording, the Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Record entire screen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Record a single app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"While you\'re recording an app, Android has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Start recording"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Allow <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> to share or record?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Entire screen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"A single app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"When you\'re sharing, recording or casting, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index c09d197..9fc2374 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Start Recording?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages, and audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Record entire screen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Record a single app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"While you\'re recording an app, Android has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Start recording"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls, and ringtones"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages, and audio that you play."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Allow <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> to share or record?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Entire screen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"A single app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"When you\'re sharing, recording, or casting, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording, or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index f4f4999..2455ed4 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación constante para una sesión de grabación de pantalla"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"¿Comenzar a grabar?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durante la grabación, el sistema Android puede capturar cualquier información sensible que aparezca en la pantalla o que se reproduzca en el dispositivo. Se incluyen contraseñas, información de pago, fotos, mensajes y audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grabar toda la pantalla"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grabar una sola app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mientras grabes, Android podrá acceder a todo el contenido visible en la pantalla o que reproduzcas en el dispositivo. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mientras grabes una app, Android podrá acceder a todo el contenido que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar grabación"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Grabar audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio del dispositivo"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sonidos del dispositivo, como música, llamadas y tonos"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servicio que brinda esta función tendrá acceso a toda la información que sea visible en la pantalla o que reproduzcas en tu dispositivo durante una grabación o transmisión. Se incluyen las contraseñas, los detalles del pago, las fotos, los mensajes y el audio que reproduzcas."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"¿Deseas comenzar a grabar o transmitir contenido?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"¿Deseas iniciar una grabación o transmisión con <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"¿Quieres permitir que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparta o grabe contenido?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Pantalla completa"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Cuando compartas, grabes o transmitas contenido, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo aquel que sea visible en la pantalla o que reproduzcas en el dispositivo. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o transmitas una app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo el contenido que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index d6db89d..0df5f16 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación continua de una sesión de grabación de la pantalla"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"¿Empezar a grabar?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Mientras grabas, el sistema Android puede capturar información sensible que se muestre o se reproduzca en tu dispositivo, como contraseñas, datos de pago, fotos, mensajes y audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grabar toda la pantalla"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grabar una sola aplicación"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mientras grabes contenido, Android podrá acceder a todo lo que sea visible en tu pantalla o que reproduzcas en tu dispositivo. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mientras grabes una aplicación, Android podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar grabación"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Grabar audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio del dispositivo"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sonido de tu dispositivo, como música, llamadas y tonos de llamada"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servicio que ofrece esta función tendrá acceso a toda la información que se muestre en la pantalla o se reproduzca en el dispositivo mientras grabas o envías contenido, incluyendo contraseñas, detalles de pagos, fotos, mensajes y audios que reproduzcas."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"¿Empezar a grabar o enviar contenido?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"¿Iniciar grabación o el envío de contenido en <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"¿Permitir que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparta o grabe contenido?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Toda la pantalla"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola aplicación"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Cuando compartas, grabes o envíes contenido, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que sea visible en tu pantalla o que reproduzcas en tu dispositivo. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o envíes una aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una aplicación"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestionar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 3edd02d..d42b397 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pooleli märguanne ekraanikuva salvestamise seansi puhul"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Kas alustada salvestamist?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Salvestamise ajal võib Androidi süsteem jäädvustada tundlikku teavet, mis on ekraanikuval nähtav või mida seadmes esitatakse. See hõlmab paroole, makseteavet, fotosid, sõnumeid ja heli."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Kogu ekraanikuva salvestamine"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ühe rakenduse salvestamine"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Salvestamise ajal on Androidil juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Rakenduse salvestamise ajal on Androidil juurdepääs kõigele, mis on selles rakenduses nähtaval või mida selles esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Alusta salvestamist"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Heli salvestamine"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Seadme heli"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Seadmest pärinev heli, nt muusika, kõned ja helinad"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Seda funktsiooni pakkuv teenus saab juurdepääsu kogu teabele, mis on teie ekraanikuval nähtav või mida seadmes salvestamise või ülekande ajal esitatakse. See hõlmab teavet, nagu paroolid, maksete üksikasjad, fotod, sõnumid ja esitatav heli."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Kas alustada salvestamist või ülekannet?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Kas alustada rakendusega <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> salvestamist või ülekannet?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Kas lubada rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> jagada või salvestada?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Kogu ekraanikuva"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Üks rakendus"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kui jagate, salvestate või kannate üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kui jagate, salvestate või kannate rakendust üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jätka"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Rakenduse jagamine või salvestamine"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Haldamine"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ajalugu"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 6724aa5..fc93a80 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pantailaren grabaketa-saioaren jakinarazpen jarraitua"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Grabatzen hasi nahi duzu?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Pantaila grabatzen duzun bitartean, baliteke Android sistemak pantailan agertzen den edo gailuak erreproduzitzen duen kontuzko informazioa grabatzea; besteak beste, pasahitzak, ordainketa-informazioa, argazkiak, mezuak eta audioa."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grabatu pantaila osoko edukia"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grabatu aplikazio bakar bat"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Grabatzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztirako sarbidea du Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Aplikazio bat grabatzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Hasi grabatzen"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Grabatu audioa"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Gailuaren audioa"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Gailuko soinuak; adibidez, musika, deiak eta tonuak"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Zerbait grabatzen edo igortzen duzunean, pantailan ikus daitekeen edo gailuak erreproduzitzen duen informazio guztia atzitu ahalko du funtzio hori eskaintzen duen zerbitzuak; besteak beste, pasahitzak, ordainketen xehetasunak, argazkiak, mezuak eta erreproduzitzen dituzun audioak."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Grabatzen edo igortzen hasi nahi duzu?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioarekin grabatzen edo igortzen hasi nahi duzu?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Edukia partekatu edo grabatzeko baimena eman nahi diozu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioari?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Pantaila osoa"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Aplikazio bakar bat"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Egin aurrera"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partekatu edo grabatu aplikazioak"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kudeatu"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index ce8f0ef..0a1007e 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"اعلان درحال انجام برای جلسه ضبط صفحهنمایش"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ضبط شروع شود؟"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"هنگام ضبط، «سیستم Android» میتواند هر اطلاعات حساسی را که روی صفحهنمایش شما نشان داده میشود یا روی دستگاه شما پخش میشود ضبط کند. این شامل گذرواژهها، اطلاعات پرداخت، عکسها، پیامها، و صدا میشود."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ضبط کل صفحه"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ضبط یک برنامه"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"درحین ضبط کردن، Android به همه محتوایی که در صفحهتان نمایان است یا در دستگاهتان پخش میشود دسترسی دارد. بنابراین مراقب گذرواژهها، جزئیات پرداخت، پیامها، یا دیگر اطلاعات حساس باشید."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"درحین ضبط برنامه، Android به همه محتوایی که در آن برنامه نمایان است یا پخش میشود دسترسی دارد. بنابراین مراقب گذرواژهها، جزئیات پرداخت، پیامها، یا دیگر اطلاعات حساس باشید."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"شروع ضبط"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ضبط صدا"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"صدای دریافتی از دستگاه"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"صدای دریافتی از دستگاه، مثل موسیقی، تماس، و آهنگ زنگ"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"سرویس ارائهدهنده این عملکرد به همه اطلاعاتی که روی صفحهنمایش قابلمشاهد است و هنگام ضبط کردن یا پخش محتوا از دستگاهتان پخش میشود دسترسی خواهد داشت. این شامل اطلاعاتی مانند گذرواژهها، جزئیات پرداخت، عکسها، پیامها، و صداهایی که پخش میکنید میشود."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ضبط یا پخش محتوا شروع شود؟"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"ضبط یا پخش محتوا با <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> شروع شود؟"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"به <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> اجازه همرسانی یا ضبط داده شود؟"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"کل صفحه"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"یک برنامه"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"وقتی درحال همرسانی، ضبط، یا پخش محتوا هستید، <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> به همه محتوایی که در صفحهتان نمایان است یا در دستگاهتان پخش میشود دسترسی دارد. بنابراین مراقب گذرواژهها، جزئیات پرداخت، پیامها، یا دیگر اطلاعات حساس باشید."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"وقتی درحال همرسانی، ضبط، یا پخش محتوای برنامهای هستید، <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> به همه محتوایی که در آن برنامه نمایان است یا پخش میشود دسترسی دارد. بنابراین مراقب گذرواژهها، جزئیات پرداخت، پیامها، یا دیگر اطلاعات حساس باشید."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ادامه"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"همرسانی یا ضبط برنامه"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"مدیریت"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"سابقه"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 9738b05..0882342 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pysyvä ilmoitus näytön tallentamisesta"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Aloitetaanko tallennus?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Tallennuksen aikana Android-järjestelmä voi tallentaa mitä tahansa näytöllä näkyvää tai laitteen toistamaa arkaluontoista tietoa. Näitä tietoja ovat esimerkiksi salasanat, maksutiedot, kuvat, viestit ja audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Tallenna koko näyttö"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Tallenna yhtä sovellusta"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Kun tallennat, Android saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Kun tallennat sovellusta, Android saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Aloita tallennus"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Tallenna audiota"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Laitteen audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Musiikki, puhelut, soittoäänet ja muut äänet laitteesta"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ominaisuuden tarjoavalla palvelulla on pääsy kaikkiin näytölläsi näkyviin tietoihin ja tietoihin laitteesi toistamasta sisällöstä tallennuksen tai striimauksen aikana. Näitä tietoja ovat esimerkiksi salasanat, maksutiedot, kuvat, viestit ja toistettava audiosisältö."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aloitetaanko tallentaminen tai striimaus?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Haluatko, että <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aloittaa tallennuksen tai striimauksen?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Sallitaanko, että <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> jaetaan tai tallennetaan?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Koko näyttö"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Yksittäinen sovellus"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kun jaat, tallennat tai striimaat, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kun jaat, tallennat tai striimaat sovellusta, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jatka"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Jaa sovellus tai tallenna sen sisältöä"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Muuta asetuksia"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index bbeb055..509cbc9 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement d\'écran"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Commencer l\'enregistrement?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durant l\'enregistrement, le système Android peut capturer de l\'information confidentielle qui s\'affiche sur votre écran ou qui joue sur votre appareil. Cela comprend les mots de passe, les renseignements sur le paiement, les photos, les messages et l\'audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Enregistrer l\'écran entier"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Enregistrer une seule appli"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Pendant l\'enregistrement, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Pendant l\'enregistrement, Android a accès à tout ce qui est affiché ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Commencer l\'enregistrement"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Enregistrer des fichiers audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio de l\'appareil"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sons de l\'appareil comme la musique, les appels et les sonneries"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Le service offrant cette fonction aura accès à toute l\'information qui est visible sur votre écran ou sur ce qui joue sur votre appareil durant l\'enregistrement ou la diffusion. Cela comprend des renseignements comme les mots de passe, les détails du paiement, les photos, les messages et le contenu audio que vous faites jouer."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Commencer à enregistrer ou à diffuser?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Commencer à enregistrer ou à diffuser avec <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Autoriser <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> à partager ou à enregistrer?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Écran entier"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Une seule application"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Lorsque vous partagez, enregistrez ou diffusez, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou diffusez une application, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est affiché ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une application"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 86e8f9e..a80b037 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement de l\'écran"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Démarrer l\'enregistrement ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durant l\'enregistrement, le système Android peut capturer les infos sensibles affichées à l\'écran ou lues sur votre appareil. Cela inclut les mots de passe, les infos de paiement, les photos, les messages et l\'audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Enregistrer tout l\'écran"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Enregistrer une seule appli"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Lorsque vous enregistrez une appli, Android à accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Lorsque vous enregistrez une appli, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Lancer l\'enregistrement"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Enregistrer l\'audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio de l\'appareil"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Son provenant de l\'appareil (musique, appels et sonneries, etc.)"</string>
@@ -337,11 +342,11 @@
<string name="interruption_level_none_twoline" msgid="8579382742855486372">"Aucune\ninterruption"</string>
<string name="interruption_level_priority_twoline" msgid="8523482736582498083">"Priorité\nuniquement"</string>
<string name="interruption_level_alarms_twoline" msgid="2045067991335708767">"Alarmes\nuniquement"</string>
- <string name="keyguard_indication_charging_time_wireless" msgid="577856646141738675">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge sans fil • Chargé dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
- <string name="keyguard_indication_charging_time" msgid="6492711711891071502">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • En charge • Chargé dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
- <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge rapide • Chargé dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
- <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge lente • Chargé dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
- <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge • Chargé dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_charging_time_wireless" msgid="577856646141738675">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge sans fil • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_charging_time" msgid="6492711711891071502">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • En charge • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge rapide • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge lente • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Le service qui fournit cette fonction aura accès à toutes les infos visibles sur votre écran ou lues depuis votre appareil lors d\'un enregistrement ou de la diffusion d\'un contenu. Cela comprend, entre autres, vos mots de passe, les détails de vos paiements, vos photos, vos messages ou les contenus audio que vous écoutez."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Démarrer l\'enregistrement ou la diffusion ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Démarrer l\'enregistrement ou la diffusion avec <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Autoriser <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> à partager ou enregistrer ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tout l\'écran"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Une seule appli"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Lorsque vous partagez, enregistrez ou castez, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou castez une appli, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une appli"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 7fd464e..bb3894a 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación en curso sobre unha sesión de gravación de pantalla"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Queres iniciar a gravación?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durante a gravación, o sistema Android pode captar información confidencial que apareza na pantalla ou se reproduza no dispositivo, como contrasinais, información de pago, fotos, mensaxes e audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gravar pantalla completa"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Gravar unha soa aplicación"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Durante a gravación, Android ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Cando gravas unha aplicación, Android ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar gravación"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Gravar audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio do dispositivo"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Son do dispositivo (por exemplo, música, chamadas e tons de chamada)"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"O servizo que proporciona esta función terá acceso a toda a información visible na pantalla ou reproducida desde o teu dispositivo mentres graves ou emitas contido. Isto inclúe información como contrasinais, detalles de pago, fotos, mensaxes e o audio que reproduzas."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Queres iniciar a gravación ou a emisión?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Queres comezar a gravar ou emitir contido con <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Queres permitir que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparta ou grave contido?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Pantalla completa"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Unha soa aplicación"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Cando compartes, gravas ou emites contido, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cando compartes, gravas ou emites unha aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir ou gravar unha aplicación"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todas"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Xestionar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 1562101..fd14277 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"સ્ક્રીન રેકોર્ડિંગ સત્ર માટે ચાલુ નોટિફિકેશન"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"રેકોર્ડિંગ શરૂ કરીએ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"રેકોર્ડ કરતી વખતે, Android System તમારી સ્ક્રીન પર દેખાતી હોય અથવા તમારા ડિવાઇસ પર ચલાવવામાં આવતી હોય તેવી કોઈપણ સંવેદનશીલ માહિતીને કૅપ્ચર કરી શકે છે. આમાં પાસવર્ડ, ચુકવણીની માહિતી, ફોટા, મેસેજ અને ઑડિયોનો સમાવેશ થાય છે."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"પૂર્ણ સ્ક્રીન રેકોર્ડ કરો"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"કોઈ એક ઍપ રેકોર્ડ કરો"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"જ્યારે તમે રેકોર્ડ કરી રહ્યાં હો, ત્યારે તમારી સ્ક્રીન પર દેખાતી હોય કે તમારા ડિવાઇસ પર ચલાવવામાં આવતી હોય તેવી કોઈપણ વસ્તુનો ઍક્સેસ Android ધરાવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"જ્યારે તમે કોઈ ઍપ રેકોર્ડ કરી રહ્યાં હો, ત્યારે તે ઍપ પર બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી કોઈપણ વસ્તુનો ઍક્સેસ Android ધરાવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"રેકોર્ડિંગ શરૂ કરો"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ઑડિયો રેકોર્ડ કરો"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ડિવાઇસનો ઑડિયો"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"મ્યુઝિક, કૉલ અને રિંગટોન જેવા તમારા ડિવાઇસના સાઉન્ડ"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"રેકોર્ડ અથવા કાસ્ટ કરતી વખતે, આ સુવિધા આપતી સેવાને તમારી સ્ક્રીન પર દેખાતી હોય અથવા તમારા ડિવાઇસ પર ચલાવવામાં આવતી હોય તેવી બધી માહિતીનો ઍક્સેસ હશે. આમાં પાસવર્ડ, ચુકવણીની વિગતો, ફોટા, મેસેજ અને તમે ચલાવો છો તે ઑડિયો જેવી માહિતીનો સમાવેશ થાય છે."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"શું રેકોર્ડ અથવા કાસ્ટ કરવાનું શરૂ કરીએ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> વડે રેકોર્ડ અથવા કાસ્ટ કરવાનું શરૂ કરીએ?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"શુ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ને શેર અથવા રેકોર્ડ કરવાની મંજૂરી આપીએ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"પૂર્ણ સ્ક્રીન"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"કોઈ એક ઍપ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"જ્યારે તમે શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તમારી સ્ક્રીન પર દેખાતી હોય કે તમારા ડિવાઇસ પર ચલાવવામાં આવતી હોય તેવી કોઈપણ વસ્તુનો ઍક્સેસ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ધરાવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"જ્યારે તમે કોઈ ઍપ શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તે ઍપ પર બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી કોઈપણ વસ્તુનો ઍક્સેસ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ધરાવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ચાલુ રાખો"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"કોઈ ઍપ શેર કરો અથવા રેકોર્ડ કરો"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"મેનેજ કરો"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ઇતિહાસ"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 5664c6a..9dc6f67 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रिकॉर्ड सेशन के लिए जारी सूचना"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"क्या आपको रिकॉर्डिंग शुरू करनी है?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"रिकॉर्ड करते समय, Android सिस्टम आपकी स्क्रीन पर दिखने वाली या चलाई जाने वाली संवेदनशील जानकारी को कैप्चर कर सकता है. इसमें पासवर्ड, पैसे चुकाने से जुड़ी जानकारी, फ़ोटो, मैसेज, और ऑडियो शामिल हैं."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"पूरी स्क्रीन रिकॉर्ड करें"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"सिर्फ़ एक ऐप्लिकेशन रिकॉर्ड करें"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Android के पास, रिकॉर्ड करने के दौरान, स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"कोई ऐप्लिकेशन रिकॉर्ड करने के दौरान, Android के पास उस पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"रिकॉर्ड करना शुरू करें"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ऑडियो रिकॉर्ड करें"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिवाइस ऑडियो"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"आपके डिवाइस से आने वाली आवाज़ जैसे कि संगीत, कॉल, और रिंगटोन"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"इस फ़ंक्शन को उपलब्ध कराने वाली सेवा, रिकॉर्ड या कास्ट करते समय, आपकी स्क्रीन पर दिखने वाली या चलाई जाने वाली जानकारी को ऐक्सेस कर सकती है. इसमें पासवर्ड, पैसे चुकाने से जुड़ी जानकारी, फ़ोटो, मैसेज, और चलाए जाने वाले ऑडियो शामिल हैं."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रिकॉर्डिंग या कास्ट करना शुरू करें?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> का इस्तेमाल करके रिकॉर्ड और कास्ट करना शुरू करें?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> को शेयर या रिकॉर्ड करने की अनुमति दें?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"पूरी स्क्रीन"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"सिर्फ़ एक ऐप्लिकेशन"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी रखें"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ऐप्लिकेशन शेयर करें या उसकी रिकॉर्डिंग करें"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"मैनेज करें"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 1a9768c..8305da8 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Tekuća obavijest za sesiju snimanja zaslona"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Želite li započeti snimanje?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Za vrijeme snimanja sustav Android može snimiti osjetljive podatke koji su vidljivi na vašem zaslonu ili se reproduciraju na vašem uređaju. To uključuje zaporke, podatke o plaćanju, fotografije, poruke i zvuk."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Snimi cijeli zaslon"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Snimi jednu aplikaciju"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Dok snimate, Android ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Dok snimate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Započni snimanje"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Snimanje zvuka"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk na uređaju"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvuk s vašeg uređaja, poput glazbe, poziva i melodija zvona"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Usluga koja pruža ovu funkcionalnost imat će pristup svim podacima koji su vidljivi na vašem zaslonu ili koji se reproduciraju s vašeg uređaja tijekom snimanja ili emitiranja. To uključuje podatke kao što su zaporke, podaci o plaćanju, fotografije, poruke i audiozapisi koje reproducirate."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Želite li započeti snimanje ili emitiranje?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Želite li započeti snimanje ili emitiranje pomoću aplikacije <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Želite li dopustiti aplikaciji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> da dijeli ili snima?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Cijeli zaslon"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Jedna aplikacija"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kad dijelite, snimate ili emitirate, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kad dijelite, snimate ili emitirate aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijeljenje ili snimanje pomoću aplikacije"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Povijest"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 3fc12ff..746e3c0 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Folyamatban lévő értesítés képernyőrögzítési munkamenethez"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Elindítja a felvételt?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"A felvétel készítése során az Android rendszer rögzítheti az eszközön lejátszott, illetve a képernyőjén megjelenő bizalmas információkat. Ide tartoznak például a jelszavak, a fizetési információk, a fotók, az üzenetek és az audiotartalmak is."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Teljes képernyő rögzítése"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Egyetlen alkalmazás rögzítése"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Rögzítés közben az Android a képernyőn látható vagy az eszközön lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Alkalmazás rögzítése közben az Android az adott appban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Rögzítés indítása"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Hang rögzítése"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Eszköz hangja"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Az eszköz által lejátszott hangok, például zeneszámok, hívások és csengőhangok"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"A funkciót biztosító szolgáltatás hozzáfér majd minden olyan információhoz, amely látható az Ön képernyőjén, illetve amelyet az Ön eszközéről játszanak le rögzítés vagy átküldés közben. Ez olyan információkat is tartalmaz, mint a jelszavak, a fizetési részletek, fotók, üzenetek és lejátszott audiotartalmak."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Biztosan elkezdi a rögzítést vagy az átküldést?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Elkezdi a rögzítést vagy átküldést a következővel: <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Engedélyezi a(z) <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> számára a megosztást és rögzítést?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Teljes képernyő"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Egyetlen alkalmazás"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Amikor Ön megosztást, rögzítést vagy átküldést végez, a(z) <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a képernyőn látható vagy az eszközön lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, a(z) <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> az adott appban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Folytatás"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Alkalmazás megosztása és rögzítése"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kezelés"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Előzmények"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index b423a26..5e5e249 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Էկրանի տեսագրման աշխատաշրջանի ընթացիկ ծանուցում"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Սկսե՞լ տեսագրումը"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Տեսագրման ընթացքում Android համակարգը կարող է գրանցել անձնական տեղեկություններ, որոնք տեսանելի են էկրանին կամ նվագարկվում են ձեր սարքում։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Տեսագրել ամբողջ էկրանը"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Տեսագրել մեկ հավելված"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Երբ դուք տեսագրում եք էկրանը, Android-ին հասանելի է դառնում այն ամենը, ինչ տեսանելի է էկրանին և նվագարկվում է ձեր սարքում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Երբ դուք տեսագրում եք որևէ հավելվածի էկրանը, Android-ին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Սկսել տեսագրումը"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Ձայնագրել"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Սարքի ձայները"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Ձեր սարքի ձայները, օրինակ՝ երաժշտությունը, զանգերն ու զանգերանգները"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ձայնագրման և հեռարձակման ընթացքում ծառայությունների մատակարարին հասանելի կլինեն ձեր սարքի էկրանին ցուցադրվող տեղեկությունները և ձեր սարքով նվագարկվող նյութերը։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Սկսե՞լ ձայնագրումը կամ հեռարձակումը"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Սկսե՞լ ձայնագրումը կամ հեռարձակումը <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածով"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Թույլատրե՞լ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին ցուցադրել կամ տեսագրել էկրանը"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ամբողջ էկրանը"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Մեկ հավելված"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ տեսանելի է էկրանին և նվագարկվում է ձեր սարքում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Շարունակել"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Հավելվածի էկրանի ցուցադրում կամ տեսագրում"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Կառավարել"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Պատմություն"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index c52b81fa..e372c37 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifikasi yang sedang berjalan untuk sesi rekaman layar"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Mulai merekam?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Saat merekam, Sistem Android dapat ikut merekam informasi sensitif yang terlihat di layar atau diputar di perangkat Anda. Informasi ini mencakup sandi, info pembayaran, foto, pesan, dan audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Rekam seluruh layar"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Rekam satu aplikasi"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Saat Anda merekam, Android akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Saat Anda merekam aplikasi, Android akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Mulai merekam"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Rekam audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio perangkat"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Suara dari perangkat Anda, seperti musik, panggilan, dan nada dering"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Layanan yang menyediakan fungsi ini akan memiliki akses ke semua informasi yang terlihat di layar atau diputar dari perangkat saat merekam atau melakukan transmisi. Ini mencakup informasi seperti sandi, detail pembayaran, foto, pesan, dan audio yang Anda putar."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Mulai merekam atau melakukan transmisi?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Mulai merekam atau melakukan transmisi dengan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Izinkan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> untuk membagikan atau merekam?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Seluruh layar"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Satu aplikasi"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Jika Anda membagikan, merekam, atau mentransmisikan, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Lanjutkan"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Bagikan atau rekam aplikasi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kelola"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histori"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 25ca6ae..136db9e 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Áframhaldandi tilkynning fyrir skjáupptökulotu"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Hefja upptöku?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Á meðan tekið er upp getur Android kerfið fangað viðkvæmar upplýsingar sem sjást á skjánum eða spilast í tækinu. Þar á meðal eru upplýsingar á borð við aðgangsorð, greiðsluupplýsingar, myndir, skilaboð og hljóð."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Taka upp allan skjáinn"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Taka upp eitt forrit"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Þegar þú tekur upp hefur Android aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Þegar þú tekur upp forrit hefur Android aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Hefja upptöku"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Taka upp hljóð"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Hljóð tækis"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Hljóð úr tækinu á borð við tónlist, símtöl og hringitóna"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Þjónustan sem býður upp á þennan eiginleika fær aðgang að öllum upplýsingum sem sjást á skjánum eða eru spilaðar í tækinu á meðan upptaka eða útsending er í gangi, þar á meðal aðgangsorði, greiðsluupplýsingum, myndum, skilaboðum og hljóðefni sem þú spilar."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Viltu hefja upptöku eða útsendingu?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Viltu hefja upptöku eða útsendingu með <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Leyfa <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> að deila eða taka upp?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Allur skjárinn"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Eitt forrit"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Þegar þú deilir, tekur upp eða sendir út hefur<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Þegar þú deilir, tekur upp eða sendir út forrit hefur <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Áfram"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deila eða taka upp forrit"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ferill"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 41b1220..736c6b9 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifica costante per una sessione di registrazione dello schermo"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Avviare la registrazione?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durante la registrazione, il sistema Android può acquisire informazioni sensibili visibili sullo schermo o riprodotte sul tuo dispositivo, tra cui password, dati di pagamento, foto, messaggi e audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Registra l\'intero schermo"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Registra una singola pp"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Quando registri, Android ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Quando registri un\'app, Android ha accesso a qualsiasi elemento visualizzato o riprodotto sull\'app. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Avvia registrazione"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Registra audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio del dispositivo"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Suoni del dispositivo, come musica, chiamate e suonerie"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Il servizio che offre questa funzione avrà accesso a tutte le informazioni visibili sul tuo schermo o riprodotte dal tuo dispositivo durante la registrazione o la trasmissione. Sono incluse informazioni quali password, dettagli sui pagamenti, foto, messaggi e audio riprodotto."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vuoi avviare la registrazione o la trasmissione?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vuoi avviare la registrazione o la trasmissione con <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Consenti a <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> di condividere o registrare?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Schermo intero"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quando condividi, registri o trasmetti, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando condividi, registri o trasmetti un\'app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ha accesso a qualsiasi elemento visualizzato o riprodotto sull\'app. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Condividi o registra un\'app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestisci"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Cronologia"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index e5e37f5..8a1ba7c 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"התראה מתמשכת לסשן הקלטת מסך"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"להתחיל את ההקלטה?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"בזמן ההקלטה, מערכת Android יכולה לתעד מידע רגיש שגלוי במסך או מופעל במכשיר שלך. מידע זה כולל סיסמאות, פרטי תשלום, תמונות, הודעות ואודיו."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"הקלטה של כל המסך"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"הקלטה של אפליקציה אחת"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"בזמן ההקלטה, תהיה ל-Android גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"בזמן הקלטה של אפליקציה, תהיה ל-Android גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"התחלת ההקלטה"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"הקלטת אודיו"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"אודיו מהמכשיר"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"צלילים מהמכשיר, כמו מוזיקה, שיחות ורינגטונים"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"לשירות שמספק את הפונקציה הזו תהיה גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך בזמן הקלטה או העברה (cast) – כולל פרטים כמו סיסמאות, פרטי תשלום, תמונות, הודעות ואודיו שמושמע מהמכשיר."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"להתחיל להקליט או להעביר (cast)?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"להתחיל להקליט או להעביר (cast) באמצעות <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"לאפשר לאפליקציה <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> לשתף או להקליט?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"כל המסך"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"אפליקציה אחת"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"בזמן שיתוף, הקלטה או העברה (cast) תהיה ל-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"המשך"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"שיתוף או הקלטה של אפליקציה"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ניהול"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"היסטוריה"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 15089a4..a2839e8 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"画面の録画セッション中の通知"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"録画を開始しますか?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"録画中に機密情報が画面に表示されたりデバイスで再生されたりした場合、Android システムでキャプチャされることがあります。これには、パスワード、お支払い情報、写真、メッセージ、音声などが含まれます。"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"画面全体を録画する"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"1 つのアプリを録画する"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"録画中は、画面に表示されている内容やデバイスで再生されている内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"アプリの録画中は、そのアプリで表示されている内容や再生されている内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"録画を開始"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"録音"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"デバイスの音声"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"デバイスからの音(音楽、通話、着信音など)"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"この機能を提供するサービスは、録画中またはキャスト中に画面上に表示される情報、またはキャスト先に転送される情報すべてにアクセスできます。これには、パスワード、お支払いの詳細、写真、メッセージ、再生される音声などが含まれます。"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"録画やキャストを開始しますか?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> で録画やキャストを開始しますか?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> に共有や録画を許可しますか?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"画面全体"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"1 つのアプリ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"共有、録画、キャスト中は、画面に表示されている内容やデバイスで再生している内容に <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> がアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"アプリの共有、録画、キャスト中は、そのアプリで表示されている内容や再生している内容に <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> がアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"続行"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"アプリの共有、録画"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"履歴"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 76033ca..6768773 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"უწყვეტი შეტყობინება ეკრანის ჩაწერის სესიისთვის"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"დაიწყოს ჩაწერა?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ჩაწერის განმავლობაში Android სისტემას შეუძლია აღბეჭდოს ნებისმიერი სენსიტიური ინფორმაცია, რომელიც თქვენს ეკრანზე გამოჩნდება ან თქვენს მოწყობილობაზე დაიკვრება. აღნიშნული მოიცავს პაროლებს, გადახდის დეტალებს, ფოტოებს, შეტყობინებებსა და აუდიოს."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"მთელი ეკრანის ჩაწერა"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ერთი აპის ჩაწერა"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"სანამ აპის ჩაწერას ახორციელებთ, Android-ს აქვს წვდომა ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენი მოწყობილობის მეშვეობით. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"სანამ აპის ჩაწერას ახორციელებთ, Android-ს აქვს წვდომა ყველაფერზე, რაც ჩანს აპში ან ითამაშეთ. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ჩაწერის დაწყება"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"აუდიოს ჩაწერა"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"მოწყობილობის აუდიო"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ხმა თქვენი მოწყობილობიდან, როგორიც არის მუსიკა, საუბარი და ზარები"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ამ ფუნქციის მომწოდებელ სერვისს ექნება წვდომა ყველა ინფორმაციაზე, რომელიც თქვენს ეკრანზე გამოჩნდება ან თქვენს მოწყობილობაზე დაიკვრება ჩაწერის ან ტრანსლირების განმავლობაში. აღნიშნული მოიცავს ისეთ ინფორმაციას, როგორიც არის პაროლები, გადახდის დეტალები, ფოტოები, შეტყობინებები და თქვენ მიერ დაკრული აუდიო."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"დაიწყოს ჩაწერა ან ტრანსლირება?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"დაიწყოს ჩაწერა ან ტრანსლირება <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-ით?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"გსურთ დართოთ ნება <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> გაზიარების ან ჩაწერისთვის?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"მთელი ეკრანი"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ერთი აპი"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"როდესაც თქვენ აზიარებთ, ჩაწერთ ან ტრანსლირებთ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> აქვს წვდომა ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> აქვს წვდომა აქვს ყველაფერზე, რაც ჩანს აპში ან ითამაშეთ. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"გაგრძელება"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"გააზიარეთ ან ჩაწერეთ აპი"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"მართვა"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ისტორია"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 4cebc9b..df3e6fd 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды бейнеге жазудың ағымдағы хабарландыруы"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Жазу басталсын ба?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Android жүйесі экранда көрсетілетін немесе құрылғыда ойнатылатын құпия ақпаратты жазып алуы мүмкін. Ондай ақпаратқа құпия сөздер, төлем ақпараты, фотосуреттер, хабарлар және аудио жатады."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Бүкіл экранды жазу"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Жалғыз қолданбаны жазу"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Жазу кезінде Android жүйесі экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Қолданба экранын жазу кезінде Android жүйесі қолданбада көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Жазуды бастау"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Аудио жазу"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Құрылғыдан шығатын дыбыс"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Музыка, қоңыраулар және рингтондар сияқты құрылғыдан шығатын дыбыс"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Осы функцияны ұсынатын қызмет жазу не трансляциялау кезінде экранда көрсетілетін немесе құрылғыда дыбысталатын ақпаратты пайдалана алады. Бұған құпия сөздер, төлем туралы мәліметтер, суреттер, хабарлар және аудиоматериалдар кіреді."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жазу немесе трансляциялау басталсын ба?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> арқылы жазу немесе трансляциялау басталсын ба?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасына экранды бөлісуге не жазуға рұқсат берілсін бе?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Бүкіл экран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Жалғыз қолданба"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Жалғастыру"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Қолданба экранын бөлісу не жазу"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазалау"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Басқару"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Тарих"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index bb1768a..fb6ec60 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ការជូនដំណឹងដែលកំពុងដំណើរការសម្រាប់រយៈពេលប្រើការថតសកម្មភាពអេក្រង់"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ចាប់ផ្តើមថតឬ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"នៅពេលកំពុងថត ប្រព័ន្ធ Android អាចថតព័ត៌មានរសើបដែលអាចមើលឃើញនៅលើអេក្រង់របស់អ្នក ឬដែលបានចាក់នៅលើឧបករណ៍របស់អ្នក។ ព័ត៌មាននេះរួមមានពាក្យសម្ងាត់ ព័ត៌មានអំពីការបង់ប្រាក់ រូបថត សារ និងសំឡេង។"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ថតអេក្រង់ទាំងមូល"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ថតកម្មវិធីតែមួយ"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"នៅពេលអ្នកកំពុងថត Android មានសិទ្ធិចូលប្រើប្រាស់អ្វីៗដែលបង្ហាញឱ្យឃើញនៅលើអេក្រង់របស់អ្នក ឬលេងនៅលើឧបករណ៍របស់អ្នក។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"នៅពេលអ្នកកំពុងថតកម្មវិធី Android មានសិទ្ធិចូលប្រើប្រាស់អ្វីៗដែលបង្ហាញ ឬលេងនៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ចាប់ផ្តើមថត"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ថតសំឡេង"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"សំឡេងឧបករណ៍"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"សំឡេងពីឧបករណ៍របស់អ្នកដូចជា តន្ត្រី ការហៅទូរសព្ទ និងសំឡេងរោទ៍ជាដើម"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"សេវាកម្មដែលផ្ដល់មុខងារនេះនឹងមានសិទ្ធិចូលប្រើព័ត៌មានទាំងអស់ដែលអាចមើលឃើញនៅលើអេក្រង់របស់អ្នក ឬដែលចាក់ពីឧបករណ៍របស់អ្នក នៅពេលកំពុងថត ឬភ្ជាប់។ ព័ត៌មាននេះមានដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ រូបថត សារ និងសំឡេងដែលអ្នកចាក់ជាដើម។"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ចាប់ផ្ដើមថត ឬភ្ជាប់មែនទេ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"ចាប់ផ្ដើមថត ឬភ្ជាប់ដោយប្រើ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ឬ?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"អនុញ្ញាតឱ្យ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ចែករំលែក ឬថតទេ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"អេក្រង់ទាំងមូល"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"កម្មវិធីតែមួយ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬបញ្ជូន <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> មានសិទ្ធិចូលប្រើប្រាស់អ្វីៗដែលបង្ហាញឱ្យឃើញនៅលើអេក្រង់របស់អ្នក ឬលេងនៅលើឧបករណ៍របស់អ្នក។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬបញ្ជូនកម្មវិធី <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> មានសិទ្ធិចូលប្រើប្រាស់អ្វីៗដែលបង្ហាញ ឬលេងនៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"បន្ត"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ចែករំលែក ឬថតកម្មវិធី"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាតទាំងអស់"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"គ្រប់គ្រង"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ប្រវត្តិ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 8d270e3..83a5e87 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೆಶನ್ಗಾಗಿ ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಅಧಿಸೂಚನೆ"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ರೆಕಾರ್ಡಿಂಗ್ ಸಮಯದಲ್ಲಿ, ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ಗೋಚರಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲಾದ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು Android ಸಿಸ್ಟಂ ಕ್ಯಾಪ್ಚರ್ ಮಾಡಬಹುದು. ಇದು ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ಮಾಹಿತಿ, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೋವನ್ನು ಒಳಗೊಂಡಿದೆ."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ಒಂದೇ ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ನೀವು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಿ"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ಸಾಧನದ ಆಡಿಯೋ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ನಿಮ್ಮ ಸಾಧನದ ಧ್ವನಿ ಉದಾ: ಸಂಗೀತ, ಕರೆಗಳು ಮತ್ತು ರಿಂಗ್ಟೋನ್ಗಳು"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ಈ ವೈಶಿಷ್ಟ್ಯವು ಒದಗಿಸುವ ಸೇವೆಗಳು, ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಗೋಚರಿಸುವ ಅಥವಾ ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಾಗ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಎಲ್ಲಾ ಮಾಹಿತಿಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುತ್ತವೆ. ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೋ ಪ್ಲೇಬ್ಯಾಕ್ನಂತಹ ಮಾಹಿತಿಯನ್ನು ಇದು ಒಳಗೊಂಡಿದೆ."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಿಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಮೂಲಕ ರೆಕಾರ್ಡಿಂಗ್, ಬಿತ್ತರಿಸುವುದನ್ನು ಪ್ರಾರಂಭಿಸುವುದೇ?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ಹಂಚಿಕೊಳ್ಳಲು ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಲು <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಅನ್ನು ಅನುಮತಿಸಬೇಕೆ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ಒಂದೇ ಆ್ಯಪ್"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ಮುಂದುವರಿಸಿ"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಿ ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ಇತಿಹಾಸ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index b5bf91f..f3fb7a0 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"화면 녹화 세션에 관한 지속적인 알림"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"녹화를 시작하시겠습니까?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Android 시스템이 녹화 중에 화면에 표시되거나 기기에서 재생되는 민감한 정보를 캡처할 수 있습니다. 여기에는 비밀번호, 결제 정보, 사진, 메시지 및 오디오가 포함됩니다."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"전체 화면 녹화"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"단일 앱 녹화"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"녹화할 때 Android에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"앱을 녹화할 때 Android에서 해당 앱에서 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"녹화 시작"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"오디오 녹음"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"기기 오디오"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"음악, 통화, 벨소리와 같이 기기에서 나는 소리"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"이 기능을 제공하는 서비스는 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"녹화 또는 전송을 시작하시겠습니까?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>으로 녹화 또는 전송을 시작하시겠습니까?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 공유 또는 녹화를 허용할까요?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"전체 화면"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"단일 앱"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"공유하거나 녹화하거나 전송할 때 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 앱에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"앱을 공유하거나 녹화하거나 전송할 때는 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"계속"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"앱 공유 또는 녹화"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"기록"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 2d8e4a6..004ec73 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды жаздыруу сеансы боюнча учурдагы билдирме"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Жаздырып баштайсызбы?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Сырсөздөр, төлөм маалыматы, сүрөттөр, билдирүүлөр жана аудиофайлдар сыяктуу экраныңызда көрүнүп турган жана түзмөктө ойноп жаткан бардык купуя маалымат жазылып калат."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Бүтүндөй экранды жаздыруу"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Жалгыз колдонмону жаздыруу"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Жаздырып жатканыңызда Android экраныңызда көрүнүп жана түзмөктө ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөм маалыматын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Жаздырып жатканыңызда Android ал колдонмодо көрүнүп жана ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөм маалыматын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Жаздырып баштоо"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Аудио жаздыруу"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Түзмөктөгү аудиолор"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Музыка, чалуулар жана шыңгырлар сыяктуу түзмөгүңүздөгү добуштар"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Жаздырып же тышкы экранга чыгарып жатканда, бул колдонмо экраныңыздагы бардык маалыматты же түзмөктө ойнолуп жаткан бардык нерселерди (сырсөздөрдү, төлөмдүн чоо-жайын, сүрөттөрдү, билдирүүлөрдү жана угуп жаткан аудиофайлдарды) көрө алат."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жаздырып же тышкы экранга чыгарып баштайсызбы?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> колдонмосу аркылуу жаздырып же тышкы экранга чыгарып баштайсызбы?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> колдонмосуна бөлүшүүгө же жаздырууга уруксат бересизби?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Бүтүндөй экран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Жалгыз колдонмо"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Бөлүшүп, жаздырып же тышкы экранда бөлүшкөндө <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> экраныңызда көрүнүп жана түзмөктө ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөм маалыматын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Бөлүшүп, жаздырып же тышкы экранда бөлүшкөндө <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ал колдонмодо көрүнүп жана ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөм маалыматын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Улантуу"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Колдонмону бөлүшүү же жаздыруу"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Башкаруу"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Таржымал"</string>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 9d7b01c..49ef330 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -59,4 +59,5 @@
<dimen name="large_dialog_width">348dp</dimen>
<dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
+ <dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 69ff576..088ae17 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ການແຈ້ງເຕືອນສຳລັບເຊດຊັນການບັນທຶກໜ້າຈໍໃດໜຶ່ງ"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ເລີ່ມການບັນທຶກບໍ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ໃນລະຫວ່າງການບັນທຶກ, ລະບົບ Android ຈະສາມາດບັນທຶກຂໍ້ມູນທີ່ລະອຽດອ່ອນໃດກໍຕາມທີ່ສະແດງຢູ່ໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຢູ່ອຸປະກອນທ່ານ. ນີ້ຮວມເຖິງລະຫັດຜ່ານ, ຂໍ້ມູນການຈ່າຍເງິນ, ຮູບ, ຂໍ້ຄວາມ ແລະ ສຽງນຳ."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ບັນທຶກໝົດໜ້າຈໍ"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ບັນທຶກແອັບດຽວ"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ໃນຂະນະທີ່ທ່ານກຳລັງບັນທຶກ, Android ຈະມີສິດເຂົ້າເຖິງສິ່ງທີ່ເບິ່ງໃຫ້ຢູ່ໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງເລື່ອງລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ໃນຂະນະທີ່ທ່ານກຳລັງບັນທຶກແອັບ, Android ຈະມີສິດເຂົ້າເຖິງສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ໃນແອັບນັ້ນ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງກ່ຽວກັບລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ເລີ່ມການບັນທຶກ"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ບັນທຶກສຽງ"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ສຽງອຸປະກອນ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ສຽງຈາກອຸປະກອນຂອງທ່ານ ເຊັ່ນ: ສຽງເພງ, ສຽງລົມໂທລະສັບ ແລະ ສຽງຣິງໂທນ"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ບໍລິການທີ່ສະໜອງຄວາມສາມາດນີ້ຈະມີສິດເຂົ້າເຖິງຂໍ້ມູນທັງໝົດທີ່ປາກົດຢູ່ໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຈາກອຸປະກອນຂອງທ່ານໃນເວລາບັນທຶກ ຫຼື ສົ່ງສັນຍານໜ້າຈໍ. ນີ້ຮວມເຖິງຂໍ້ມູນຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຮູບ, ຂໍ້ຄວາມ ແລະ ສຽງທີ່ທ່ານຫຼິ້ນ."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ເລີ່ມການບັນທຶກ ຫຼື ການສົ່ງສັນຍານໜ້າຈໍບໍ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"ເລີ່ມການບັນທຶກ ຫຼື ການສົ່ງສັນຍານໜ້າຈໍກັບ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ບໍ?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ອະນຸຍາດໃຫ້ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ແບ່ງປັນ ຫຼື ບັນທຶກບໍ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ທົງໝົດໜ້າຈໍ"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ແອັບດຽວ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ມີສິດເຂົ້າເຖິງສິ່ງທີ່ເຫັນໄດ້ໃນໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງເລື່ອງລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ໃນຕອນທີ່ທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ມີສິດເຂົ້າເຖິງສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ໃນແອັບນັ້ນ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງກ່ຽວກັບລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ສືບຕໍ່"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ແບ່ງປັນ ຫຼື ບັນທຶກແອັບ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ຈັດການ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ປະຫວັດ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index fd9c7cc..8e548b6 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Šiuo metu rodomas ekrano įrašymo sesijos pranešimas"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Pradėti įrašymą?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Įrašant „Android“ sistema gali fiksuoti bet kokią neskelbtiną informaciją, rodomą ekrane ar leidžiamą įrenginyje. Tai apima slaptažodžius, išsamią mokėjimo informaciją, nuotraukas, pranešimus ir garso įrašus."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Įrašyti visą ekraną"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Įrašyti vieną programą"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Kai įrašote, „Android“ gali pasiekti viską, kas rodoma ekrane ar leidžiama įrenginyje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Kai įrašote programą „Android“ gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Pradėti įrašymą"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Įrašyti garsą"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Įrenginio garsas"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Garsas iš jūsų įrenginio, pvz., muzika, skambučiai ir skambėjimo tonai"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Šią funkciją teikianti paslauga galės pasiekti visą informaciją, matomą ekrane ir leidžiamą iš įrenginio įrašant ar perduodant turinį. Tai apima įvairią informaciją, pvz., slaptažodžius, išsamią mokėjimo informaciją, nuotraukas, pranešimus ir leidžiamus garso įrašus."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Pradėti įrašyti ar perduoti turinį?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Pradėti įrašyti ar perduoti turinį naudojant „<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>“?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Leisti „<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>“ bendrinti ar įrašyti?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Visas ekranas"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Viena programa"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kai bendrinate, įrašote ar perduodate turinį, „<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>“ gali pasiekti viską, kas rodoma ekrane ar leidžiama įrenginyje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kai bendrinate, įrašote ar perduodate turinį, „<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>“ gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Tęsti"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Programos bendrinimas ar įrašymas"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Tvarkyti"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 44e5507..5a63b62 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Aktīvs paziņojums par ekrāna ierakstīšanas sesiju"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vai sākt ierakstīšanu?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Ierakstīšanas laikā Android sistēmā var tikt tverta jebkura sensitīvā informācija, kas ir redzama jūsu ekrānā vai tiek atskaņota jūsu ierīcē. Šī informācija ir paroles, maksājumu informācija, fotoattēli, ziņojumi un audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Ierakstīt visu ekrānu"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ierakstīt vienu lietotni"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Ierakstīšanas laikā Android var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Lietotnes ierakstīšanas laikā Android var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Sākt ierakstīšanu"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Ierakstīt audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Ierīces audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Skaņa no jūsu ierīces, piemēram, mūzika, sarunas un zvana signāli"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Pakalpojums, kas nodrošina šo funkciju, iegūs piekļuvi visai informācijai, kas ierakstīšanas vai apraides laikā tiks rādīta jūsu ekrānā vai atskaņota jūsu ierīcē. Atļauja attiecas uz tādu informāciju kā paroles, maksājumu informācija, fotoattēli, ziņojumi un jūsu atskaņotais audio saturs."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vai vēlaties sākt ierakstīšanu/apraidi?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vai vēlaties sākt ierakstīšanu vai apraidi, izmantojot lietotni <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vai atļaujat lietotnei <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> veikt kopīgošanu vai ierakstīšanu?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Viss ekrāns"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Viena lietotne"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Turpināt"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Lietotnes kopīgošana vai ierakstīšana"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Pārvaldīt"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Vēsture"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 8b8753e..de9e82c7 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Тековно известување за сесија за снимање на екранот"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Да се започне со снимање?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"При снимањето, системот Android може да ги сними сите чувствителни податоци што се видливи на вашиот екран или пуштени на уредот. Ова вклучува лозинки, податоци за плаќање, фотографии, пораки и аудио."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Снимај го целиот екран"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Снимај една апликација"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Додека снимате, Android има пристап до сѐ што е видливо на вашиот екран или пуштено на вашиот уред. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Додека снимате апликација, Android има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Започни со снимање"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Снимај аудио"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Аудио од уредот"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук од вашиот уред, како на пр., музика, повици и мелодии"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Услугата што ја обезбедува функцијава ќе има пристап до сите податоци што се видливи на екранот или пуштени од вашиот уред додека се снима или емитува. Ова вклучува податоци како лозинките, деталите за плаќање, фотографиите, пораките и аудиото што го пуштате."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Да почне снимање или емитување?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Да почне снимање или емитување со <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Да ѝ се дозволи на <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> да споделува или снима?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Цел екран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Една апликација"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Кога споделувате, снимате или емитувате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има пристап до сѐ што е видливо на вашиот екран или пуштено на вашиот уред. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Кога споделувате, снимате или емитувате апликација, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продолжи"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделете или снимете апликација"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Управувајте"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 532ded7..7611822 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ഒരു സ്ക്രീൻ റെക്കോർഡിംഗ് സെഷനായി നിലവിലുള്ള അറിയിപ്പ്"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"റെക്കോർഡിംഗ് ആരംഭിക്കണോ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"റെക്കോർഡ് ചെയ്യുമ്പോൾ, നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് തന്ത്രപ്രധാന വിവരങ്ങളും Android സിസ്റ്റത്തിന് പകർത്താനാവും. പാസ്വേഡുകൾ, പേയ്മെന്റ് വിവരം, ഫോട്ടോകൾ, സന്ദേശങ്ങൾ, ഓഡിയോ എന്നിവ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"പൂർണ സ്ക്രീൻ റെക്കോർഡ് ചെയ്യൂ"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ഒറ്റ ആപ്പ് റെക്കോർഡ് ചെയ്യുക"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"റെക്കോർഡ് ചെയ്യുമ്പോൾ, Android-ന് സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ഒരു ആപ്പ് റെക്കോർഡ് ചെയ്യുമ്പോൾ, Android-ന് ആ ആപ്പിൽ കാണിക്കുന്നതോ പ്ലേ ചെയ്യുന്നതോ ആയ എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"റെക്കോർഡിംഗ് ആരംഭിക്കുക"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ഉപകരണത്തിന്റെ ഓഡിയോ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"സംഗീതം, കോളുകൾ, റിംഗ്ടോണുകൾ എന്നിവപോലെ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്നുള്ള ശബ്ദം"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"റെക്കോർഡ് ചെയ്യുമ്പോഴോ കാസ്റ്റ് ചെയ്യുമ്പോഴോ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് പ്ലേ ചെയ്യുന്നതോ നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ആയ എല്ലാ വിവരങ്ങളിലേക്കും ഈ ഫംഗ്ഷൻ ലഭ്യമാക്കുന്ന സേവനത്തിന് ആക്സസ് ഉണ്ടായിരിക്കും. നിങ്ങൾ പ്ലേ ചെയ്യുന്ന ഓഡിയോ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, പാസ്വേഡുകൾ എന്നിവ പോലുള്ള വിവരങ്ങൾ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ഉപയോഗിച്ച് റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"പങ്കിടാനോ റെക്കോർഡ് ചെയ്യാനോ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"മുഴുവൻ സ്ക്രീൻ"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ഒറ്റ ആപ്പ്"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"തുടരുക"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ഒരു ആപ്പ് പങ്കിടുക അല്ലെങ്കിൽ റെക്കോർഡ് ചെയ്യുക"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്ക്കുക"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ചരിത്രം"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 985a0e6..6805916 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Дэлгэц бичих горимын үргэлжилж буй мэдэгдэл"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Бичлэгийг эхлүүлэх үү?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Бичих үед Андройд систем нь таны дэлгэц дээр харагдах эсвэл төхөөрөмж дээрээ тоглуулсан аливаа эмзэг мэдээллийг авах боломжтой. Үүнд нууц үг, төлбөрийн мэдээлэл, зураг, мессеж болон аудио багтана."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Бүтэн дэлгэцийг бичих"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Нэг аппыг бичих"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Таныг бичиж байх үед Android нь таны дэлгэц дээр харагдаж буй эсвэл төхөөрөмж дээр тоглуулж буй аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж эсвэл бусад эмзэг мэдээлэлд болгоомжтой хандаарай."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Таныг апп бичиж байх үед Android нь тухайн апп дээр харуулж эсвэл тоглуулж буй аливаа зүйлд хандах эрхтэй."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Бичиж эхлэх"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Аудио бичих"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Төхөөрөмжийн аудио"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Хөгжим, дуудлага болон хонхны ая зэрэг таны төхөөрөмжийн дуу"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Энэ функцийг ажиллуулж байгаа үйлчилгээ нь бичлэг хийх эсвэл дамжуулах үед таны дэлгэц дээр харагдах эсвэл таны төхөөрөмжөөс тоглуулах бүх мэдээлэлд хандах боломжтой байна. Үүнд нууц үг, төлбөрийн дэлгэрэнгүй, зураг болон таны тоглуулдаг аудио зэрэг мэдээлэл багтана."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Бичлэг хийх эсвэл дамжуулахыг эхлүүлэх үү?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-тай бичлэг хийж эсвэл дамжуулж эхлэх үү?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-д хуваалцах эсвэл бичихийг зөвшөөрөх үү?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Бүтэн дэлгэц"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Нэг апп"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Таныг хуваалцаж, бичиж эсвэл дамжуулж байх үед <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> нь таны дэлгэц дээр харагдаж буй аливаа зүйл эсвэл төхөөрөмж дээр тань тоглуулж буй зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж эсвэл бусад эмзэг мэдээлэлд болгоомжтой хандаарай."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Таныг хуваалцаж, бичиж эсвэл дамжуулж байх үед <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> нь тухайн апп дээр харуулсан эсвэл тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж эсвэл бусад эмзэг мэдээлэлд болгоомжтой хандаарай."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Үргэлжлүүлэх"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Хуваалцах эсвэл бичих апп"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Удирдах"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Түүх"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 6a2276a..2f96f33 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रेकॉर्ड सत्रासाठी सुरू असलेली सूचना"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"रेकॉर्डिंग सुरू करायचे आहे का?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"रेकॉर्डिंग करताना, Android सिस्टीम तुमच्या स्क्रीनवर दिसणारी किंवा तुमच्या डिव्हाइसवर प्ले केलेली कोणतीही संवेदनशील माहिती कॅप्चर करू शकते. यात पासवर्ड, पेमेंट माहिती, फोटो, मेसेज आणि ऑडिओचा समावेश आहे."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"संपूर्ण स्क्रीन रेकॉर्ड करा"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"एकच अॅप रेकॉर्ड करा"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"तुम्ही रेकॉर्ड करत असताना, Android ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"तुम्ही अॅप रेकॉर्ड करत असताना, Android ला त्या अॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"रेकॉर्डिंग सुरू करा"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ऑडिओ रेकॉर्ड करा"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिव्हाइस ऑडिओ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"तुमच्या डिव्हाइसवरील आवाज, जसे की संगीत, कॉल आणि रिंगटोन"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"हे कार्य पुरवणाऱ्या सेवेस तुमच्या स्क्रीनवर दृश्यमान असलेल्या किंवा रेकॉर्ड किंवा कास्ट करताना तुमच्या डिव्हाइसमधून प्ले केलेल्या सर्व माहितीचा अॅक्सेस असेल. यामध्ये पासवर्ड, पेमेंट तपशील, फोटो, मेसेज आणि तुम्ही प्ले केलेला ऑडिओ यासारख्या माहितीचा समावेश असतो."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकॉर्ड करणे किंवा कास्ट करणे सुरू करायचे का ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ने रेकॉर्ड करणे किंवा कास्ट करणे सुरू करायचे का?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला शेअर किंवा रेकॉर्ड करण्याची अनुमती द्यायची आहे का?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"संपूर्ण स्क्रीन"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"एक अॅप"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तुम्ही अॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला त्या अॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"पुढे सुरू ठेवा"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"अॅप शेअर किंवा रेकॉर्ड करा"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 8a9a8d6..e6dcbbb 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pemberitahuan breterusan untuk sesi rakaman skrin"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Mula Merakam?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Semasa merakam, Sistem Android dapat menangkap mana-mana maklumat sensitif yang kelihatan pada skrin anda atau yang dimainkan pada peranti anda. Ini termasuklah kata laluan, maklumat pembayaran, foto, mesej dan audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Rakam seluruh skrin"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Rakam satu apl"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Apabila anda merakam, Android mempunyai akses kepada apa-apa yang boleh dilihat pada skrin anda atau dimainkan pada peranti anda. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Apabila anda merakam apl, Android mempunyai akses kepada apa-apa yang dipaparkan atau dimainkan pada apl tersebut. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Mulakan rakaman"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Rakam audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio peranti"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Bunyi daripada peranti anda, seperti muzik, panggilan dan nada dering"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Perkhidmatan yang menyediakan fungsi ini akan mempunyai akses kepada semua maklumat yang kelihatan pada skrin anda atau dimainkan daripada peranti anda semasa merakam atau membuat penghantaran. Ini termasuklah maklumat seperti kata laluan, butiran pembayaran, foto, mesej dan audio yang anda mainkan."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Mulakan rakaman atau penghantaran?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Mulakan rakaman atau penghantaran dengan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Benarkan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> berkongsi atau merakam?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Seluruh skrin"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Satu apl"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Apabila anda berkongsi, merakam atau menghantar, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> mempunyai akses kepada apa-apa yang boleh dilihat pada skrin anda atau dimainkan pada peranti anda. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Apabila anda berkongsi, merakam atau menghantar apl, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> mempunyai akses kepada apa-apa yang dipaparkan atau dimainkan pada apl tersebut. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Teruskan"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Kongsi atau rakam apl"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Urus"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Sejarah"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index ddfb404..fe1b908 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ဖန်သားပြင် ရိုက်ကူးသည့် စက်ရှင်အတွက် ဆက်တိုက်လာနေသော အကြောင်းကြားချက်"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"စတင် ရိုက်ကူးမလား။"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ရိုက်ကူးနေစဉ်အတွင်း Android စနစ်သည် သင့်ဖန်သားပြင်ပေါ်တွင် မြင်နိုင်သော (သို့) သင့်စက်ပစ္စည်းတွင် ဖွင့်ထားသော အရေးကြီးသည့် အချက်အလက်များကို ရိုက်ယူနိုင်သည်။ ၎င်းတွင် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ ဓာတ်ပုံ၊ မက်ဆေ့ဂျ်နှင့် အသံများ ပါဝင်သည်။"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ဖန်သားပြင်တစ်ခုလုံးရိုက်ကူးရန်"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"အက်ပ်တစ်ခုတွင် ရိုက်ကူးရန်"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ရိုက်ကူးနေစဉ် Android သည် သင့်ဖန်သားပြင်ရှိ မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"အက်ပ်တစ်ခုကို ရိုက်ကူးနေစဉ် Android သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"စတင်ရိုက်ကူးရန်"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"အသံဖမ်းရန်"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"စက်ပစ္စည်းအသံ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"သီချင်း၊ ဖုန်းခေါ်ဆိုမှုနှင့် ဖုန်းမြည်သံကဲ့သို့ သင့်စက်ပစ္စည်းမှ အသံ"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ဤဝန်ဆောင်မှုသည် ရိုက်ကူးဖမ်းယူနေစဉ် (သို့) ကာစ်လုပ်နေစဉ်အတွင်း သင့်ဖန်သားပြင်တွင် မြင်ရသော (သို့) သင့်စက်တွင် ဖွင့်ထားသော အချက်အလက်အားလုံးကို ကြည့်နိုင်ပါမည်။ ၎င်းတွင် စကားဝှက်များ၊ ငွေပေးချေမှုအသေးစိတ်များ၊ ဓာတ်ပုံများ၊ မက်ဆေ့ဂျ်များနှင့် သင်ဖွင့်သည့်အသံကဲ့သို့သော အချက်အလက်များ ပါဝင်သည်။"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ရိုက်ကူးဖမ်းယူခြင်း (သို့) ကာစ်လုပ်ခြင်း စတင်မလား။"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> နှင့် ဖမ်းယူခြင်း သို့မဟုတ် ကာစ်လုပ်ခြင်း စတင်မလား။"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"မျှဝေရန် (သို့) ရိုက်ကူးရန် <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ကို ခွင့်ပြုမလား။"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ဖန်သားပြင်တစ်ခုလုံး"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"အက်ပ်တစ်ခုတွင်"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် သင့်ဖန်သားပြင်ရှိ မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"အက်ပ်ဖြင့် မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ရှေ့ဆက်ရန်"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"အက်ပ် မျှဝေခြင်း (သို့) ရိုက်ကူးခြင်း"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"စီမံရန်"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"မှတ်တမ်း"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 01aabee..951eb8c 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Vedvarende varsel for et skjermopptak"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vil du starte et opptak?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Under opptak kan Android-systemet registrere all sensitiv informasjon som er synlig på skjermen eller spilles av på enheten. Dette inkluderer passord, betalingsinformasjon, bilder, meldinger og lyd."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Ta opp hele skjermen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ta opp én app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Når du tar opp noe, har Android tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Når du tar opp en app, har Android tilgang til alt som vises eller spilles av i appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Start opptaket"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Spill inn lyd"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Enhetslyd"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Lyd fra enheten, f.eks. musikk, samtaler og ringelyder"</string>
@@ -233,13 +238,13 @@
<string name="quick_settings_internet_label" msgid="6603068555872455463">"Internett"</string>
<string name="quick_settings_networks_available" msgid="1875138606855420438">"Tilgjengelige nettverk"</string>
<string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Nettverk er utilgjengelige"</string>
- <string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Ingen tilgjengelige Wi-Fi-nettverk"</string>
+ <string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Ingen tilgjengelige Wifi-nettverk"</string>
<string name="quick_settings_wifi_secondary_label_transient" msgid="7501659015509357887">"Slår på …"</string>
<string name="quick_settings_cast_title" msgid="2279220930629235211">"Skjermcasting"</string>
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Enhet uten navn"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ingen enheter er tilgjengelige"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi er ikke tilkoblet"</string>
+ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi er ikke tilkoblet"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Fargeinvertering"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Fargekorrigering"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Tjenesten som leverer denne funksjonen, får tilgang til all informasjon som er synlig på skjermen din, eller som spilles av fra enheten når du tar opp eller caster. Dette inkluderer informasjon som passord, betalingsopplysninger, bilder, meldinger og lyd du spiller av."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vil du starte opptak eller casting?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vil du starte opptak eller casting med <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vil du gi <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tillatelse til å dele eller ta opp?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hele skjermen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Én app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Når du deler, tar opp eller caster noe, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, tar opp eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsett"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller ta opp en app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Logg"</string>
@@ -704,7 +716,7 @@
<string name="mobile_data" msgid="4564407557775397216">"Mobildata"</string>
<string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> – <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string>
- <string name="wifi_is_off" msgid="5389597396308001471">"Wi-Fi er av"</string>
+ <string name="wifi_is_off" msgid="5389597396308001471">"Wifi er av"</string>
<string name="bt_is_off" msgid="7436344904889461591">"Bluetooth er av"</string>
<string name="dnd_is_off" msgid="3185706903793094463">"Ikke forstyrr er av"</string>
<string name="qs_dnd_prompt_auto_rule" msgid="3535469468310002616">"Ikke forstyrr ble slått på av en automatisk regel (<xliff:g id="ID_1">%s</xliff:g>)."</string>
@@ -713,7 +725,7 @@
<string name="running_foreground_services_title" msgid="5137313173431186685">"Apper kjører i bakgrunnen"</string>
<string name="running_foreground_services_msg" msgid="3009459259222695385">"Trykk for detaljer om batteri- og databruk"</string>
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vil du slå av mobildata?"</string>
- <string name="mobile_data_disable_message" msgid="8604966027899770415">"Du får ikke tilgang til data eller internett via <xliff:g id="CARRIER">%s</xliff:g>. Internett er bare tilgjengelig via Wi-Fi."</string>
+ <string name="mobile_data_disable_message" msgid="8604966027899770415">"Du får ikke tilgang til data eller internett via <xliff:g id="CARRIER">%s</xliff:g>. Internett er bare tilgjengelig via Wifi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatøren din"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Fordi en app skjuler tillatelsesforespørselen, kan ikke Innstillinger bekrefte svaret ditt."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vil du tillate at <xliff:g id="APP_0">%1$s</xliff:g> viser <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt?"</string>
@@ -914,10 +926,10 @@
<string name="unlock_to_view_networks" msgid="5072880496312015676">"Lås opp for å se nettverk"</string>
<string name="wifi_empty_list_wifi_on" msgid="3864376632067585377">"Søker etter nettverk …"</string>
<string name="wifi_failed_connect_message" msgid="4161863112079000071">"Kunne ikke koble til nettverket"</string>
- <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi kobles ikke til automatisk inntil videre"</string>
+ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wifi kobles ikke til automatisk inntil videre"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Se alle"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"For å bytte nettverk, koble fra Ethernet"</string>
- <string name="wifi_scan_notify_message" msgid="3753839537448621794">"For å forbedre brukeropplevelsen på enheten kan apper og tjenester søke etter Wi-Fi-nettverk når som helst – også når Wi-Fi er slått av. Du kan endre dette i innstillingene for wifi-skanning. "<annotation id="link">"Endre"</annotation></string>
+ <string name="wifi_scan_notify_message" msgid="3753839537448621794">"For å forbedre brukeropplevelsen på enheten kan apper og tjenester søke etter Wifi-nettverk når som helst – også når Wifi er slått av. Du kan endre dette i innstillingene for wifi-skanning. "<annotation id="link">"Endre"</annotation></string>
<string name="turn_off_airplane_mode" msgid="8425587763226548579">"Slå av flymodus"</string>
<string name="qs_tile_request_dialog_text" msgid="3501359944139877694">"<xliff:g id="APPNAME">%1$s</xliff:g> vil legge til denne brikken i Hurtiginnstillinger"</string>
<string name="qs_tile_request_dialog_add" msgid="4888460910694986304">"Legg til brikke"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index caeceb4..35112a7 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"कुनै स्क्रिन रेकर्ड गर्ने सत्रका लागि चलिरहेको सूचना"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"रेकर्ड गर्न थाल्ने हो?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"रेकर्ड गर्दा, Android सिस्टमले तपाईंको स्क्रिनमा देखिने वा तपाईंको डिभाइसमा प्ले गरिने सबै संवेदनशील जानकारी रेकर्ड गर्न सक्छ। यो जानकारीमा पासवर्ड, भुक्तानीसम्बन्धी जानकारी, फोटो, सन्देश र अडियो समावेश हुन्छ।"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"पूरै स्क्रिन रेकर्ड गर्नुहोस्"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"एउटा एप मात्र रेकर्ड गर्नुहोस्"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"तपाईंले रेकर्ड गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले रेकर्ड गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"तपाईंले रेकर्ड गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले रेकर्ड गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"रेकर्ड गर्न थाल्नुहोस्"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"अडियो रेकर्ड गरियोस्"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिभाइसको अडियो"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"तपाईंको डिभाइसका सङ्गीत, कल र रिङटोन जस्ता साउन्ड"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"यो कार्य गर्ने सेवाले तपाईंको स्क्रिनमा देख्न सकिने सबै जानकारी अथवा रेकर्ड वा कास्ट गर्दा तपाईंको डिभाइसबाट प्ले गरिएका कुरा हेर्न तथा प्रयोग गर्न सक्छ। यसले हेर्न तथा प्रयोग गर्न सक्ने कुरामा पासवर्ड, भुक्तानीका विवरण, फोटो, सन्देश र तपाईंले प्ले गर्ने अडियो कुराहरू समावेश हुन सक्छन्।"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकर्ड गर्न वा cast गर्न थाल्ने हो?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> मार्फत रेकर्ड गर्न वा cast गर्न थाल्ने हो?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> लाई सेयर गर्न वा रेकर्ड गर्न दिने हो?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"पूर्ण स्क्रिन"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"एकल एप"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले सेयर, रेकर्ड वा कास्ट गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी राख्नुहोस्"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"सेयर वा रेकर्ड गर्नका लागि एप चयन गर्नुहोस्"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थित गर्नुहोस्"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 1cad1bc..641c128 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Doorlopende melding voor een schermopname-sessie"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Opname starten?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Tijdens de opname kan het Android-systeem gevoelige informatie opnemen die zichtbaar is op je scherm of wordt afgespeeld op je apparaat, waaronder wachtwoorden, betalingsgegevens, foto\'s, berichten en audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Volledig scherm opnemen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Eén app opnemen"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Als je opneemt, heeft Android toegang tot alles dat zichtbaar is op je scherm of wordt afgespeeld op je apparaat. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Als je een app opneemt, heeft Android toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Opname starten"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Audio opnemen"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio van apparaat"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Geluid van je apparaat, zoals muziek, gesprekken en ringtones"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"De service die deze functie levert, krijgt tijdens het opnemen of casten toegang tot alle informatie die op je scherm te zien is of op je apparaat wordt afgespeeld. Dit omvat informatie zoals wachtwoorden, betalingsgegevens, foto\'s, berichten en audio die je afspeelt."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Beginnen met opnemen of casten?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Beginnen met opnemen of casten met <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Toestaan dat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> deelt of opneemt?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Volledig scherm"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Eén app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Als je deelt, opneemt of cast, heeft <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot alles dat zichtbaar is op je scherm of wordt afgespeeld op je apparaat. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Als je deelt, opneemt of cast, heeft <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Doorgaan"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App delen of opnemen"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Beheren"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Geschiedenis"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index a5983de..04c3774 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ଏକ ସ୍କ୍ରିନ୍ ରେକର୍ଡ୍ ସେସନ୍ ପାଇଁ ଚାଲୁଥିବା ବିଜ୍ଞପ୍ତି"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବେ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ରେକର୍ଡିଂ ସମୟରେ, Android ସିଷ୍ଟମ୍ ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ଚାଲୁଥିବା ଯେ କୌଣସି ସମ୍ବେଦନଶୀଳ ସୂଚନାକୁ କ୍ୟାପଚର୍ କରିପାରିବ। ଏଥିରେ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ସୂଚନା, ଫଟୋ, ମେସେଜ ଏବଂ ଅଡିଓ ଅନ୍ତର୍ଭୁକ୍ତ।"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ ରେକର୍ଡ କର"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ଏକ ସିଙ୍ଗଲ ଆପ ରେକର୍ଡ କରନ୍ତୁ"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ଆପଣ ରେକର୍ଡିଂ କରିବା ବେଳେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ଆପଣ ଏକ ଆପ ରେକର୍ଡିଂ କରିବା ବେଳେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ରେକର୍ଡିଂ ଆରମ୍ଭ କରନ୍ତୁ"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ଡିଭାଇସ୍ ଅଡିଓ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ଆପଣଙ୍କ ଡିଭାଇସରୁ ସାଉଣ୍ଡ, ଯେପରିକି ସଙ୍ଗୀତ, କଲ୍ ଏବଂ ରିଂଟୋନଗୁଡ଼ିକ"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ବେଳେ ଆପଣଙ୍କର ଡିଭାଇସରେ ଦେଖାଯାଉଥିବା ବା ଆପଣଙ୍କ ଡିଭାଇସରୁ ପ୍ଲେ କରାଯାଉଥିବା ସବୁ ସୂଚନାକୁ ଏହି ଫଙ୍କସନ୍ ପ୍ରଦାନ କରୁଥିବା ସେବାର ଆକ୍ସେସ୍ ରହିବ। ପାସ୍ୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ଫଟୋ, ମେସେଜ୍ ଏବଂ ଆପଣ ଚଲାଉଥିବା ଅଡିଓ ପରି ସୂଚନା ଏଥିରେ ଅନ୍ତର୍ଭୁକ୍ତ ଅଛି।"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ଆରମ୍ଭ କରିବେ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ସହ ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ଆରମ୍ଭ କରିବେ?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ସେୟାର କିମ୍ବା ରେକର୍ଡ କରିବା ପାଇଁ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ଏକ ସିଙ୍ଗଲ ଆପ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ସେହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ଜାରି ରଖନ୍ତୁ"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ଏକ ଆପକୁ ସେୟାର କିମ୍ବା ରେକର୍ଡ କରନ୍ତୁ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ସମସ୍ତ ଖାଲି କରନ୍ତୁ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ପରିଚାଳନା କରନ୍ତୁ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ଇତିହାସ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 0c78e89..5f9709a 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ਕਿਸੇ ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਸੈਸ਼ਨ ਲਈ ਚੱਲ ਰਹੀ ਸੂਚਨਾ"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ਕੀ ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰਨੀ ਹੈ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਸਿਸਟਮ ਕੋਈ ਵੀ ਅਜਿਹੀ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਕੈਪਚਰ ਕਰ ਸਕਦਾ ਹੈ ਜੋ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਣਯੋਗ ਹੈ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਵਿੱਚ ਪਾਸਵਰਡ, ਭੁਗਤਾਨ ਵੇਰਵੇ, ਫ਼ੋਟੋਆਂ, ਸੁਨੇਹੇ ਅਤੇ ਆਡੀਓ ਸ਼ਾਮਲ ਹਨ।"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ਇਕਹਿਰੀ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰੋ"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ਆਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ਡੀਵਾਈਸ ਆਡੀਓ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਧੁਨੀ, ਜਿਵੇਂ ਕਿ ਸੰਗੀਤ, ਕਾਲਾਂ ਅਤੇ ਰਿੰਗਟੋਨਾਂ"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ਇਹ ਫੰਕਸ਼ਨ ਪ੍ਰਦਾਨ ਕਰਨ ਵਾਲੀ ਸੇਵਾ ਕੋਲ ਸਾਰੀ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਹੋਵੇਗੀ ਜੋ ਕਿ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਣਯੋਗ ਹੁੰਦੀ ਹੈ ਜਾਂ ਰਿਕਾਰਡ ਜਾਂ ਕਾਸਟ ਕਰਨ ਵੇਲੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਵਿੱਚ ਪਾਸਵਰਡ, ਭੁਗਤਾਨ ਵੇਰਵੇ, ਫ਼ੋਟੋਆਂ, ਸੁਨੇਹੇ ਅਤੇ ਤੁਹਾਡੇ ਵੱਲੋਂ ਚਲਾਏ ਆਡੀਓ ਦੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੁੰਦੀ ਹੈ।"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ਕੀ ਰਿਕਾਰਡ ਜਾਂ ਕਾਸਟ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਨਾਲ ਰਿਕਾਰਡਿੰਗ ਜਾਂ ਕਾਸਟ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ਕੀ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਜਾਂ ਰਿਕਾਰਡ ਕਰਨ ਲਈ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ਇਕਹਿਰੀ ਐਪ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ਜਾਰੀ ਰੱਖੋ"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ਐਪ ਨੂੰ ਸਾਂਝਾ ਕਰੋ ਜਾਂ ਰਿਕਾਰਡ ਕਰੋ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ਇਤਿਹਾਸ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 063ffbf..563f8dc 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Stałe powiadomienie o sesji rejestrowania zawartości ekranu"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Rozpocząć nagrywanie?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Podczas nagrywania system Android może rejestrować wszelkie informacje poufne wyświetlane na ekranie lub odtwarzane na urządzeniu. Dotyczy to m.in. haseł, szczegółów płatności, zdjęć, wiadomości i odtwarzanych dźwięków."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Nagrywaj cały ekran"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Nagrywaj pojedynczą aplikację"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Podczas nagrywania Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Podczas nagrywania treści z aplikacji Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Zacznij nagrywać"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Nagraj dźwięk"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Dźwięki z urządzenia"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Dźwięki odtwarzane na urządzeniu, na przykład muzyka, połączenia i dzwonki"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Podczas nagrywania i przesyłania usługa udostępniająca tę funkcję będzie miała dostęp do wszystkich informacji widocznych na ekranie lub odtwarzanych na urządzeniu. Dotyczy to m.in. haseł, szczegółów płatności, zdjęć, wiadomości i odtwarzanych dźwięków."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Rozpocząć nagrywanie lub przesyłanie?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Rozpocząć nagrywanie lub przesyłanie za pomocą aplikacji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Zezwolić aplikacji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> na udostępnianie lub nagrywanie?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Cały ekran"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Pojedyncza aplikacja"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Dalej"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Udostępnianie i nagrywanie za pomocą aplikacji"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Zarządzaj"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index f85ab52..218fd95 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação contínua para uma sessão de gravação de tela"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Iniciar gravação?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durante a gravação, o sistema Android pode capturar informações confidenciais visíveis na tela ou tocadas no dispositivo. Isso inclui senhas, informações de pagamento, fotos, mensagens e áudio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gravar a tela inteira"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Gravar um app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Enquanto você grava, o Android tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Enquanto você grava um app, o Android tem acesso a todas as informações visíveis ou reproduzidas no app. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar gravação"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Gravar áudio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Áudio do dispositivo"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sons do dispositivo, como música, chamadas e toques"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"O serviço que oferece essa função terá acesso a todas as informações visíveis na tela ou reproduzidas durante uma gravação ou transmissão. Isso inclui senhas, detalhes de pagamento, fotos, mensagens e áudio."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Iniciar gravação ou transmissão?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Iniciar gravação ou transmissão com o app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Permitir que o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> compartilhe ou grave a tela?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tela cheia"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Um único app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quando você compartilha, grava ou transmite a tela, o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando você compartilha, grava ou transmite um app, o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartilhar ou gravar um app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index a33552b..085522a 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação persistente de uma sessão de gravação de ecrã"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Iniciar a gravação?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Enquanto estiver a gravar, o sistema Android pode capturar quaisquer informações confidenciais que estejam visíveis no ecrã ou que sejam reproduzidas no dispositivo. Isto inclui palavras-passe, informações de pagamento, fotos, mensagens e áudio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gravar o ecrã inteiro"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Gravar só uma app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Enquanto está a gravar, o Android tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Enquanto está a gravar uma app, o Android tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Começar gravação"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Gravar áudio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Áudio do dispositivo"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"O som do dispositivo, como música, chamadas e toques."</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"O serviço que fornece esta função terá acesso a todas as informações que estiverem visíveis no ecrã ou que forem reproduzidas a partir do dispositivo durante a gravação ou transmissão. Isto inclui informações como palavras-passe, detalhes de pagamentos, fotos, mensagens e áudio reproduzido."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Começar a gravar ou a transmitir?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Começar a gravar ou a transmitir com a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Permitir que a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> partilhe ou grave?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ecrã inteiro"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Só uma app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quando está a partilhar, gravar ou transmitir, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando está a partilhar, gravar ou transmitir uma app, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partilhe ou grave uma app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gerir"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
@@ -474,7 +486,7 @@
<string name="wallet_secondary_label_no_card" msgid="8488069304491125713">"Tocar para abrir"</string>
<string name="wallet_secondary_label_updating" msgid="5726130686114928551">"A atualizar"</string>
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para utilizar"</string>
- <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao obter os seus cartões. Tente novamente mais tarde."</string>
+ <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao obter os seus cartões. Tente mais tarde."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Definições do ecrã de bloqueio"</string>
<string name="qr_code_scanner_title" msgid="5290201053875420785">"Leia o código QR"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index f85ab52..218fd95 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação contínua para uma sessão de gravação de tela"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Iniciar gravação?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durante a gravação, o sistema Android pode capturar informações confidenciais visíveis na tela ou tocadas no dispositivo. Isso inclui senhas, informações de pagamento, fotos, mensagens e áudio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gravar a tela inteira"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Gravar um app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Enquanto você grava, o Android tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Enquanto você grava um app, o Android tem acesso a todas as informações visíveis ou reproduzidas no app. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar gravação"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Gravar áudio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Áudio do dispositivo"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sons do dispositivo, como música, chamadas e toques"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"O serviço que oferece essa função terá acesso a todas as informações visíveis na tela ou reproduzidas durante uma gravação ou transmissão. Isso inclui senhas, detalhes de pagamento, fotos, mensagens e áudio."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Iniciar gravação ou transmissão?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Iniciar gravação ou transmissão com o app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Permitir que o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> compartilhe ou grave a tela?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tela cheia"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Um único app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quando você compartilha, grava ou transmite a tela, o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando você compartilha, grava ou transmite um app, o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartilhar ou gravar um app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 7612dfc..0db2dd5 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificare în curs pentru o sesiune de înregistrare a ecranului"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Începi înregistrarea?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"În timpul înregistrării, sistemul Android poate captura informațiile sensibile vizibile pe ecran sau redate pe dispozitiv. Aici sunt incluse parole, informații de plată, fotografii, mesaje și conținut audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Înregistrează tot ecranul"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Înregistrează doar o aplicație"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Când înregistrezi, Android are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Când înregistrezi o aplicație, Android are acces la orice se afișează sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Începe înregistrarea"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Înregistrează audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Conținutul audio de la dispozitiv"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sunetul de la dispozitiv, precum muzică, apeluri și tonuri de sonerie"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Serviciul care oferă această funcție va avea acces la toate informațiile vizibile pe ecran sau redate pe dispozitiv în timp ce înregistrezi sau proiectezi. Între aceste informații se numără parole, detalii de plată, fotografii, mesaje și conținutul audio pe care îl redai."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Începi să înregistrezi sau să proiectezi?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Începi să înregistrezi sau să proiectezi cu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> poate permite accesul sau înregistra?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tot ecranul"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"O singură aplicație"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Când permiți accesul, înregistrezi sau proiectezi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice se afișează pe ecran sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuă"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Permite accesul la o aplicație sau înregistreaz-o"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestionează"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Istoric"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 5d804b9..7dcf873 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущее уведомление для записи видео с экрана"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Начать запись?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"В записи может появиться конфиденциальная информация, которая видна на экране или воспроизводится на устройстве, например пароли, сведения о платежах, фотографии, сообщения и аудиозаписи."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записывать весь экран"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записывать окно приложения"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Когда вы записываете видео с экрана, Android получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Когда вы записываете видео с окна приложения, Android получает доступ ко всему, что видно и воспроизводится в приложении. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Начать запись"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Записывать аудио"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук с устройства"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук с вашего устройства, например музыка, звонки и рингтоны"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Во время записи или трансляции у сервиса, предоставляющего эту функцию, будет доступ ко всей информации, которая видна на экране или воспроизводится на устройстве, включая пароли, сведения о платежах, фотографии, сообщения и звуки."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Начать запись или трансляцию?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Начать запись или трансляцию через приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\"?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Разрешить приложению \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" демонстрировать экран или записывать видео с него?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Весь экран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Отдельное приложение"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далее"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Демонстрация экрана или запись видео с него"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 17a6a19..de498cb 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"තිර පටිගත කිරීමේ සැසියක් සඳහා කෙරෙන දැනුම් දීම"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"පටිගත කිරීම ආරම්භ කරන්නද?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"පටිගත කරන අතරතුර, Android පද්ධතියට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය කරන ඕනෑම සංවේදී තොරතුරක් ග්රහණය කර ගැනීමට හැකිය. මෙයට මුරපද, ගෙවීම් තොරතුරු, ඡායාරූප, පණිවිඩ සහ ඕඩියෝ ඇතුළත් වේ."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"සම්පූර්ණ තිරය පටිගත කරන්න"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"තනි යෙදුමක් පටිගත කරන්න"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ඔබ පටිගත කරන අතරේ, Android හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ඔබ යෙදුමක් පටිගත කරන අතරේ, Android හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"පටිගත කිරීම අරඹන්න"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ඕඩියෝ පටිගත කරන්න"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"උපාංග ඕඩියෝ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"සංගීතය, ඇමතුම් සහ නාද රිද්ම වැනි ඔබේ උපාංගය වෙතින් ශබ්ද"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"මෙම ශ්රිතය සපයන සේවාවට පටිගත කරන හෝ විකාශ කරන අතරතුර ඔබේ තිරයේ දිස් වන හෝ ඔබේ උපාංගයෙන් වාදනය කරන සියලු තොරතුරු වෙත ප්රවේශය ලැබෙනු ඇත. මෙහි මුරපද, ගෙවීම් විස්තර, ඡායාරූප, පණිවිඩ සහ ඔබ වාදනය කරන ඕඩියෝ යනාදි තොරතුරු ඇතුළත් වේ."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"පටිගත කිරීම හෝ විකාශය කිරීම ආරම්භ කරන්නද?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> සමග පටිගත කිරීම හෝ විකාශය කිරීම ආරම්භ කරන්නද?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට බෙදා ගැනීමට හෝ පටිගත කිරීමට ඉඩ දෙන්න ද?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"සම්පූර්ණ තිරය"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"තනි යෙදුමක්"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශනය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ඉදිරියට යන්න"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"යෙදුමක් බෙදා ගන්න හෝ පටිගත කරන්න"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"කළමනාකරණය කරන්න"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ඉතිහාසය"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index cab5c4e..0818e07 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Zobrazuje sa upozornenie týkajúce sa relácie záznamu obrazovky"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Chcete spustiť nahrávanie?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Počas nahrávania zaznamená systém Android všetky citlivé údaje, ktoré sa zobrazia na obrazovke alebo prehrajú v zariadení. Zahrnuje to heslá, platobné údaje, fotky, správy a zvuky."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Nahrávať celú obrazovku"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Nahrávať jednu aplikáciu"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Počas nahrávania bude mať Android prístup k všetkému na obrazovke, prípadne k obsahu, ktorý sa bude v zariadení prehrávať. Preto venujte zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Počas nahrávania aplikácie bude mať Android prístup k všetkému obsahu, ktorý sa v nej bude zobrazovať alebo prehrávať. Preto venujte zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Spustiť nahrávanie"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Nahrávať zvuk"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk zariadenia"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvuk zo zariadenia, napríklad hudba, hovory a tóny zvonenia"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Služba poskytujúca túto funkciu bude mať prístup k všetkým informáciám zobrazovaným na obrazovke alebo prehrávaným v zariadení počas nahrávania či prenosu. Patria medzi ne informácie, akými sú napríklad heslá, platobné podrobnosti, fotky, správy a prehrávaný zvuk."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Chcete začať nahrávanie alebo prenos?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Chcete spustiť nahrávanie alebo prenos s aktivovaným povolením <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Chcete povoliť aplikácii <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> zdieľanie alebo nahrávanie?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Celá obrazovka"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Jedna aplikácia"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Počas zdieľania, nahrávania alebo prenosu bude mať <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> prístup k všetkému na obrazovke, prípadne k obsahu, ktorý sa bude v zariadení prehrávať. Preto venujte zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Počas zdieľania, nahrávania alebo prenosu bude mať aplikácia <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> prístup k všetkému obsahu, ktorý sa v nej bude zobrazovať alebo prehrávať. Preto venujte zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovať"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Aplikácia na zdieľanie alebo nahrávanie"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Spravovať"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"História"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 620d9b5..b31033f 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Nenehno obveščanje o seji snemanja zaslona"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Želite začeti snemati?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Med snemanjem lahko sistem Android zajame morebitne občutljive podatke, ki so prikazani na zaslonu ali se predvajajo v napravi. To vključuje gesla, podatke za plačilo, fotografije, sporočila in zvok."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Snemanje celotnega zaslona"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Snemanje posamezne aplikacije"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Med snemanjem ima Android dostop do vsega, kar je prikazano na zaslonu ali se predvaja v napravi. Zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Med snemanjem aplikacije ima Android dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Začni snemanje"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Snemanje zvoka"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvok v napravi"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvoki v napravi, kot so glasba, klici in toni zvonjenja."</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Storitev, ki zagotavlja to funkcijo, bo imela dostop do vseh podatkov, ki so med snemanjem ali predvajanjem prikazani na vašem zaslonu ali se predvajajo iz vaše naprave. To vključuje podatke, kot so gesla, podrobnosti o plačilu, fotografije, sporočila in zvok, ki ga predvajate."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Želite začeti snemati ali predvajati?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Želite začeti snemati ali predvajati z aplikacijo <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Ali aplikaciji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> dovolite deljenje ali snemanje?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Celoten zaslon"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Posamezna aplikacija"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Pri deljenju, snemanju ali predvajanju ima aplikacija <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> dostop do vsega, kar je prikazano na zaslonu ali se predvaja v napravi. Zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Pri deljenju, snemanju ali predvajanju aplikacije ima aplikacija <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Naprej"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deljenje ali snemanje aplikacije"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljaj"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Zgodovina"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index be38908..c1a83db 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Njoftim i vazhdueshëm për një seancë regjistrimi të ekranit"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Të niset regjistrimi?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Gjatë regjistrimit, sistemi Android mund të regjistrojë çdo informacion delikat që është i dukshëm në ekranin tënd ose që luhet në pajisje. Kjo përfshin fjalëkalimet, informacionin e pagesave, fotografitë, mesazhet dhe audion."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Regjistro të gjithë ekranin"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Regjistro vetëm një aplikacion"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Gjatë regjistrimit, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Gjatë regjistrimit të një aplikacioni, Android ka qasje te çdo gjë e dukshme ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Nis regjistrimin"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Regjistro audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audioja e pajisjes"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Tingulli nga pajisja, si muzika, telefonatat dhe tonet e ziles"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Shërbimi që e ofron këtë funksion do të ketë qasje te të gjitha informacionet që janë të dukshme në ekran ose që luhen nga pajisja jote gjatë regjistrimit ose transmetimit. Kjo përfshin informacione, si p.sh.: fjalëkalimet, detajet e pagesave, fotografitë, mesazhet dhe audion që luan ti."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Do të fillosh regjistrimin ose transmetimin?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Fillo regjistrimin ose transmetimin me <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Të lejohet <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> të shpërndajë ose regjistrojë?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ekran i plotë"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Vetëm një aplikacion"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Gjatë shpërndarjes, regjistrimit ose transmetimit, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Gjatë shpërndarjes, regjistrimit ose transmetimit të një aplikacioni, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Vazhdo"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shpërndaj ose regjistro një aplikacion"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Menaxho"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historiku"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index f7ef654..e380b0e 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Обавештење о сесији снимања екрана је активно"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Желите да започнете снимање?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Током снимања Android систем може да сними осетљиве информације које су видљиве на екрану или које се пуштају на уређају. То обухвата лозинке, информације о плаћању, слике, поруке и звук."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Снимај цео екран"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Снимај једну апликацију"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају док снимате. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Када снимате апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Започни снимање"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Снимај звук"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук уређаја"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук са уређаја, на пример, музика, позиви и мелодије звона"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Услуга која пружа ову функцију ће имати приступ свим информацијама које се приказују на екрану или репродукују са уређаја током снимања или пребацивања. То обухвата информације попут лозинки, информација о плаћању, слика, порука и звука који пуштате."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Желите да почнете снимање или пребацивање?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Желите да почнете снимање или пребацивање помоћу апликације <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Желите да дозволите дељење и снимање за <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Цео екран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Једна апликација"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Када делите, снимате или пребацујете, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Настави"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Делите или снимите апликацију"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Управљајте"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index d3bd2cd..1778b01 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Avisering om att skärminspelning pågår"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vill du starta inspelningen?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"När du spelar in kan Android-systemet registrera alla känsliga uppgifter som visas på skärmen eller spelas upp på enheten. Detta omfattar lösenord, betalningsuppgifter, foton, meddelanden och ljud."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Spela in hela skärmen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Spela in en enda app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"När du spelar in har Android åtkomst till allt som visas på skärmen eller spelas upp på enheten. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"När du spelar in en app har Android åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Börja spela in"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Spela in ljud"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Ljud på enheten"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Ljud från enheten, till exempel musik, samtal och ringsignaler"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Den tjänst som tillhandahåller funktionen får åtkomst till all information som visas på skärmen eller spelas upp från enheten när du spelar in eller castar. Detta omfattar uppgifter som lösenord, betalningsinformation, foton, meddelanden och ljud som du spelar upp."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vill du börja spela in eller casta?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vill du börja spela in eller casta med <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vill du tillåta att <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> delar eller spelar in?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hela skärmen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"En enda app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"När du delar, spelar in eller castar har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas på skärmen eller spelas upp på enheten. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"När du delar, spelar in eller castar en app har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsätt"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dela eller spela in en app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Hantera"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index e29fbd2..3ba8d30 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Arifa inayoendelea ya kipindi cha kurekodi skrini"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Ungependa kuanza kurekodi?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Wakati wa kurekodi, Mfumo wa Android unaweza kunasa maelezo yoyote nyeti yanayoonekana kwenye skrini au yanayochezwa kwenye kifaa chako. Hii ni pamoja na manenosiri, maelezo ya malipo, picha, ujumbe na sauti."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Rekodi skrini nzima"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Rekodi programu moja"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Unaporekodi, Android inaweza kufikia kitu chochote kitakachoonekana kwenye skrini yako au kuchezwa kwenye kifaa chako. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Unaporekodi programu, Android inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Anza kurekodi"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Rekodi sauti"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Sauti ya kifaa"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sauti kutoka kwenye kifaa chako, kama vile muziki, simu na milio ya simu"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Huduma inayotoa utendaji huu itaweza kufikia maelezo yote yanayoonekana kwenye skrini yako au yanayochezwa kwenye kifaa chako wakati wa kurekodi au kutuma. Hii ni pamoja na maelezo kama vile manenosiri, maelezo ya malipo, picha, ujumbe na sauti unayocheza."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Ungependa kuanza kurekodi au kutuma?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Ungependa kuanza kurekodi au kutuma ukitumia <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Ungependa kuruhusu programu ya <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ishiriki au kurekodi?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Skrini nzima"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Programu moja"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Unapotuma, kurekodi au kushiriki, programu ya <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> inaweza kufikia kitu chochote kitakachoonekana kwenye skrini yako au kuchezwa kwenye kifaa chako. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Unapotuma, kurekodi au kushiriki programu, programu ya <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Endelea"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shiriki au rekodi programu"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Dhibiti"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 5dcbeb5..599bf30 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -68,6 +68,7 @@
<dimen name="qs_security_footer_background_inset">0dp</dimen>
<dimen name="qs_panel_padding_top">8dp</dimen>
+ <dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
<dimen name="large_dialog_width">472dp</dimen>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 0550387..dbb90b3 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"திரை ரெக்கார்டிங் அமர்விற்கான தொடர் அறிவிப்பு"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ரெக்கார்டிங்கைத் தொடங்கவா?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ரெக்கார்டு செய்யும்போது, உங்கள் திரையில் தோன்றக்கூடிய அல்லது சாதனத்தில் பிளே ஆகக்கூடிய பாதுகாக்கப்பட வேண்டிய தகவலை Android சிஸ்டம் படமெடுக்க முடியும். கடவுச்சொற்கள், பேமெண்ட் தகவல், படங்கள், மெசேஜ்கள், ஆடியோ ஆகியவை இதில் அடங்கும்."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"முழு திரையை ரெக்கார்டு செய்தல்"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ஓர் ஆப்ஸை ரெக்கார்டு செய்தல்"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"நீங்கள் ரெக்கார்டு செய்யும்போது அந்தச் சாதனத்தில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ஓர் ஆப்ஸை நீங்கள் ரெக்கார்டு செய்யும்போது அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ரெக்கார்டிங்கைத் தொடங்கு"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ஆடியோவை ரெக்கார்டு செய்"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"சாதன ஆடியோ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"இசை, அழைப்புகள், ரிங்டோன்கள் போன்ற உங்கள் சாதனத்திலிருந்து வரும் ஒலி"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"இந்தச் செயல்பாட்டை வழங்கும் சேவையானது உங்கள் திரையில் தெரியும் தகவல்கள், ரெக்கார்டு செய்யும்போதோ அனுப்பும்போதோ உங்கள் சாதனத்திலிருந்து பிளே ஆகும் அனைத்துத் தகவல்கள் ஆகியவற்றுக்கான அணுகலைக் கொண்டிருக்கும். கடவுச்சொற்கள், பேமெண்ட் தொடர்பான தகவல்கள், படங்கள், மெசேஜ்கள், நீங்கள் பிளே செய்யும் ஆடியோ போன்ற அனைத்துத் தகவல்களும் இதில் அடங்கும்."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ரெக்கார்டிங் செய்யவோ அனுப்புவோ தொடங்கவா?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> மூலம் ரெக்கார்டிங் செய்யவோ அனுப்புவதற்கோ தொடங்கிவீட்டீர்களா?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"பகிர அல்லது ரெக்கார்டு செய்ய <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸை அனுமதிக்கலாமா?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"திரை முழுவதும்"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ஓர் ஆப்ஸ்"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ உங்கள் சாதனத்தில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ஓர் ஆப்ஸை நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"தொடர்க"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ஆப்ஸைப் பகிர்தல் அல்லது ரெக்கார்டு செய்தல்"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"நிர்வகி"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"இதுவரை வந்த அறிவிப்புகள்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 1e4fce6..42c24a2 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"స్క్రీన్ రికార్డ్ సెషన్ కోసం ఆన్గోయింగ్ నోటిఫికేషన్"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"రికార్డింగ్ను ప్రారంభించాలా?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"రికార్డ్ చేస్తున్నప్పుడు, Android సిస్టమ్ మీ స్క్రీన్పై ప్రదర్శించబడిన లేదా మీ పరికరం నుండి ప్లే చేయబడిన ఏ సున్నితమైన సమాచారాన్నయినా క్యాప్చర్ చేయగలదు. ఈ సమాచారంలో పాస్వర్డ్లు, పేమెంట్ వివరాలు, ఫోటోలు, మెసేజ్లు, ఆడియో కూడా ఉంటాయి."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ఫుల్ స్క్రీన్ రికార్డ్ చేయండి"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"సింగిల్ యాప్ రికార్డ్ చేయండి"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"మీరు రికార్డ్ చేసేటప్పుడు, మీ స్క్రీన్పై కనిపించే దేనికైనా లేదా మీ పరికరంలో ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి, పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"మీరు యాప్ను రికార్డ్ చేసేటప్పుడు, ఆ యాప్లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి, పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"రికార్డింగ్ను ప్రారంభించండి"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ఆడియోను రికార్డ్ చేయి"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"పరికరం ఆడియో"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"మీ పరికరం నుండి వచ్చే మ్యూజిక్, కాల్స్, రింగ్టోన్ల వంటి ధ్వనులు"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"రికార్డ్ చేస్తున్నప్పుడు లేదా ప్రసారం చేస్తున్నప్పుడు మీ స్క్రీన్పై ప్రదర్శించబడిన లేదా మీ పరికరం నుండి ప్లే చేయబడిన సమాచారం మొత్తాన్ని, ఈ ఫంక్షన్ను అందిస్తున్న సర్వీస్ యాక్సెస్ చేయగలదు. ఈ సమాచారంలో, పాస్వర్డ్లు, పేమెంట్ వివరాలు, ఫోటోలు, మెసేజ్లు, మీరు ప్లే చేసే ఆడియో వంటివి ఉంటాయి."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"రికార్డ్ చేయడం లేదా ప్రసారం చేయడం ప్రారంభించాలా?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>తో రికార్డ్ చేయడం లేదా ప్రసారం చేయడం ప్రారంభించాలా?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"షేర్ చేయడానికి లేదా రికార్డ్ చేయడానికి <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ను అనుమతించాలా?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ఫుల్-స్క్రీన్"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"సింగిల్ యాప్"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"మీరు షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, మీ స్క్రీన్పై కనిపించే దేనికైనా లేదా మీ పరికరంలో ప్లే అయిన దేనికైనా <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>కు యాక్సెస్ ఉంటుంది. కాబట్టి, పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"మీరు ఏదైనా యాప్ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>కు యాక్సెస్ ఉంటుంది. కాబట్టి, పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"కొనసాగించండి"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"యాప్ను షేర్ చేయండి లేదా రికార్డ్ చేయండి"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"మేనేజ్ చేయండి"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"హిస్టరీ"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index e2e84d7..8078ac9 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"การแจ้งเตือนต่อเนื่องสำหรับเซสชันการบันทึกหน้าจอ"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"เริ่มบันทึกเลยไหม"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ขณะบันทึก ระบบ Android อาจบันทึกข้อมูลที่ละเอียดอ่อนซึ่งปรากฏบนหน้าจอหรือเล่นในอุปกรณ์ได้ ซึ่งรวมถึงรหัสผ่าน ข้อมูลการชำระเงิน รูปภาพ ข้อความ และเสียง"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"บันทึกทั้งหน้าจอ"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"บันทึกแอปเดียว"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ขณะกำลังบันทึก Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่ปรากฏบนหน้าจอหรือเล่นอยู่ในอุปกรณ์ ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ขณะกำลังบันทึกแอป Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"เริ่มบันทึก"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"บันทึกเสียง"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"เสียงจากอุปกรณ์"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"เสียงจากอุปกรณ์ เช่น เพลง การโทร และเสียงเรียกเข้า"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"บริการที่มีฟังก์ชันนี้จะมีสิทธิ์เข้าถึงข้อมูลทั้งหมดที่ปรากฏบนหน้าจอหรือเปิดจากอุปกรณ์ของคุณขณะบันทึกหรือแคสต์ ซึ่งรวมถึงข้อมูลอย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน รูปภาพ ข้อความ และเสียงที่คุณเล่น"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"เริ่มบันทึกหรือแคสต์ใช่ไหม"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"เริ่มบันทึกหรือแคสต์ด้วย <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> เลยไหม"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"อนุญาตให้ \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" แชร์หรือบันทึกไหม"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ทั้งหน้าจอ"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"แอปเดียว"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"เมื่อกำลังแชร์ บันทึก หรือแคสต์ \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" จะมีสิทธิ์เข้าถึงทุกสิ่งที่ปรากฏบนหน้าจอหรือเล่นอยู่ในอุปกรณ์ ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ต่อไป"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"แชร์หรือบันทึกแอป"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"จัดการ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ประวัติ"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index c32aa99..f8d42b2 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Kasalukuyang notification para sa session ng pag-record ng screen"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Simulang Mag-record?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Habang nagre-record, puwedeng ma-capture ng Android System ang anumang sensitibong impormasyong nakikita sa iyong screen o nagpe-play sa device mo. Kasama dito ang mga password, impormasyon sa pagbabayad, mga larawan, mensahe, at audio."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"I-record ang buong screen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Mag-record ng isang app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Habang nagre-record ka, may access ang Android sa kahit anong nakikita sa iyong screen o pine-play sa device mo. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang sensitibong impormasyon."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Habang nagre-record ka ng app, may access ang Android sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang sensitibong impormasyon."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Simulang mag-record"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Mag-record ng audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio ng device"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Tunog mula sa iyong device, gaya ng musika, mga tawag, at ringtone"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ang serbisyong nagbibigay ng function na ito ay magkakaroon ng access sa lahat ng impormasyong nakikita sa iyong screen o pine-play mula sa device mo habang nagre-record o nagka-cast. Kasama rito ang impormasyong tulad ng mga password, detalye ng pagbabayad, larawan, mensahe, at audio na pine-play mo."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Magsimulang mag-record o mag-cast?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Simulang mag-record o mag-cast gamit ang <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Payagan ang <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> na magbahagi o mag-record?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Buong screen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Isang app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kapag nagbabahagi, nagre-record, o nagka-cast ka, may access ang <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sa kahit anong nakikita sa iyong screen o pine-play sa device mo. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang sensitibong impormasyon."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang impormasyon."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Magpatuloy"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Ibahagi o i-record ang isang app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Pamahalaan"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index d405540..a238f3f 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekran kaydı oturumu için devam eden bildirim"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Kayıt başlatılsın mı?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Kayıt sırasında Android Sistemi, ekranınızda görünen veya cihazınızda oynatılan hassas bilgileri yakalayabilir. Buna şifreler, ödeme bilgileri, fotoğraflar, mesajlar ve sesler dahildir."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Tüm ekranı kaydedin"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Tek bir uygulamayı kaydedin"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Kayıt özelliğini kullandığınızda Android, ekranınızda görünen veya cihazınızda oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Bir uygulamayı kaydetme özelliğini kullandığınızda Android, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Kaydı başlat"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Ses kaydet"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Cihaz sesi"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Müzik, aramalar, zil sesleri gibi cihazınızdan sesler"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu işlevi sağlayan hizmet, ekranınızda görünen veya kayıt ya da yayın sırasında cihazınızdan oynatılan tüm bilgilere erişecektir. Bu bilgiler arasında şifreler, ödeme detayları, fotoğraflar, mesajlar ve çaldığınız sesler gibi bilgiler yer alır."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Kayıt veya yayınlama başlatılsın mı?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ile kayıt veya yayınlama başlatılsın mı?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> uygulamasının paylaşmasına veya kaydetmesine izin verilsin mi?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tüm ekran"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Tek bir uygulama"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Paylaşım, kayıt ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, ekranınızda görünen veya cihazınızda oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Bir uygulamayı paylaşma, kaydetme ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Devam"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Uygulamayı paylaşın veya kaydedin"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Yönet"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Geçmiş"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 58803f4..d3f392a 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Сповіщення про сеанс запису екрана"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Почати запис?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Під час запису система Android може фіксувати будь-яку конфіденційну інформацію, яка з\'являється на екрані або відтворюється на пристрої, зокрема паролі, платіжну інформацію, фотографії, повідомлення та звуки."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записувати весь екран"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записувати окремий додаток"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Коли ви записуєте вміст екрана, ОС Android отримує доступ до всього, що відображається на ньому або відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Коли ви записуєте додаток, ОС Android отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Почати запис"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Записувати звук"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук із пристрою"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук із пристрою, зокрема музика, виклики та сигнали дзвінка"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Сервіс, що надає цю функцію, матиме доступ до всієї інформації, яка з\'являється на екрані або відтворюється на пристрої під час запису чи трансляції, зокрема до паролів, інформації про платежі, фотографій, повідомлень і аудіофайлів."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Почати запис або трансляцію?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Почати запис або трансляцію за допомогою додатка <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Дозволити додатку <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> показувати або записувати?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Увесь екран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Окремий додаток"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Коли ви показуєте, записуєте або транслюєте екран, додаток <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається на екрані чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Коли ви показуєте, записуєте або транслюєте додаток, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продовжити"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Показувати або записувати додаток"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Керувати"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Історія"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 3472290..1e71b14 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"اسکرین ریکارڈ سیشن کیلئے جاری اطلاع"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ریکارڈنگ شروع کریں؟"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ریکارڈ کرنے کے دوران، Android سسٹم آپ کی اسکرین پر نظر آنے والی یا آپ کے آلہ پر چلنے والی کسی بھی حساس معلومات کو کیپچر کر سکتا ہے۔ اس میں پاس ورڈز، ادائیگی کی معلومات، تصاویر، پیغامات اور آڈیو شامل ہیں۔"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"پوری اسکرین کو ریکارڈ کریں"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"واحد ایپ کو ریکارڈ کریں"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"جب آپ ریکارڈنگ کر رہے ہوتے ہیں تو Android کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"جب آپ کسی ایپ کو ریکارڈ کر رہے ہوتے ہیں تو Android کو اس ایپ پر دکھائی گئی یا چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ریکارڈنگ شروع کریں"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"آڈیو ریکارڈ کریں"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"آلہ کا آڈیو"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"آپ کے آلے سے آواز، جیسے موسیقی، کالز اور رِنگ ٹونز"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"یہ فنکشن فراہم کرنے والی سروس کو اس تمام معلومات تک رسائی حاصل ہوگی جو آپ کی اسکرین پر نظر آتی ہے یا ریکارڈنگ یا کاسٹنگ کے دوران آپ کے آلے سے چلائی جاتی ہے۔ اس میں پاس ورڈز، ادائیگی کی تفصیلات، تصاویر، پیغامات اور وہ آڈیو جو آپ چلاتے ہیں جیسی معلومات شامل ہے۔"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ریکارڈنگ یا کاسٹنگ شروع کریں؟"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کے ذریعے ریکارڈنگ یا کاسٹنگ شروع کریں؟"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو اشتراک یا ریکارڈ کرنے کی اجازت دیں؟"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"پوری اسکرین"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"واحد ایپ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی گئی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"جاری رکھیں"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ایپ کا اشتراک یا ریکارڈ کریں"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"نظم کریں"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"سرگزشت"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 0102c3cb..3ab1a92 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekrandan yozib olish seansi uchun joriy bildirishnoma"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Yozib olish boshlansinmi?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Yozib olishda Android tizimi ekraningizda koʻringan yoki qurilmangizda ijro etilgan maxfiy axborotni ham yozib olishi mumkin. Bunga parollar, toʻlovga oid axborot, suratlar, xabarlar va audio kiradi."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Butun ekranni yozib olish"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Bitta ilovani yozib olish"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Yozib olish vaqtida Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Yozib olish vaqtida Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Yozib olish"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Audio yozib olish"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Qurilmadagi audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Qurilmangizdagi musiqa, chaqiruvlar va ringtonlar kabi ovozlar"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu funksiyani taʼminlovchi xizmat ekranda chiqqan yoki yozib olish va translatsiya vaqtida ijro etilgan barcha axborotlarga ruxsat oladi. Bu axborotlar parollar, toʻlov tafsilotlari, rasmlar, xabarlar va ijro etilgan audiolardan iborat boʻlishi mumkin."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Yozib olish yoki translatsiya boshlansinmi?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> orqali yozib olish yoki translatsiya boshlansinmi?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ilovasida ulashish yoki yozib olish uchun ruxsat berilsinmi?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Butun ekran"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Bitta ilova"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Ulashish, yozib olish va translatsiya qilish vaqtida <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ilovasi ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Ulashish, yozib olish va translatsiya qilish vaqtida <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ilovasi ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Davom etish"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Ilovada ulashish yoki yozib olish"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Boshqarish"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Tarix"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index f8b5491..022c081 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Thông báo đang diễn ra về phiên ghi màn hình"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Bắt đầu ghi?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Trong khi ghi, Hệ thống Android có thể ghi lại mọi thông tin nhạy cảm xuất hiện trên màn hình hoặc phát trên thiết bị của bạn. Những thông tin này bao gồm mật khẩu, thông tin thanh toán, ảnh, thông báo và âm thanh."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Ghi toàn màn hình"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ghi một ứng dụng"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Khi bạn ghi, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện trên màn hình hoặc phát trên thiết bị của bạn. Vì vậy, hãy thận trọng để không làm lộ mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Khi bạn ghi một ứng dụng, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện hoặc phát trên thiết bị đó. Vì vậy, hãy thận trọng để không làm lộ mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Bắt đầu ghi"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Ghi âm"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Âm thanh trên thiết bị"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Âm thanh trên thiết bị, chẳng hạn như nhạc, cuộc gọi và nhạc chuông"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Dịch vụ cung cấp chức năng này có quyền truy cập vào tất cả các thông tin hiển thị trên màn hình của bạn hoặc phát trên thiết bị của bạn trong khi ghi âm/ghi hình hoặc truyền, bao gồm cả thông tin như mật khẩu, chi tiết thanh toán, ảnh, tin nhắn và âm thanh mà bạn phát."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Bắt đầu ghi âm/ghi hình hoặc truyền?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Bắt đầu ghi âm/ghi hình hoặc truyền bằng <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Cho phép <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> chia sẻ hoặc ghi?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Toàn màn hình"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Một ứng dụng"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Khi bạn chia sẻ, ghi hoặc truyền, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sẽ có quyền truy cập vào mọi nội dung xuất hiện trên màn hình hoặc phát trên thiết bị của bạn. Vì vậy, hãy thận trọng để không làm lộ mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sẽ có quyền truy cập vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng để không làm lộ mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Tiếp tục"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Chia sẻ hoặc ghi ứng dụng"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Quản lý"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Lịch sử"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 5cb91978..87821bf 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"持续显示屏幕录制会话通知"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"要开始录制吗?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"在录制内容时,Android 系统可以捕捉到您屏幕上显示或设备中播放的敏感信息,其中包括密码、付款信息、照片、消息和音频。"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"录制整个屏幕"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"录制单个应用"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"在您进行录制时,Android 可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"在您录制某个应用时,Android 可以访问此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"开始录制"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"录制音频"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"设备音频"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"设备发出的声音,例如音乐、通话和铃声"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"在录制或投放内容时,提供此功能的服务将可获取您屏幕上显示或设备中播放的所有信息,其中包括密码、付款明细、照片、消息以及您播放的音频等信息。"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要开始录制或投放内容吗?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"要开始使用<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>录制或投放内容吗?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允许 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或录制吗?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整个屏幕"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"单个应用"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问通过此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"继续"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或录制应用"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"历史记录"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index fd01945..c703619 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"持續顯示錄影畫面工作階段通知"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"要開始錄製嗎?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"錄影時,Android 系統可擷取螢幕上顯示或裝置播放的任何敏感資料,包括密碼、付款資料、相片、訊息和音訊。"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"錄製整個螢幕畫面"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"錄製單一應用程式"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"進行錄製時,Android 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"錄製應用程式時,Android 可存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"開始錄製"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"錄音"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"裝置音訊"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"裝置播放的音效,例如音樂、通話和鈴聲"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"在錄影或投放時,此功能的服務供應商可以存取螢幕顯示或裝置播放的任何資料,當中包括密碼、付款詳情、相片、訊息和播放的語音等。"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要開始錄影或投放嗎?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"要使用「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」開始錄影或投放嗎?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允許 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或錄製嗎?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整個螢幕畫面"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"單一應用程式"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"記錄"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 696b2d3..bbe4b6c 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"持續顯示螢幕畫面錄製工作階段通知"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"要開始錄製嗎?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"錄製螢幕畫面時,Android 系統可擷取螢幕上顯示或裝置播放的任何機密資訊,包括密碼、付款資訊、相片、訊息和音訊。"</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"錄製整個螢幕畫面"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"錄製單一應用程式"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"進行錄製時,Android 可以存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"錄製應用程式時,Android 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"開始錄製"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"錄音"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"裝置音訊"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"裝置所播放的音效,例如音樂、通話和鈴聲等等"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"在錄製或投放內容時,提供這項功能的服務可存取畫面上顯示的任何資訊或裝置播放的任何內容,包括密碼、付款詳情、相片、訊息和你播放的音訊。"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要開始錄製或投放內容嗎?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"要使用「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」開始錄製或投放內容嗎?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允許 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或錄製?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整個螢幕畫面"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"單一應用程式"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放應用程式時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"記錄"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 751a45f..0ba0733a 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -94,6 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Isaziso esiqhubekayo seseshini yokurekhoda isikrini"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Qala ukurekhoda?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Ngenkathi irekhoda, Isistimu ye-Android ingathatha noma iluphi ulwazi olubucayi olubonakal kusikrini sakho noma oludlalwa kudivayisi yakho. Lokhu kufaka phakathi amaphasiwedi, ulwazi lokukhokha, izithombe, imilayezo, nomsindo."</string>
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Rekhoda sonke isikrini"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Rekhoda i-app eyodwa"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Ngenkathi urekhoda, i-Android inokufinyelela kunoma yini ebonakalayo esikrinini sakho noma edlalwa kudivayisi yakho. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Ngenkathi urekhoda i-app, i-Android inokufinyelela kunoma yini eboniswayo noma edlalwayo kuleyo app. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Qala ukurekhoda"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Rekhoda umsindo"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Umsindo wedivayisi"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Umsindo ophuma kudivayisi yakho, njengomculo, amakholi, namathoni okukhala"</string>
@@ -361,6 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Isevisi enikezela ngalo msebenzi izothola ukufinyelela kulo lonke ulwazi olubonakalayo esikrinini sakho noma oludlalwa kusuka kudivayisi yakho ngenkathi urekhoda noma usakaza. Lokhu kubandakanya ulwazi olufana namaphasiwedi, imininingwane yenkokhelo, izithombe, imilayezo, nomsindo owudlalayo."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Qala ukurekhoda noma ukusakaza?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Qala ukurekhoda noma ukusakaza nge-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vumela i-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> yabelane noma irekhode?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Sonke isikrini"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"I-app eyodwa"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Uma wabelana, urekhoda, noma usakaza, i-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> inokufinyelela kunoma yini ebonakalayo kusikrini sakho noma edlalwa kudivayisi yakho. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Uma wabelana, urekhoda, noma usakaza i-app, i-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> inokufinyelela kunoma yini eboniswayo noma edlalwayo kuleyo app. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Qhubeka"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Yabelana noma rekhoda i-app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Phatha"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Umlando"</string>
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index c67ac8d..8221d78 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -18,6 +18,13 @@
<resources>
<!-- Whether to show the user switcher in quick settings when only a single user is present. -->
<bool name="qs_show_user_switcher_for_single_user">false</bool>
+
<!-- Whether to show a custom biometric prompt size-->
<bool name="use_custom_bp_size">false</bool>
+
+ <!-- Whether to enable clipping on Quick Settings -->
+ <bool name="qs_enable_clipping">true</bool>
+
+ <!-- Whether to enable transparent background for notification scrims -->
+ <bool name="notification_scrim_transparent">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 37549c9..9188ce0 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -337,9 +337,6 @@
have been scrolled off-screen. -->
<bool name="config_showNotificationShelf">true</bool>
- <!-- Whether or not the notifications should always fade as they are dismissed. -->
- <bool name="config_fadeNotificationsOnDismiss">false</bool>
-
<!-- Whether or not the fade on the notification is based on the amount that it has been swiped
off-screen. -->
<bool name="config_fadeDependingOnAmountSwiped">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f7019dc..2eebdc6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -559,7 +559,8 @@
<dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
<dimen name="qs_panel_elevation">4dp</dimen>
<dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
- <dimen name="qs_panel_padding_top">80dp</dimen>
+ <dimen name="qs_panel_padding_top">48dp</dimen>
+ <dimen name="qs_panel_padding_top_combined_headers">80dp</dimen>
<dimen name="qs_data_usage_text_size">14sp</dimen>
<dimen name="qs_data_usage_usage_text_size">36sp</dimen>
@@ -1056,9 +1057,8 @@
<!-- Media tap-to-transfer chip for receiver device -->
<dimen name="media_ttt_chip_size_receiver">100dp</dimen>
<dimen name="media_ttt_icon_size_receiver">95dp</dimen>
- <!-- Since the generic icon isn't circular, we need to scale it down so it still fits within
- the circular chip. -->
- <dimen name="media_ttt_generic_icon_size_receiver">70dp</dimen>
+ <!-- Add some padding for the generic icon so it doesn't go all the way to the border. -->
+ <dimen name="media_ttt_generic_icon_padding">12dp</dimen>
<dimen name="media_ttt_receiver_vert_translation">20dp</dimen>
<!-- Window magnification -->
@@ -1457,6 +1457,9 @@
<dimen name="media_projection_app_selector_icon_size">32dp</dimen>
<dimen name="media_projection_app_selector_recents_padding">16dp</dimen>
<dimen name="media_projection_app_selector_loader_size">32dp</dimen>
+ <dimen name="media_projection_app_selector_task_rounded_corners">10dp</dimen>
+ <dimen name="media_projection_app_selector_task_icon_size">24dp</dimen>
+ <dimen name="media_projection_app_selector_task_icon_margin">8dp</dimen>
<!-- Dream overlay related dimensions -->
<dimen name="dream_overlay_status_bar_height">60dp</dimen>
@@ -1566,4 +1569,9 @@
<dimen name="dream_overlay_status_bar_ambient_text_shadow_dx">0.5dp</dimen>
<dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen>
<dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen>
+
+ <!-- Default device corner radius, used for assist UI -->
+ <dimen name="config_rounded_mask_size">0px</dimen>
+ <dimen name="config_rounded_mask_size_top">0px</dimen>
+ <dimen name="config_rounded_mask_size_bottom">0px</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index ba5f67f..7ca42f7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -190,8 +190,9 @@
<item type="id" name="qqs_tile_layout"/>
<!-- The buttons in the Quick Settings footer actions.-->
- <item type="id" name="settings_button_container"/>
+ <item type="id" name="multi_user_switch"/>
<item type="id" name="pm_lite"/>
+ <item type="id" name="settings_button_container"/>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
index ffab3cd..12e0b9a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shared.animation
import android.view.View
+import android.view.View.LAYOUT_DIRECTION_RTL
import android.view.ViewGroup
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
@@ -58,9 +59,15 @@
// progress == 0 -> -translationMax
// progress == 1 -> 0
val xTrans = (progress - 1f) * translationMax
+ val rtlMultiplier =
+ if (rootView.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ -1
+ } else {
+ 1
+ }
viewsToTranslate.forEach { (view, direction, shouldBeAnimated) ->
if (shouldBeAnimated()) {
- view.get()?.translationX = xTrans * direction.multiplier
+ view.get()?.translationX = xTrans * direction.multiplier * rtlMultiplier
}
}
}
@@ -90,7 +97,7 @@
/** Direction of the animation. */
enum class Direction(val multiplier: Float) {
- LEFT(-1f),
- RIGHT(1f),
+ START(-1f),
+ END(1f),
}
}
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 d7a0b47..3efdc5a 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 @@
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 @@
* Updates display properties in order to calculate the initial position for the views
* Must be called before [registerViewForAnimation]
*/
- fun updateDisplayProperties() {
+ @JvmOverloads
+ fun updateDisplayProperties(@Rotation rotation: Int = windowManager.defaultDisplay.rotation) {
windowManager.defaultDisplay.getSize(screenSize)
// Simple implementation to get current fold orientation,
// this might not be correct on all devices
// TODO: use JetPack WindowManager library to get the fold orientation
- isVerticalFold = windowManager.defaultDisplay.rotation == Surface.ROTATION_0 ||
- windowManager.defaultDisplay.rotation == Surface.ROTATION_180
+ isVerticalFold = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
}
/**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index c2e7445..860a5da 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -58,6 +58,7 @@
private var lastOnTextChanged: CharSequence? = null
private var lastInvalidate: CharSequence? = null
private var lastTimeZoneChange: CharSequence? = null
+ private var lastAnimationCall: CharSequence? = null
private val time = Calendar.getInstance()
@@ -222,6 +223,7 @@
}
fun animateAppearOnLockscreen() {
+ lastAnimationCall = "${getTimestamp()} call=animateAppearOnLockscreen"
setTextStyle(
weight = dozingWeight,
textSize = -1f,
@@ -246,6 +248,7 @@
if (isAnimationEnabled && textAnimator == null) {
return
}
+ lastAnimationCall = "${getTimestamp()} call=animateFoldAppear"
setTextStyle(
weight = lockScreenWeightInternal,
textSize = -1f,
@@ -272,6 +275,7 @@
// Skip charge animation if dozing animation is already playing.
return
}
+ lastAnimationCall = "${getTimestamp()} call=animateCharge"
val startAnimPhase2 = Runnable {
setTextStyle(
weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -295,6 +299,7 @@
}
fun animateDoze(isDozing: Boolean, animate: Boolean) {
+ lastAnimationCall = "${getTimestamp()} call=animateDoze"
setTextStyle(
weight = if (isDozing) dozingWeight else lockScreenWeight,
textSize = -1f,
@@ -408,6 +413,11 @@
pw.println(" lastTimeZoneChange=$lastTimeZoneChange")
pw.println(" currText=$text")
pw.println(" currTimeContextDesc=$contentDescription")
+ pw.println(" lastAnimationCall=$lastAnimationCall")
+ pw.println(" dozingWeightInternal=$dozingWeightInternal")
+ pw.println(" lockScreenWeightInternal=$lockScreenWeightInternal")
+ pw.println(" dozingColor=$dozingColor")
+ pw.println(" lockScreenColor=$lockScreenColor")
pw.println(" time=$time")
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 38a3124..f03fee4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -22,7 +22,7 @@
import android.provider.Settings
import android.util.Log
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
@@ -33,7 +33,7 @@
import javax.inject.Inject
private val TAG = ClockRegistry::class.simpleName
-private val DEBUG = true
+private const val DEBUG = true
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
@@ -130,6 +130,10 @@
}
availableClocks[id] = ClockInfo(clock, provider)
+ if (DEBUG) {
+ Log.i(TAG, "Added ${clock.clockId}")
+ }
+
if (currentId == id) {
if (DEBUG) {
Log.i(TAG, "Current clock ($currentId) was connected")
@@ -143,6 +147,9 @@
val currentId = currentClockId
for (clock in provider.getClocks()) {
availableClocks.remove(clock.clockId)
+ if (DEBUG) {
+ Log.i(TAG, "Removed ${clock.clockId}")
+ }
if (currentId == clock.clockId) {
Log.w(TAG, "Current clock ($currentId) was disconnected")
@@ -161,7 +168,7 @@
fun getClockThumbnail(clockId: ClockId): Drawable? =
availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
- fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId)
+ fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
fun registerClockChangeListener(listener: ClockChangeListener) =
clockChangeListeners.add(listener)
@@ -169,11 +176,14 @@
fun unregisterClockChangeListener(listener: ClockChangeListener) =
clockChangeListeners.remove(listener)
- fun createCurrentClock(): Clock {
+ fun createCurrentClock(): ClockController {
val clockId = currentClockId
if (isEnabled && clockId.isNotEmpty()) {
val clock = createClock(clockId)
if (clock != null) {
+ if (DEBUG) {
+ Log.i(TAG, "Rendering clock $clockId")
+ }
return clock
} else {
Log.e(TAG, "Clock $clockId not found; using default")
@@ -183,7 +193,7 @@
return createClock(DEFAULT_CLOCK_ID)!!
}
- private fun createClock(clockId: ClockId): Clock? =
+ private fun createClock(clockId: ClockId): ClockController? =
availableClocks[clockId]?.provider?.createClock(clockId)
private data class ClockInfo(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
new file mode 100644
index 0000000..b887951
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.shared.clocks
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Color
+import android.icu.text.NumberFormat
+import android.util.TypedValue
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.shared.R
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+
+private val TAG = DefaultClockController::class.simpleName
+
+/**
+ * Controls the default clock visuals.
+ *
+ * This serves as an adapter between the clock interface and the AnimatableClockView used by the
+ * existing lockscreen clock.
+ */
+class DefaultClockController(
+ ctx: Context,
+ private val layoutInflater: LayoutInflater,
+ private val resources: Resources,
+) : ClockController {
+ override val smallClock: DefaultClockFaceController
+ override val largeClock: LargeClockFaceController
+ private val clocks: List<AnimatableClockView>
+
+ private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"))
+ private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong())
+ private val burmeseLineSpacing =
+ resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
+ private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
+
+ override val events: DefaultClockEvents
+ override lateinit var animations: DefaultClockAnimations
+ private set
+
+ init {
+ val parent = FrameLayout(ctx)
+ smallClock =
+ DefaultClockFaceController(
+ layoutInflater.inflate(R.layout.clock_default_small, parent, false)
+ as AnimatableClockView
+ )
+ largeClock =
+ LargeClockFaceController(
+ layoutInflater.inflate(R.layout.clock_default_large, parent, false)
+ as AnimatableClockView
+ )
+ clocks = listOf(smallClock.view, largeClock.view)
+
+ events = DefaultClockEvents()
+ animations = DefaultClockAnimations(0f, 0f)
+ events.onLocaleChanged(Locale.getDefault())
+ }
+
+ override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ largeClock.recomputePadding()
+ animations = DefaultClockAnimations(dozeFraction, foldFraction)
+ events.onColorPaletteChanged(resources)
+ events.onTimeZoneChanged(TimeZone.getDefault())
+ events.onTimeTick()
+ }
+
+ open inner class DefaultClockFaceController(
+ override val view: AnimatableClockView,
+ ) : ClockFaceController {
+ // MAGENTA is a placeholder, and will be assigned correctly in initialize
+ private var currentColor = Color.MAGENTA
+ private var isRegionDark = false
+
+ init {
+ view.setColors(currentColor, currentColor)
+ }
+
+ override val events =
+ object : ClockFaceEvents {
+ override fun onRegionDarknessChanged(isRegionDark: Boolean) {
+ this@DefaultClockFaceController.isRegionDark = isRegionDark
+ updateColor()
+ }
+ }
+
+ fun updateColor() {
+ val color =
+ if (isRegionDark) {
+ resources.getColor(android.R.color.system_accent1_100)
+ } else {
+ resources.getColor(android.R.color.system_accent2_600)
+ }
+
+ if (currentColor == color) {
+ return
+ }
+
+ currentColor = color
+ view.setColors(DOZE_COLOR, color)
+ view.animateAppearOnLockscreen()
+ }
+ }
+
+ inner class LargeClockFaceController(
+ view: AnimatableClockView,
+ ) : DefaultClockFaceController(view) {
+ fun recomputePadding() {
+ val lp = view.getLayoutParams() as FrameLayout.LayoutParams
+ lp.topMargin = (-0.5f * view.bottom).toInt()
+ view.setLayoutParams(lp)
+ }
+ }
+
+ inner class DefaultClockEvents : ClockEvents {
+ override fun onTimeTick() = clocks.forEach { it.refreshTime() }
+
+ override fun onTimeFormatChanged(is24Hr: Boolean) =
+ clocks.forEach { it.refreshFormat(is24Hr) }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) =
+ clocks.forEach { it.onTimeZoneChanged(timeZone) }
+
+ override fun onFontSettingChanged() {
+ smallClock.view.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
+ )
+ largeClock.view.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
+ )
+ largeClock.recomputePadding()
+ }
+
+ override fun onColorPaletteChanged(resources: Resources) {
+ largeClock.updateColor()
+ smallClock.updateColor()
+ }
+
+ override fun onLocaleChanged(locale: Locale) {
+ val nf = NumberFormat.getInstance(locale)
+ if (nf.format(FORMAT_NUMBER.toLong()) == burmeseNumerals) {
+ clocks.forEach { it.setLineSpacingScale(burmeseLineSpacing) }
+ } else {
+ clocks.forEach { it.setLineSpacingScale(defaultLineSpacing) }
+ }
+
+ clocks.forEach { it.refreshFormat() }
+ }
+ }
+
+ inner class DefaultClockAnimations(
+ dozeFraction: Float,
+ foldFraction: Float,
+ ) : ClockAnimations {
+ private var foldState = AnimationState(0f)
+ private var dozeState = AnimationState(0f)
+
+ init {
+ dozeState = AnimationState(dozeFraction)
+ foldState = AnimationState(foldFraction)
+
+ if (foldState.isActive) {
+ clocks.forEach { it.animateFoldAppear(false) }
+ } else {
+ clocks.forEach { it.animateDoze(dozeState.isActive, false) }
+ }
+ }
+
+ override fun enter() {
+ if (!dozeState.isActive) {
+ clocks.forEach { it.animateAppearOnLockscreen() }
+ }
+ }
+
+ override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
+
+ override fun fold(fraction: Float) {
+ val (hasChanged, hasJumped) = foldState.update(fraction)
+ if (hasChanged) {
+ clocks.forEach { it.animateFoldAppear(!hasJumped) }
+ }
+ }
+
+ override fun doze(fraction: Float) {
+ val (hasChanged, hasJumped) = dozeState.update(fraction)
+ if (hasChanged) {
+ clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
+ }
+ }
+ }
+
+ private class AnimationState(
+ var fraction: Float,
+ ) {
+ var isActive: Boolean = fraction < 0.5f
+ fun update(newFraction: Float): Pair<Boolean, Boolean> {
+ val wasActive = isActive
+ val hasJumped =
+ (fraction == 0f && newFraction == 1f) || (fraction == 1f && newFraction == 0f)
+ isActive = newFraction > fraction
+ fraction = newFraction
+ return Pair(wasActive != isActive, hasJumped)
+ }
+ }
+
+ override fun dump(pw: PrintWriter) {
+ pw.print("smallClock=")
+ smallClock.view.dump(pw)
+
+ pw.print("largeClock=")
+ largeClock.view.dump(pw)
+ }
+
+ companion object {
+ @VisibleForTesting const val DOZE_COLOR = Color.WHITE
+ private const val FORMAT_NUMBER = 1234567890
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 19ac2e4..6627c5d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -15,24 +15,14 @@
import android.content.Context
import android.content.res.Resources
-import android.graphics.Color
import android.graphics.drawable.Drawable
-import android.icu.text.NumberFormat
-import android.util.TypedValue
import android.view.LayoutInflater
-import android.widget.FrameLayout
-import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.ClockAnimations
-import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
import com.android.systemui.shared.R
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
import javax.inject.Inject
private val TAG = DefaultClockProvider::class.simpleName
@@ -48,11 +38,12 @@
override fun getClocks(): List<ClockMetadata> =
listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
- override fun createClock(id: ClockId): Clock {
+ override fun createClock(id: ClockId): ClockController {
if (id != DEFAULT_CLOCK_ID) {
throw IllegalArgumentException("$id is unsupported by $TAG")
}
- return DefaultClock(ctx, layoutInflater, resources)
+
+ return DefaultClockController(ctx, layoutInflater, resources)
}
override fun getClockThumbnail(id: ClockId): Drawable? {
@@ -64,190 +55,3 @@
return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
}
}
-
-/**
- * Controls the default clock visuals.
- *
- * This serves as an adapter between the clock interface and the
- * AnimatableClockView used by the existing lockscreen clock.
- */
-class DefaultClock(
- ctx: Context,
- private val layoutInflater: LayoutInflater,
- private val resources: Resources
-) : Clock {
- override val smallClock: AnimatableClockView
- override val largeClock: AnimatableClockView
- private val clocks get() = listOf(smallClock, largeClock)
-
- private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"))
- private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong())
- private val burmeseLineSpacing =
- resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
- private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
-
- override val events: ClockEvents
- override lateinit var animations: ClockAnimations
- private set
-
- private var smallRegionDarkness = false
- private var largeRegionDarkness = false
-
- init {
- val parent = FrameLayout(ctx)
-
- smallClock = layoutInflater.inflate(
- R.layout.clock_default_small,
- parent,
- false
- ) as AnimatableClockView
-
- largeClock = layoutInflater.inflate(
- R.layout.clock_default_large,
- parent,
- false
- ) as AnimatableClockView
-
- events = DefaultClockEvents()
- animations = DefaultClockAnimations(0f, 0f)
-
- events.onLocaleChanged(Locale.getDefault())
-
- // DOZE_COLOR is a placeholder, and will be assigned correctly in initialize
- clocks.forEach { it.setColors(DOZE_COLOR, DOZE_COLOR) }
- }
-
- override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
- recomputePadding()
- animations = DefaultClockAnimations(dozeFraction, foldFraction)
- events.onColorPaletteChanged(resources, true, true)
- events.onTimeZoneChanged(TimeZone.getDefault())
- events.onTimeTick()
- }
-
- inner class DefaultClockEvents() : ClockEvents {
- override fun onTimeTick() = clocks.forEach { it.refreshTime() }
-
- override fun onTimeFormatChanged(is24Hr: Boolean) =
- clocks.forEach { it.refreshFormat(is24Hr) }
-
- override fun onTimeZoneChanged(timeZone: TimeZone) =
- clocks.forEach { it.onTimeZoneChanged(timeZone) }
-
- override fun onFontSettingChanged() {
- smallClock.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
- )
- largeClock.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
- )
- recomputePadding()
- }
-
- override fun onColorPaletteChanged(
- resources: Resources,
- smallClockIsDark: Boolean,
- largeClockIsDark: Boolean
- ) {
- if (smallRegionDarkness != smallClockIsDark) {
- smallRegionDarkness = smallClockIsDark
- updateClockColor(smallClock, smallClockIsDark)
- }
- if (largeRegionDarkness != largeClockIsDark) {
- largeRegionDarkness = largeClockIsDark
- updateClockColor(largeClock, largeClockIsDark)
- }
- }
-
- override fun onLocaleChanged(locale: Locale) {
- val nf = NumberFormat.getInstance(locale)
- if (nf.format(FORMAT_NUMBER.toLong()) == burmeseNumerals) {
- clocks.forEach { it.setLineSpacingScale(burmeseLineSpacing) }
- } else {
- clocks.forEach { it.setLineSpacingScale(defaultLineSpacing) }
- }
-
- clocks.forEach { it.refreshFormat() }
- }
- }
-
- inner class DefaultClockAnimations(
- dozeFraction: Float,
- foldFraction: Float
- ) : ClockAnimations {
- private var foldState = AnimationState(0f)
- private var dozeState = AnimationState(0f)
-
- init {
- dozeState = AnimationState(dozeFraction)
- foldState = AnimationState(foldFraction)
-
- if (foldState.isActive) {
- clocks.forEach { it.animateFoldAppear(false) }
- } else {
- clocks.forEach { it.animateDoze(dozeState.isActive, false) }
- }
- }
-
- override fun enter() {
- if (!dozeState.isActive) {
- clocks.forEach { it.animateAppearOnLockscreen() }
- }
- }
-
- override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
-
- override fun fold(fraction: Float) {
- val (hasChanged, hasJumped) = foldState.update(fraction)
- if (hasChanged) {
- clocks.forEach { it.animateFoldAppear(!hasJumped) }
- }
- }
-
- override fun doze(fraction: Float) {
- val (hasChanged, hasJumped) = dozeState.update(fraction)
- if (hasChanged) {
- clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
- }
- }
- }
-
- private class AnimationState(
- var fraction: Float
- ) {
- var isActive: Boolean = fraction < 0.5f
- fun update(newFraction: Float): Pair<Boolean, Boolean> {
- val wasActive = isActive
- val hasJumped = (fraction == 0f && newFraction == 1f) ||
- (fraction == 1f && newFraction == 0f)
- isActive = newFraction > fraction
- fraction = newFraction
- return Pair(wasActive != isActive, hasJumped)
- }
- }
-
- private fun updateClockColor(clock: AnimatableClockView, isRegionDark: Boolean) {
- val color = if (isRegionDark) {
- resources.getColor(android.R.color.system_accent1_100)
- } else {
- resources.getColor(android.R.color.system_accent2_600)
- }
- clock.setColors(DOZE_COLOR, color)
- clock.animateAppearOnLockscreen()
- }
-
- private fun recomputePadding() {
- val lp = largeClock.getLayoutParams() as FrameLayout.LayoutParams
- lp.topMargin = (-0.5f * largeClock.bottom).toInt()
- largeClock.setLayoutParams(lp)
- }
-
- override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) }
-
- companion object {
- @VisibleForTesting const val DOZE_COLOR = Color.WHITE
- private const val FORMAT_NUMBER = 1234567890
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 2111df5..647dd47 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -27,6 +27,8 @@
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -233,17 +235,14 @@
@ViewDebug.ExportedProperty(category="recents")
public boolean isLocked;
+ public Point positionInParent;
+
+ public Rect appBounds;
+
// Last snapshot data, only used for recent tasks
public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
- /**
- * Indicates that this task for the desktop tile in recents.
- *
- * Used when desktop mode feature is enabled.
- */
- public boolean desktopTile;
-
public Task() {
// Do nothing
}
@@ -274,7 +273,8 @@
this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
other.isLocked, other.taskDescription, other.topActivity);
lastSnapshotData.set(other.lastSnapshotData);
- desktopTile = other.desktopTile;
+ positionInParent = other.positionInParent;
+ appBounds = other.appBounds;
}
/**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 22bffda..6087655 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -132,8 +132,11 @@
mMainThreadHandler.postAtFrontOfQueue(() -> {
// If the screen rotation changes while locked, potentially update lock to flow with
// new screen rotation and hide any showing suggestions.
- if (isRotationLocked()) {
- if (shouldOverrideUserLockPrefs(rotation)) {
+ boolean rotationLocked = isRotationLocked();
+ // The isVisible check makes the rotation button disappear when we are not locked
+ // (e.g. for tabletop auto-rotate).
+ if (rotationLocked || mRotationButton.isVisible()) {
+ if (shouldOverrideUserLockPrefs(rotation) && rotationLocked) {
setRotationLockedAtAngle(rotation);
}
setRotateSuggestionButtonState(false /* visible */, true /* forced */);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 916526d..ce337bb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -19,7 +19,6 @@
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import static android.app.ActivityTaskManager.getService;
import android.annotation.NonNull;
@@ -27,7 +26,6 @@
import android.app.Activity;
import android.app.ActivityClient;
import android.app.ActivityManager;
-import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -252,8 +250,9 @@
public boolean startActivityFromRecents(int taskId, ActivityOptions options) {
try {
Bundle optsBundle = options == null ? null : options.toBundle();
- getService().startActivityFromRecents(taskId, optsBundle);
- return true;
+ return ActivityManager.isStartResultSuccessful(
+ getService().startActivityFromRecents(
+ taskId, optsBundle));
} catch (Exception e) {
return false;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 85278dd..f2742b7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -43,27 +43,8 @@
public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
- // See IPip.aidl
- public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
- // See ISplitScreen.aidl
- public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
- // See IFloatingTasks.aidl
- public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
- // See IOneHanded.aidl
- public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
- // See IShellTransitions.aidl
- public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
- "extra_shell_shell_transitions";
- // See IStartingWindow.aidl
- public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
- "extra_shell_starting_window";
// See ISysuiUnlockAnimationController.aidl
public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
- // See IRecentTasks.aidl
- public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
- public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
- // See IDesktopMode.aidl
- public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 7c3b5fc..2d6bef5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -60,7 +60,7 @@
public static final int ACTIVITY_TYPE_ASSISTANT = WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
public final int activityType;
- public int taskId;
+ public final int taskId;
public final SurfaceControl leash;
public final boolean isTranslucent;
public final Rect clipRect;
@@ -72,7 +72,7 @@
public final Rect startScreenSpaceBounds;
public final boolean isNotInRecents;
public final Rect contentInsets;
- public ActivityManager.RunningTaskInfo taskInfo;
+ public final ActivityManager.RunningTaskInfo taskInfo;
public final boolean allowEnterPip;
public final int rotationChange;
public final int windowType;
@@ -102,7 +102,7 @@
activityType = app.windowConfiguration.getActivityType();
taskInfo = app.taskInfo;
allowEnterPip = app.allowEnterPip;
- rotationChange = 0;
+ rotationChange = app.rotationChange;
mStartLeash = app.startLeash;
windowType = app.windowType;
@@ -131,6 +131,7 @@
isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType
);
target.setWillShowImeOnTarget(willShowImeOnTarget);
+ target.setRotationChange(rotationChange);
return target;
}
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 ec938b2..aca9907 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 @@
*/
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 @@
}
override fun destroy() {
- try {
- windowManagerInterface.removeRotationWatcher(rotationWatcher)
- } catch (e: RemoteException) {
- e.rethrowFromSystemServer()
- }
-
+ rotationChangeProvider.removeCallback(rotationListener)
scopedUnfoldTransitionProgressProvider.destroy()
}
@@ -76,10 +64,4 @@
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-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 74bd9c6..bb3df8f 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.os.Handler
+import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
import com.android.systemui.util.settings.SettingsUtilModule
@@ -27,6 +28,7 @@
import javax.inject.Named
@Module(includes = [
+ FeatureFlagsDebugStartableModule::class,
ServerFlagReaderModule::class,
SettingsUtilModule::class,
])
@@ -46,5 +48,15 @@
@Provides
@Named(ALL_FLAGS)
fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags()
+
+ @JvmStatic
+ @Provides
+ fun providesRestarter(barService: IStatusBarService): Restarter {
+ return object: Restarter {
+ override fun restart() {
+ barService.restart()
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 38b5c9a..0f7e732 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -16,11 +16,29 @@
package com.android.systemui.flags
+import com.android.internal.statusbar.IStatusBarService
import dagger.Binds
import dagger.Module
+import dagger.Provides
-@Module(includes = [ServerFlagReaderModule::class])
+@Module(includes = [
+ FeatureFlagsReleaseStartableModule::class,
+ ServerFlagReaderModule::class
+])
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ fun providesRestarter(barService: IStatusBarService): Restarter {
+ return object: Restarter {
+ override fun restart() {
+ barService.restart()
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index 00f1c01..207f344 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -15,6 +15,12 @@
*/
package com.android.keyguard;
+import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.START;
+import static androidx.constraintlayout.widget.ConstraintSet.TOP;
+
import android.annotation.Nullable;
import android.app.admin.IKeyguardCallback;
import android.app.admin.IKeyguardClient;
@@ -30,7 +36,10 @@
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
-import android.view.ViewGroup;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -49,7 +58,7 @@
private static final int REMOTE_CONTENT_READY_TIMEOUT_MILLIS = 500;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final Context mContext;
- private final ViewGroup mParent;
+ private final ConstraintLayout mParent;
private AdminSecurityView mView;
private Handler mHandler;
private IKeyguardClient mClient;
@@ -156,6 +165,7 @@
mUpdateMonitor = updateMonitor;
mKeyguardCallback = callback;
mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
+ mView.setId(View.generateViewId());
}
/**
@@ -167,6 +177,15 @@
}
if (!mView.isAttachedToWindow()) {
mParent.addView(mView);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mParent);
+ constraintSet.connect(mView.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.connect(mView.getId(), START, PARENT_ID, START);
+ constraintSet.connect(mView.getId(), END, PARENT_ID, END);
+ constraintSet.connect(mView.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.constrainHeight(mView.getId(), ConstraintSet.MATCH_CONSTRAINT);
+ constraintSet.constrainWidth(mView.getId(), ConstraintSet.MATCH_CONSTRAINT);
+ constraintSet.applyTo(mParent);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
deleted file mode 100644
index 6064be9..0000000
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.icu.text.NumberFormat;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settingslib.Utils;
-import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.ViewController;
-
-import java.io.PrintWriter;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.TimeZone;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-/**
- * Controller for an AnimatableClockView on the keyguard. Instantiated by
- * {@link KeyguardClockSwitchController}.
- */
-public class AnimatableClockController extends ViewController<AnimatableClockView> {
- private static final String TAG = "AnimatableClockCtrl";
- private static final int FORMAT_NUMBER = 1234567890;
-
- private final StatusBarStateController mStatusBarStateController;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final BatteryController mBatteryController;
- private final int mDozingColor = Color.WHITE;
- private Optional<RegionSamplingHelper> mRegionSamplingHelper = Optional.empty();
- private Rect mSamplingBounds = new Rect();
- private int mLockScreenColor;
- private final boolean mRegionSamplingEnabled;
-
- private boolean mIsDozing;
- private boolean mIsCharging;
- private float mDozeAmount;
- boolean mKeyguardShowing;
- private Locale mLocale;
-
- private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"));
- private final String mBurmeseNumerals;
- private final float mBurmeseLineSpacing;
- private final float mDefaultLineSpacing;
-
- @Inject
- public AnimatableClockController(
- AnimatableClockView view,
- StatusBarStateController statusBarStateController,
- BroadcastDispatcher broadcastDispatcher,
- BatteryController batteryController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- @Main Resources resources,
- @Main Executor mainExecutor,
- @Background Executor bgExecutor,
- FeatureFlags featureFlags
- ) {
- super(view);
- mStatusBarStateController = statusBarStateController;
- mBroadcastDispatcher = broadcastDispatcher;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mBatteryController = batteryController;
-
- mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
- mBurmeseLineSpacing = resources.getFloat(
- R.dimen.keyguard_clock_line_spacing_scale_burmese);
- mDefaultLineSpacing = resources.getFloat(
- R.dimen.keyguard_clock_line_spacing_scale);
-
- mRegionSamplingEnabled = featureFlags.isEnabled(Flags.REGION_SAMPLING);
- if (!mRegionSamplingEnabled) {
- return;
- }
-
- mRegionSamplingHelper = Optional.of(new RegionSamplingHelper(mView,
- new RegionSamplingHelper.SamplingCallback() {
- @Override
- public void onRegionDarknessChanged(boolean isRegionDark) {
- if (isRegionDark) {
- mLockScreenColor = Color.WHITE;
- } else {
- mLockScreenColor = Color.BLACK;
- }
- initColors();
- }
-
- @Override
- public Rect getSampledRegion(View sampledView) {
- mSamplingBounds = new Rect(sampledView.getLeft(), sampledView.getTop(),
- sampledView.getRight(), sampledView.getBottom());
- return mSamplingBounds;
- }
-
- @Override
- public boolean isSamplingEnabled() {
- return mRegionSamplingEnabled;
- }
- }, mainExecutor, bgExecutor)
- );
- mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
- regionSamplingHelper.setWindowVisible(true);
- });
- }
-
- private void reset() {
- mView.animateDoze(mIsDozing, false);
- }
-
- private final BatteryController.BatteryStateChangeCallback mBatteryCallback =
- new BatteryController.BatteryStateChangeCallback() {
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- if (mKeyguardShowing && !mIsCharging && charging) {
- mView.animateCharge(mStatusBarStateController::isDozing);
- }
- mIsCharging = charging;
- }
- };
-
- private final BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- updateLocale();
- }
- };
-
- private final StatusBarStateController.StateListener mStatusBarStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onDozeAmountChanged(float linear, float eased) {
- boolean noAnimation = (mDozeAmount == 0f && linear == 1f)
- || (mDozeAmount == 1f && linear == 0f);
- boolean isDozing = linear > mDozeAmount;
- mDozeAmount = linear;
- if (mIsDozing != isDozing) {
- mIsDozing = isDozing;
- mView.animateDoze(mIsDozing, !noAnimation);
- }
- }
- };
-
- private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mKeyguardShowing = showing;
- if (!mKeyguardShowing) {
- // reset state (ie: after weight animations)
- reset();
- }
- }
-
- @Override
- public void onTimeFormatChanged(String timeFormat) {
- mView.refreshFormat();
- }
-
- @Override
- public void onTimeZoneChanged(TimeZone timeZone) {
- mView.onTimeZoneChanged(timeZone);
- }
-
- @Override
- public void onUserSwitchComplete(int userId) {
- mView.refreshFormat();
- }
- };
-
- @Override
- protected void onInit() {
- mIsDozing = mStatusBarStateController.isDozing();
- }
-
- @Override
- protected void onViewAttached() {
- updateLocale();
- mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
- new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
- mDozeAmount = mStatusBarStateController.getDozeAmount();
- mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0;
- mBatteryController.addCallback(mBatteryCallback);
- mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
-
- mStatusBarStateController.addCallback(mStatusBarStateListener);
-
- mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
- regionSamplingHelper.start(mSamplingBounds);
- });
-
- mView.onTimeZoneChanged(TimeZone.getDefault());
- initColors();
- mView.animateDoze(mIsDozing, false);
- }
-
- @Override
- protected void onViewDetached() {
- mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
- mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
- mBatteryController.removeCallback(mBatteryCallback);
- mStatusBarStateController.removeCallback(mStatusBarStateListener);
- mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
- regionSamplingHelper.stop();
- });
- }
-
- /** Animate the clock appearance */
- public void animateAppear() {
- if (!mIsDozing) mView.animateAppearOnLockscreen();
- }
-
- /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
- * fully folded state and it goes to sleep (always on display screen) */
- public void animateFoldAppear() {
- mView.animateFoldAppear(true);
- }
-
- /**
- * Updates the time for the view.
- */
- public void refreshTime() {
- mView.refreshTime();
- }
-
- /**
- * Return locallly stored dozing state.
- */
- @VisibleForTesting
- public boolean isDozing() {
- return mIsDozing;
- }
-
- private void updateLocale() {
- Locale currLocale = Locale.getDefault();
- if (!Objects.equals(currLocale, mLocale)) {
- mLocale = currLocale;
- NumberFormat nf = NumberFormat.getInstance(mLocale);
- if (nf.format(FORMAT_NUMBER).equals(mBurmeseNumerals)) {
- mView.setLineSpacingScale(mBurmeseLineSpacing);
- } else {
- mView.setLineSpacingScale(mDefaultLineSpacing);
- }
- mView.refreshFormat();
- }
- }
-
- private void initColors() {
- if (!mRegionSamplingEnabled) {
- mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
- com.android.systemui.R.attr.wallpaperTextColorAccent);
- }
- mView.setColors(mDozingColor, mLockScreenColor);
- mView.animateDoze(mIsDozing, false);
- }
-
- /**
- * Dump information for debugging
- */
- public void dump(@NonNull PrintWriter pw) {
- pw.println(this);
- mView.dump(pw);
- mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
- regionSamplingHelper.dump(pw);
- });
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 907943a..7971e84 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -293,7 +293,7 @@
}
protected List<SubscriptionInfo> getSubscriptionInfo() {
- return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+ return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo();
}
protected void updateCarrierText() {
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index b444f4c..910955a 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -23,12 +23,18 @@
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.flags.Flags.REGION_SAMPLING
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ClockController
import com.android.systemui.shared.regionsampling.RegionSamplingInstance
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
@@ -38,24 +44,31 @@
import java.util.TimeZone
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
* [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
*/
open class ClockEventController @Inject constructor(
- private val statusBarStateController: StatusBarStateController,
- private val broadcastDispatcher: BroadcastDispatcher,
- private val batteryController: BatteryController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val configurationController: ConfigurationController,
- @Main private val resources: Resources,
- private val context: Context,
- @Main private val mainExecutor: Executor,
- @Background private val bgExecutor: Executor,
- private val featureFlags: FeatureFlags
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val batteryController: BatteryController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val configurationController: ConfigurationController,
+ @Main private val resources: Resources,
+ private val context: Context,
+ @Main private val mainExecutor: Executor,
+ @Background private val bgExecutor: Executor,
+ private val featureFlags: FeatureFlags
) {
- var clock: Clock? = null
+ var clock: ClockController? = null
set(value) {
field = value
if (value != null) {
@@ -69,47 +82,50 @@
private var isCharging = false
private var dozeAmount = 0f
- private var isKeyguardShowing = false
+ private var isKeyguardVisible = false
+ private var isRegistered = false
+ private var disposableHandle: DisposableHandle? = null
+ private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
- private val regionSamplingEnabled =
- featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
-
- private val updateFun = object : RegionSamplingInstance.UpdateColorCallback {
- override fun updateColors() {
- if (regionSamplingEnabled) {
- smallClockIsDark = smallRegionSamplingInstance.currentRegionDarkness().isDark
- largeClockIsDark = largeRegionSamplingInstance.currentRegionDarkness().isDark
- } else {
- val isLightTheme = TypedValue()
- context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
- smallClockIsDark = isLightTheme.data == 0
- largeClockIsDark = isLightTheme.data == 0
- }
- clock?.events?.onColorPaletteChanged(resources, smallClockIsDark, largeClockIsDark)
+ private fun updateColors() {
+ if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
+ smallClockIsDark = smallRegionSampler!!.currentRegionDarkness().isDark
+ largeClockIsDark = largeRegionSampler!!.currentRegionDarkness().isDark
+ } else {
+ val isLightTheme = TypedValue()
+ context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
+ smallClockIsDark = isLightTheme.data == 0
+ largeClockIsDark = isLightTheme.data == 0
}
+
+ clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark)
+ clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark)
}
- fun updateRegionSamplers(currentClock: Clock?) {
- smallRegionSamplingInstance = createRegionSampler(
- currentClock?.smallClock,
+ private fun updateRegionSamplers(currentClock: ClockController?) {
+ smallRegionSampler?.stopRegionSampler()
+ largeRegionSampler?.stopRegionSampler()
+
+ smallRegionSampler = createRegionSampler(
+ currentClock?.smallClock?.view,
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateFun
+ ::updateColors
)
- largeRegionSamplingInstance = createRegionSampler(
- currentClock?.largeClock,
+ largeRegionSampler = createRegionSampler(
+ currentClock?.largeClock?.view,
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateFun
+ ::updateColors
)
- smallRegionSamplingInstance.startRegionSampler()
- largeRegionSamplingInstance.startRegionSampler()
+ smallRegionSampler!!.startRegionSampler()
+ largeRegionSampler!!.startRegionSampler()
- updateFun.updateColors()
+ updateColors()
}
protected open fun createRegionSampler(
@@ -117,25 +133,29 @@
mainExecutor: Executor?,
bgExecutor: Executor?,
regionSamplingEnabled: Boolean,
- updateFun: RegionSamplingInstance.UpdateColorCallback
+ updateColors: () -> Unit
): RegionSamplingInstance {
return RegionSamplingInstance(
sampledView,
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateFun)
+ object : RegionSamplingInstance.UpdateColorCallback {
+ override fun updateColors() {
+ updateColors()
+ }
+ })
}
- lateinit var smallRegionSamplingInstance: RegionSamplingInstance
- lateinit var largeRegionSamplingInstance: RegionSamplingInstance
+ var smallRegionSampler: RegionSamplingInstance? = null
+ var largeRegionSampler: RegionSamplingInstance? = null
private var smallClockIsDark = true
private var largeClockIsDark = true
private val configListener = object : ConfigurationController.ConfigurationListener {
override fun onThemeChanged() {
- updateFun.updateColors()
+ clock?.events?.onColorPaletteChanged(resources)
}
override fun onDensityOrFontScaleChanged() {
@@ -145,7 +165,7 @@
private val batteryCallback = object : BatteryStateChangeCallback {
override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- if (isKeyguardShowing && !isCharging && charging) {
+ if (isKeyguardVisible && !isCharging && charging) {
clock?.animations?.charge()
}
isCharging = charging
@@ -158,19 +178,10 @@
}
}
- private val statusBarStateListener = object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- clock?.animations?.doze(linear)
-
- isDozing = linear > dozeAmount
- dozeAmount = linear
- }
- }
-
private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onKeyguardVisibilityChanged(showing: Boolean) {
- isKeyguardShowing = showing
- if (!isKeyguardShowing) {
+ override fun onKeyguardVisibilityChanged(visible: Boolean) {
+ isKeyguardVisible = visible
+ if (!isKeyguardVisible) {
clock?.animations?.doze(if (isDozing) 1f else 0f)
}
}
@@ -188,13 +199,11 @@
}
}
- init {
- isDozing = statusBarStateController.isDozing
- }
-
- fun registerListeners() {
- dozeAmount = statusBarStateController.dozeAmount
- isDozing = statusBarStateController.isDozing || dozeAmount != 0f
+ fun registerListeners(parent: View) {
+ if (isRegistered) {
+ return
+ }
+ isRegistered = true
broadcastDispatcher.registerReceiver(
localeBroadcastReceiver,
@@ -203,19 +212,30 @@
configurationController.addCallback(configListener)
batteryController.addCallback(batteryCallback)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
- statusBarStateController.addCallback(statusBarStateListener)
- smallRegionSamplingInstance.startRegionSampler()
- largeRegionSamplingInstance.startRegionSampler()
+ smallRegionSampler?.startRegionSampler()
+ largeRegionSampler?.startRegionSampler()
+ disposableHandle = parent.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ listenForDozing(this)
+ listenForDozeAmount(this)
+ listenForDozeAmountTransition(this)
+ }
+ }
}
fun unregisterListeners() {
+ if (!isRegistered) {
+ return
+ }
+ isRegistered = false
+
+ disposableHandle?.dispose()
broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
configurationController.removeCallback(configListener)
batteryController.removeCallback(batteryCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
- statusBarStateController.removeCallback(statusBarStateListener)
- smallRegionSamplingInstance.stopRegionSampler()
- largeRegionSamplingInstance.stopRegionSampler()
+ smallRegionSampler?.stopRegionSampler()
+ largeRegionSampler?.stopRegionSampler()
}
/**
@@ -224,12 +244,43 @@
fun dump(pw: PrintWriter) {
pw.println(this)
clock?.dump(pw)
- smallRegionSamplingInstance.dump(pw)
- largeRegionSamplingInstance.dump(pw)
+ smallRegionSampler?.dump(pw)
+ largeRegionSampler?.dump(pw)
}
- companion object {
- private val TAG = ClockEventController::class.simpleName
- private const val FORMAT_NUMBER = 1234567890
+ @VisibleForTesting
+ internal fun listenForDozeAmount(scope: CoroutineScope): Job {
+ return scope.launch {
+ keyguardInteractor.dozeAmount.collect {
+ dozeAmount = it
+ clock?.animations?.doze(dozeAmount)
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
+ return scope.launch {
+ keyguardTransitionInteractor.aodToLockscreenTransition.collect {
+ // Would eventually run this:
+ // dozeAmount = it.value
+ // clock?.animations?.doze(dozeAmount)
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun listenForDozing(scope: CoroutineScope): Job {
+ return scope.launch {
+ combine (
+ keyguardInteractor.dozeAmount,
+ keyguardInteractor.isDozing,
+ ) { localDozeAmount, localIsDozing ->
+ localDozeAmount > dozeAmount || localIsDozing
+ }
+ .collect { localIsDozing ->
+ isDozing = localIsDozing
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
index 692fe83..e6a2bfa 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 @@
*/
@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 @@
return strongAuthFlags and flagCheck != 0
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index d7cd1d0..d03ef98 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -17,7 +17,7 @@
import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockController;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -94,7 +94,7 @@
onDensityOrFontScaleChanged();
}
- void setClock(Clock clock, int statusBarState) {
+ void setClock(ClockController clock, int statusBarState) {
// Disconnect from existing plugin.
mSmallClockFrame.removeAllViews();
mLargeClockFrame.removeAllViews();
@@ -105,11 +105,14 @@
}
// Attach small and big clock views to hierarchy.
- mSmallClockFrame.addView(clock.getSmallClock());
- mLargeClockFrame.addView(clock.getLargeClock());
+ Log.i(TAG, "Attached new clock views to switch");
+ mSmallClockFrame.addView(clock.getSmallClock().getView());
+ mLargeClockFrame.addView(clock.getLargeClock().getView());
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
+ Log.i(TAG, "updateClockViews; useLargeClock=" + useLargeClock + "; animate=" + animate
+ + "; mChildrenAreLaidOut=" + mChildrenAreLaidOut);
if (mClockInAnim != null) mClockInAnim.cancel();
if (mClockOutAnim != null) mClockOutAnim.cancel();
if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 2165099..202134b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -40,7 +40,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
@@ -164,7 +164,7 @@
protected void onViewAttached() {
mClockRegistry.registerClockChangeListener(mClockChangedListener);
setClock(mClockRegistry.createCurrentClock());
- mClockEventController.registerListeners();
+ mClockEventController.registerListeners(mView);
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
@@ -262,7 +262,7 @@
mCurrentClockSize = clockSize;
- Clock clock = getClock();
+ ClockController clock = getClock();
boolean appeared = mView.switchToClock(clockSize, animate);
if (clock != null && animate && appeared && clockSize == LARGE) {
clock.getAnimations().enter();
@@ -273,7 +273,7 @@
* Animates the clock view between folded and unfolded states
*/
public void animateFoldToAod(float foldFraction) {
- Clock clock = getClock();
+ ClockController clock = getClock();
if (clock != null) {
clock.getAnimations().fold(foldFraction);
}
@@ -286,7 +286,7 @@
if (mSmartspaceController != null) {
mSmartspaceController.requestSmartspaceUpdate();
}
- Clock clock = getClock();
+ ClockController clock = getClock();
if (clock != null) {
clock.getEvents().onTimeTick();
}
@@ -319,17 +319,17 @@
* We can't directly getBottom() because clock changes positions in AOD for burn-in
*/
int getClockBottom(int statusBarHeaderHeight) {
- Clock clock = getClock();
+ ClockController clock = getClock();
if (clock == null) {
return 0;
}
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
int frameHeight = mLargeClockFrame.getHeight();
- int clockHeight = clock.getLargeClock().getHeight();
+ int clockHeight = clock.getLargeClock().getView().getHeight();
return frameHeight / 2 + clockHeight / 2;
} else {
- int clockHeight = clock.getSmallClock().getHeight();
+ int clockHeight = clock.getSmallClock().getView().getHeight();
return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
}
}
@@ -338,15 +338,15 @@
* Get the height of the currently visible clock on the keyguard.
*/
int getClockHeight() {
- Clock clock = getClock();
+ ClockController clock = getClock();
if (clock == null) {
return 0;
}
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
- return clock.getLargeClock().getHeight();
+ return clock.getLargeClock().getView().getHeight();
} else {
- return clock.getSmallClock().getHeight();
+ return clock.getSmallClock().getView().getHeight();
}
}
@@ -361,12 +361,12 @@
mNotificationIconAreaController.setupAodIcons(nic);
}
- private void setClock(Clock clock) {
+ private void setClock(ClockController clock) {
mClockEventController.setClock(clock);
mView.setClock(clock, mStatusBarStateController.getState());
}
- private Clock getClock() {
+ private ClockController getClock() {
return mClockEventController.getClock();
}
@@ -398,7 +398,8 @@
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
- Clock clock = getClock();
+ mView.dump(pw, args);
+ ClockController clock = getClock();
if (clock != null) {
clock.dump(pw);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 453072b..5d86ccd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -176,6 +176,8 @@
@Override
public void startAppearAnimation() {
+ setAlpha(1f);
+ setTranslationY(0);
if (mAppearAnimator.isRunning()) {
mAppearAnimator.cancel();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 1e5c53d..2cc5ccdc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -24,7 +24,6 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -107,8 +106,6 @@
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 5b22324..9871645 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -330,9 +330,6 @@
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
- break;
case PROMPT_REASON_NONE:
break;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 0a91150..c46e33d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -22,7 +22,6 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -124,8 +123,6 @@
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 632fcd1..93ee151 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -21,15 +21,23 @@
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
+import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD;
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.LEFT;
+import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.RIGHT;
+import static androidx.constraintlayout.widget.ConstraintSet.START;
+import static androidx.constraintlayout.widget.ConstraintSet.TOP;
+import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
+
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
import static java.lang.Integer.max;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
@@ -44,12 +52,12 @@
import android.graphics.drawable.LayerDrawable;
import android.os.UserManager;
import android.provider.Settings;
+import android.transition.TransitionManager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.util.TypedValue;
import android.view.GestureDetector;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -59,17 +67,15 @@
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowManager;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
@@ -92,9 +98,9 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
-public class KeyguardSecurityContainer extends FrameLayout {
+/** Determines how the bouncer is displayed to the user. */
+public class KeyguardSecurityContainer extends ConstraintLayout {
static final int USER_TYPE_PRIMARY = 1;
static final int USER_TYPE_WORK_PROFILE = 2;
static final int USER_TYPE_SECONDARY_USER = 3;
@@ -125,15 +131,6 @@
// How much to scale the default slop by, to avoid accidental drags.
private static final float SLOP_SCALE = 4f;
- private static final long IME_DISAPPEAR_DURATION_MS = 125;
-
- // The duration of the animation to switch security sides.
- private static final long SECURITY_SHIFT_ANIMATION_DURATION_MS = 500;
-
- // How much of the switch sides animation should be dedicated to fading the security out. The
- // remainder will fade it back in again.
- private static final float SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f;
-
@VisibleForTesting
KeyguardSecurityViewFlipper mSecurityViewFlipper;
private GlobalSettings mGlobalSettings;
@@ -320,7 +317,8 @@
}
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));
@@ -332,7 +330,7 @@
mViewMode = new OneHandedViewMode();
break;
case MODE_USER_SWITCHER:
- mViewMode = new UserSwitcherViewMode();
+ mViewMode = new UserSwitcherViewMode(userSwitcherCallback);
break;
default:
mViewMode = new DefaultViewMode();
@@ -538,7 +536,7 @@
}
/**
- * Runs after a succsssful authentication only
+ * Runs after a successful authentication only
*/
public void startDisappearAnimation(SecurityMode securitySelection) {
mDisappearAnimRunning = true;
@@ -649,47 +647,8 @@
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int maxHeight = 0;
- int maxWidth = 0;
- int childState = 0;
-
- for (int i = 0; i < getChildCount(); i++) {
- final View view = getChildAt(i);
- if (view.getVisibility() != GONE) {
- int updatedWidthMeasureSpec = mViewMode.getChildWidthMeasureSpec(widthMeasureSpec);
- final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-
- // When using EXACTLY spec, measure will use the layout width if > 0. Set before
- // measuring the child
- lp.width = MeasureSpec.getSize(updatedWidthMeasureSpec);
- measureChildWithMargins(view, updatedWidthMeasureSpec, 0,
- heightMeasureSpec, 0);
-
- maxWidth = Math.max(maxWidth,
- view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
- maxHeight = Math.max(maxHeight,
- view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
- childState = combineMeasuredStates(childState, view.getMeasuredState());
- }
- }
-
- maxWidth += getPaddingLeft() + getPaddingRight();
- maxHeight += getPaddingTop() + getPaddingBottom();
-
- // Check against our minimum height and width
- maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
- maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
-
- setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
- resolveSizeAndState(maxHeight, heightMeasureSpec,
- childState << MEASURED_HEIGHT_STATE_SHIFT));
- }
-
- @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
-
int width = right - left;
if (changed && mWidth != width) {
mWidth = width;
@@ -761,7 +720,7 @@
* Enscapsulates the differences between bouncer modes for the container.
*/
interface ViewMode {
- default void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+ default void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {};
@@ -787,11 +746,6 @@
/** On notif tap, this animation will run */
default void startAppearAnimation(SecurityMode securityMode) {};
- /** Override to alter the width measure spec to perhaps limit the ViewFlipper size */
- default int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
- return parentWidthMeasureSpec;
- }
-
/** Called when we are setting a new ViewMode */
default void onDestroy() {};
}
@@ -801,13 +755,12 @@
* screen devices
*/
abstract static class SidedSecurityMode implements ViewMode {
- @Nullable private ValueAnimator mRunningSecurityShiftAnimator;
private KeyguardSecurityViewFlipper mViewFlipper;
- private ViewGroup mView;
+ private ConstraintLayout mView;
private GlobalSettings mGlobalSettings;
private int mDefaultSideSetting;
- public void init(ViewGroup v, KeyguardSecurityViewFlipper viewFlipper,
+ public void init(ConstraintLayout v, KeyguardSecurityViewFlipper viewFlipper,
GlobalSettings globalSettings, boolean leftAlignedByDefault) {
mView = v;
mViewFlipper = viewFlipper;
@@ -850,127 +803,6 @@
protected abstract void updateSecurityViewLocation(boolean leftAlign, boolean animate);
- protected void translateSecurityViewLocation(boolean leftAlign, boolean animate) {
- translateSecurityViewLocation(leftAlign, animate, i -> {});
- }
-
- /**
- * Moves the inner security view to the correct location with animation. This is triggered
- * when the user double taps on the side of the screen that is not currently occupied by
- * the security view.
- */
- protected void translateSecurityViewLocation(boolean leftAlign, boolean animate,
- Consumer<Float> securityAlphaListener) {
- if (mRunningSecurityShiftAnimator != null) {
- mRunningSecurityShiftAnimator.cancel();
- mRunningSecurityShiftAnimator = null;
- }
-
- int targetTranslation = leftAlign
- ? 0 : mView.getMeasuredWidth() - mViewFlipper.getWidth();
-
- if (animate) {
- // This animation is a bit fun to implement. The bouncer needs to move, and fade
- // in/out at the same time. The issue is, the bouncer should only move a short
- // amount (120dp or so), but obviously needs to go from one side of the screen to
- // the other. This needs a pretty custom animation.
- //
- // This works as follows. It uses a ValueAnimation to simply drive the animation
- // progress. This animator is responsible for both the translation of the bouncer,
- // and the current fade. It will fade the bouncer out while also moving it along the
- // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
- // bouncer closer to its destination, then fade it back in again. The effect is that
- // the bouncer will move from 0 -> X while fading out, then
- // (destination - X) -> destination while fading back in again.
- // TODO(b/208250221): Make this animation properly abortable.
- Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
- mView.getContext(), android.R.interpolator.fast_out_extra_slow_in);
- Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
- Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
-
- mRunningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- mRunningSecurityShiftAnimator.setDuration(SECURITY_SHIFT_ANIMATION_DURATION_MS);
- mRunningSecurityShiftAnimator.setInterpolator(Interpolators.LINEAR);
-
- int initialTranslation = (int) mViewFlipper.getTranslationX();
- int totalTranslation = (int) mView.getResources().getDimension(
- R.dimen.security_shift_animation_translation);
-
- final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering()
- && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
- if (shouldRestoreLayerType) {
- mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
- }
-
- float initialAlpha = mViewFlipper.getAlpha();
-
- mRunningSecurityShiftAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mRunningSecurityShiftAnimator = null;
- }
- });
- mRunningSecurityShiftAnimator.addUpdateListener(animation -> {
- float switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION;
- boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
-
- int currentTranslation = (int) (positionInterpolator.getInterpolation(
- animation.getAnimatedFraction()) * totalTranslation);
- int translationRemaining = totalTranslation - currentTranslation;
-
- // Flip the sign if we're going from right to left.
- if (leftAlign) {
- currentTranslation = -currentTranslation;
- translationRemaining = -translationRemaining;
- }
-
- float opacity;
- if (isFadingOut) {
- // The bouncer fades out over the first X%.
- float fadeOutFraction = MathUtils.constrainedMap(
- /* rangeMin= */1.0f,
- /* rangeMax= */0.0f,
- /* valueMin= */0.0f,
- /* valueMax= */switchPoint,
- animation.getAnimatedFraction());
- opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
-
- // When fading out, the alpha needs to start from the initial opacity of the
- // view flipper, otherwise we get a weird bit of jank as it ramps back to
- // 100%.
- mViewFlipper.setAlpha(opacity * initialAlpha);
-
- // Animate away from the source.
- mViewFlipper.setTranslationX(initialTranslation + currentTranslation);
- } else {
- // And in again over the remaining (100-X)%.
- float fadeInFraction = MathUtils.constrainedMap(
- /* rangeMin= */0.0f,
- /* rangeMax= */1.0f,
- /* valueMin= */switchPoint,
- /* valueMax= */1.0f,
- animation.getAnimatedFraction());
-
- opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
- mViewFlipper.setAlpha(opacity);
-
- // Fading back in, animate towards the destination.
- mViewFlipper.setTranslationX(targetTranslation - translationRemaining);
- }
- securityAlphaListener.accept(opacity);
-
- if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
- mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
- }
- });
-
- mRunningSecurityShiftAnimator.start();
- } else {
- mViewFlipper.setTranslationX(targetTranslation);
- }
- }
-
-
boolean isLeftAligned() {
return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
mDefaultSideSetting)
@@ -989,11 +821,11 @@
* Default bouncer is centered within the space
*/
static class DefaultViewMode implements ViewMode {
- private ViewGroup mView;
+ private ConstraintLayout mView;
private KeyguardSecurityViewFlipper mViewFlipper;
@Override
- public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+ public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {
@@ -1005,11 +837,14 @@
}
private void updateSecurityViewGroup() {
- FrameLayout.LayoutParams lp =
- (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams();
- lp.gravity = Gravity.CENTER_HORIZONTAL;
- mViewFlipper.setLayoutParams(lp);
- mViewFlipper.setTranslationX(0);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.connect(mViewFlipper.getId(), START, PARENT_ID, START);
+ constraintSet.connect(mViewFlipper.getId(), END, PARENT_ID, END);
+ constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
+ constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT);
+ constraintSet.applyTo(mView);
}
}
@@ -1018,7 +853,7 @@
* a user switcher, in both portrait and landscape modes.
*/
static class UserSwitcherViewMode extends SidedSecurityMode {
- private ViewGroup mView;
+ private ConstraintLayout mView;
private ViewGroup mUserSwitcherViewGroup;
private KeyguardSecurityViewFlipper mViewFlipper;
private TextView mUserSwitcher;
@@ -1029,11 +864,14 @@
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
this::setupUserSwitcher;
- private float mAnimationLastAlpha = 1f;
- private boolean mAnimationWaitsToShift = true;
+ private UserSwitcherCallback mUserSwitcherCallback;
+
+ UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) {
+ mUserSwitcherCallback = userSwitcherCallback;
+ }
@Override
- public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+ public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {
@@ -1063,6 +901,7 @@
mPopup.dismiss();
mPopup = null;
}
+ setupUserSwitcher();
}
@Override
@@ -1101,7 +940,6 @@
return;
}
- mView.setAlpha(1f);
mUserSwitcherViewGroup.setAlpha(0f);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
1f);
@@ -1208,120 +1046,82 @@
}
};
- 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();
});
}
- /**
- * Each view will get half the width. Yes, it would be easier to use something other than
- * FrameLayout but it was too disruptive to downstream projects to change.
- */
- @Override
- public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
- return MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(parentWidthMeasureSpec) / 2,
- MeasureSpec.EXACTLY);
- }
-
@Override
public void updateSecurityViewLocation() {
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
}
public void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
- setYTranslation();
- setGravity();
- setXTranslation(leftAlign, animate);
- }
-
- private void setXTranslation(boolean leftAlign, boolean animate) {
- if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- mUserSwitcherViewGroup.setTranslationX(0);
- mViewFlipper.setTranslationX(0);
- } else {
- int switcherTargetTranslation = leftAlign
- ? mView.getMeasuredWidth() - mViewFlipper.getWidth() : 0;
- if (animate) {
- mAnimationWaitsToShift = true;
- mAnimationLastAlpha = 1f;
- translateSecurityViewLocation(leftAlign, animate, securityAlpha -> {
- // During the animation security view fades out - alpha goes from 1 to
- // (almost) 0 - and then fades in - alpha grows back to 1.
- // If new alpha is bigger than previous one it means we're at inflection
- // point and alpha is zero or almost zero. That's when we want to do
- // translation of user switcher, so that it's not visible to the user.
- boolean fullyFadeOut = securityAlpha == 0.0f
- || securityAlpha > mAnimationLastAlpha;
- if (fullyFadeOut && mAnimationWaitsToShift) {
- mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
- mAnimationWaitsToShift = false;
- }
- mUserSwitcherViewGroup.setAlpha(securityAlpha);
- mAnimationLastAlpha = securityAlpha;
- });
- } else {
- translateSecurityViewLocation(leftAlign, animate);
- mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
- }
+ if (animate) {
+ TransitionManager.beginDelayedTransition(mView,
+ new KeyguardSecurityViewTransition());
}
-
- }
-
- private void setGravity() {
- if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL);
- updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
- } else {
- // horizontal gravity is the same because we translate these views anyway
- updateViewGravity(mViewFlipper, Gravity.LEFT | Gravity.BOTTOM);
- updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
- }
- }
-
- private void setYTranslation() {
int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- mUserSwitcherViewGroup.setTranslationY(yTrans);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans);
+ constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID);
+ constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID);
+ constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
+ constraintSet.setVerticalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
+ constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(), WRAP_CONTENT);
+ constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(), WRAP_CONTENT);
+ constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
+ constraintSet.applyTo(mView);
} else {
- // Attempt to reposition a bit higher to make up for this frame being a bit lower
- // on the device
- mUserSwitcherViewGroup.setTranslationY(-yTrans);
- mViewFlipper.setTranslationY(0);
+ int leftElement = leftAlign ? mViewFlipper.getId() : mUserSwitcherViewGroup.getId();
+ int rightElement =
+ leftAlign ? mUserSwitcherViewGroup.getId() : mViewFlipper.getId();
+
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.connect(leftElement, LEFT, PARENT_ID, LEFT);
+ constraintSet.connect(leftElement, RIGHT, rightElement, LEFT);
+ constraintSet.connect(rightElement, LEFT, leftElement, RIGHT);
+ constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT);
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM,
+ yTrans);
+ constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.setHorizontalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
+ constraintSet.setHorizontalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
+ constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(),
+ MATCH_CONSTRAINT);
+ constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(),
+ MATCH_CONSTRAINT);
+ constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT);
+ constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
+ constraintSet.applyTo(mView);
}
}
- private void updateViewGravity(View v, int gravity) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
- lp.gravity = gravity;
- v.setLayoutParams(lp);
+ interface UserSwitcherCallback {
+ void showUnlockToContinueMessage();
}
}
@@ -1330,11 +1130,11 @@
* between alternate sides of the display.
*/
static class OneHandedViewMode extends SidedSecurityMode {
- private ViewGroup mView;
+ private ConstraintLayout mView;
private KeyguardSecurityViewFlipper mViewFlipper;
@Override
- public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+ public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {
@@ -1342,28 +1142,10 @@
mView = v;
mViewFlipper = viewFlipper;
- updateSecurityViewGravity();
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
}
/**
- * One-handed mode contains the child to half of the available space.
- */
- @Override
- public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
- return MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(parentWidthMeasureSpec) / 2,
- MeasureSpec.EXACTLY);
- }
-
- private void updateSecurityViewGravity() {
- FrameLayout.LayoutParams lp =
- (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams();
- lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
- mViewFlipper.setLayoutParams(lp);
- }
-
- /**
* Moves the bouncer to align with a tap (most likely in the shade), so the bouncer
* appears on the same side as a touch.
*/
@@ -1380,7 +1162,20 @@
}
protected void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
- translateSecurityViewLocation(leftAlign, animate);
+ if (animate) {
+ TransitionManager.beginDelayedTransition(mView,
+ new KeyguardSecurityViewTransition());
+ }
+ ConstraintSet constraintSet = new ConstraintSet();
+ if (leftAlign) {
+ constraintSet.connect(mViewFlipper.getId(), LEFT, PARENT_ID, LEFT);
+ } else {
+ constraintSet.connect(mViewFlipper.getId(), RIGHT, PARENT_ID, RIGHT);
+ }
+ constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+ constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.constrainPercentWidth(mViewFlipper.getId(), 0.5f);
+ constraintSet.applyTo(mView);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 57058b7..bcd1a1e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -427,6 +427,7 @@
public void startAppearAnimation() {
if (mCurrentSecurityMode != SecurityMode.None) {
+ mView.setAlpha(1f);
mView.startAppearAnimation(mCurrentSecurityMode);
getCurrentSecurityController().startAppearAnimation();
}
@@ -619,7 +620,9 @@
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/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 9d0a8ac..ac00e94 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -61,12 +61,6 @@
int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
/**
- * Some auth is required because the trustagent expired either from timeout or manually by
- * the user
- */
- int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
-
- /**
* Reset the view and prepare to take input. This should do things like clearing the
* password or pattern and clear error messages.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
new file mode 100644
index 0000000..c9128e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -0,0 +1,199 @@
+/*
+ * 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.keyguard
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.util.MathUtils
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.AnimationUtils
+import com.android.internal.R.interpolator.fast_out_extra_slow_in
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+
+/** Animates constraint layout changes for the security view. */
+class KeyguardSecurityViewTransition : Transition() {
+
+ companion object {
+ const val PROP_BOUNDS = "securityViewLocation:bounds"
+
+ // The duration of the animation to switch security sides.
+ const val SECURITY_SHIFT_ANIMATION_DURATION_MS = 500L
+
+ // How much of the switch sides animation should be dedicated to fading the security out.
+ // The remainder will fade it back in again.
+ const val SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f
+ }
+
+ private fun captureValues(values: TransitionValues) {
+ val boundsRect = Rect()
+ boundsRect.left = values.view.left
+ boundsRect.top = values.view.top
+ boundsRect.right = values.view.right
+ boundsRect.bottom = values.view.bottom
+ values.values[PROP_BOUNDS] = boundsRect
+ }
+
+ override fun getTransitionProperties(): Array<String>? {
+ return arrayOf(PROP_BOUNDS)
+ }
+
+ override fun captureEndValues(transitionValues: TransitionValues?) {
+ transitionValues?.let { captureValues(it) }
+ }
+
+ override fun captureStartValues(transitionValues: TransitionValues?) {
+ transitionValues?.let { captureValues(it) }
+ }
+
+ override fun createAnimator(
+ sceneRoot: ViewGroup?,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator? {
+ if (sceneRoot == null || startValues == null || endValues == null) {
+ return null
+ }
+
+ // This animation is a bit fun to implement. The bouncer needs to move, and fade
+ // in/out at the same time. The issue is, the bouncer should only move a short
+ // amount (120dp or so), but obviously needs to go from one side of the screen to
+ // the other. This needs a pretty custom animation.
+ //
+ // This works as follows. It uses a ValueAnimation to simply drive the animation
+ // progress. This animator is responsible for both the translation of the bouncer,
+ // and the current fade. It will fade the bouncer out while also moving it along the
+ // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
+ // bouncer closer to its destination, then fade it back in again. The effect is that
+ // the bouncer will move from 0 -> X while fading out, then
+ // (destination - X) -> destination while fading back in again.
+ // TODO(b/208250221): Make this animation properly abortable.
+ val positionInterpolator =
+ AnimationUtils.loadInterpolator(sceneRoot.context, fast_out_extra_slow_in)
+ val fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN
+ val fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ var runningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
+ runningSecurityShiftAnimator.duration = SECURITY_SHIFT_ANIMATION_DURATION_MS
+ runningSecurityShiftAnimator.interpolator = Interpolators.LINEAR
+ val startRect = startValues.values[PROP_BOUNDS] as Rect
+ val endRect = endValues.values[PROP_BOUNDS] as Rect
+ val v = startValues.view
+ val totalTranslation: Int =
+ sceneRoot.resources.getDimension(R.dimen.security_shift_animation_translation).toInt()
+ val shouldRestoreLayerType =
+ (v.hasOverlappingRendering() && v.layerType != View.LAYER_TYPE_HARDWARE)
+ if (shouldRestoreLayerType) {
+ v.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */ null)
+ }
+ val initialAlpha: Float = v.alpha
+ runningSecurityShiftAnimator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ runningSecurityShiftAnimator = null
+ if (shouldRestoreLayerType) {
+ v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null)
+ }
+ }
+ }
+ )
+
+ runningSecurityShiftAnimator.addUpdateListener { animation: ValueAnimator ->
+ val switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION
+ val isFadingOut = animation.animatedFraction < switchPoint
+ val opacity: Float
+ var currentTranslation =
+ (positionInterpolator.getInterpolation(animation.animatedFraction) *
+ totalTranslation)
+ .toInt()
+ var translationRemaining = totalTranslation - currentTranslation
+ val leftAlign = endRect.left < startRect.left
+ if (leftAlign) {
+ currentTranslation = -currentTranslation
+ translationRemaining = -translationRemaining
+ }
+
+ if (isFadingOut) {
+ // The bouncer fades out over the first X%.
+ val fadeOutFraction =
+ MathUtils.constrainedMap(
+ /* rangeMin= */ 1.0f,
+ /* rangeMax= */ 0.0f,
+ /* valueMin= */ 0.0f,
+ /* valueMax= */ switchPoint,
+ animation.animatedFraction
+ )
+ opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction)
+
+ // When fading out, the alpha needs to start from the initial opacity of the
+ // view flipper, otherwise we get a weird bit of jank as it ramps back to
+ // 100%.
+ v.alpha = opacity * initialAlpha
+ if (v is KeyguardSecurityViewFlipper) {
+ v.setLeftTopRightBottom(
+ startRect.left + currentTranslation,
+ startRect.top,
+ 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)%.
+ val fadeInFraction =
+ MathUtils.constrainedMap(
+ /* rangeMin= */ 0.0f,
+ /* rangeMax= */ 1.0f,
+ /* valueMin= */ switchPoint,
+ /* valueMax= */ 1.0f,
+ animation.animatedFraction
+ )
+ opacity = fadeInInterpolator.getInterpolation(fadeInFraction)
+ v.alpha = opacity
+
+ // Fading back in, animate towards the destination.
+ if (v is KeyguardSecurityViewFlipper) {
+ v.setLeftTopRightBottom(
+ endRect.left - translationRemaining,
+ endRect.top,
+ endRect.right - translationRemaining,
+ endRect.bottom
+ )
+ } else {
+ v.setLeftTopRightBottom(
+ endRect.left,
+ endRect.top,
+ endRect.right,
+ endRect.bottom
+ )
+ }
+ }
+ }
+ runningSecurityShiftAnimator.start()
+ return runningSecurityShiftAnimator
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c715a4e..e9f06ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -212,9 +212,9 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (showing) {
- if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ if (visible) {
+ if (DEBUG) Slog.v(TAG, "refresh statusview visible:true");
refreshTime();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 7d6f377..f974e27 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -20,8 +20,8 @@
import android.view.ViewGroup
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
@@ -49,13 +49,14 @@
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
setOf(
- ViewIdToTranslate(R.id.keyguard_status_area, LEFT, filterNever),
- ViewIdToTranslate(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
+ ViewIdToTranslate(R.id.keyguard_status_area, START, filterNever),
ViewIdToTranslate(
- R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.start_button, LEFT, filterNever),
- ViewIdToTranslate(R.id.end_button, RIGHT, filterNever)),
+ R.id.lockscreen_clock_view_large, START, filterSplitShadeOnly),
+ ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterNever),
+ ViewIdToTranslate(
+ R.id.notification_stack_scroller, END, filterSplitShadeOnly),
+ ViewIdToTranslate(R.id.start_button, START, filterNever),
+ ViewIdToTranslate(R.id.end_button, END, filterNever)),
progressProvider = unfoldProgressProvider)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d14666d..46e187e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -308,7 +308,8 @@
HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
private int mPhoneState;
- private boolean mKeyguardIsVisible;
+ private boolean mKeyguardShowing;
+ private boolean mKeyguardOccluded;
private boolean mCredentialAttempted;
private boolean mKeyguardGoingAway;
private boolean mGoingToSleep;
@@ -318,7 +319,6 @@
private boolean mAuthInterruptActive;
private boolean mNeedsSlowUnlockTransition;
private boolean mAssistantVisible;
- private boolean mKeyguardOccluded;
private boolean mOccludingAppRequestingFp;
private boolean mOccludingAppRequestingFace;
private boolean mSecureCameraLaunched;
@@ -620,7 +620,7 @@
* of them based on carrier config. e.g. In this case we should only show one carrier name
* on the status bar and quick settings.
*/
- public List<SubscriptionInfo> getFilteredSubscriptionInfo(boolean forceReload) {
+ public List<SubscriptionInfo> getFilteredSubscriptionInfo() {
List<SubscriptionInfo> subscriptions = getSubscriptionInfo(false);
if (subscriptions.size() == 2) {
SubscriptionInfo info1 = subscriptions.get(0);
@@ -681,14 +681,42 @@
}
/**
- * Updates KeyguardUpdateMonitor's internal state to know if keyguard is occluded
+ * Updates KeyguardUpdateMonitor's internal state to know if keyguard is showing and if
+ * its occluded. The keyguard is considered visible if its showing and NOT occluded.
*/
- public void setKeyguardOccluded(boolean occluded) {
- mKeyguardOccluded = occluded;
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED);
- }
+ public void setKeyguardShowing(boolean showing, boolean occluded) {
+ final boolean occlusionChanged = mKeyguardOccluded != occluded;
+ final boolean showingChanged = mKeyguardShowing != showing;
+ if (!occlusionChanged && !showingChanged) {
+ return;
+ }
+ final boolean wasKeyguardVisible = isKeyguardVisible();
+ mKeyguardShowing = showing;
+ mKeyguardOccluded = occluded;
+ final boolean isKeyguardVisible = isKeyguardVisible();
+ mLogger.logKeyguardShowingChanged(showing, occluded, isKeyguardVisible);
+
+ if (isKeyguardVisible != wasKeyguardVisible) {
+ if (isKeyguardVisible) {
+ mSecureCameraLaunched = false;
+ }
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onKeyguardVisibilityChanged(isKeyguardVisible);
+ }
+ }
+ }
+
+ if (occlusionChanged) {
+ updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED);
+ } else if (showingChanged) {
+ updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED);
+ }
+ }
/**
* Request to listen for face authentication when an app is occluding keyguard.
@@ -1400,6 +1428,16 @@
}
}
+ 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++) {
@@ -1750,11 +1788,14 @@
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) {
@@ -1772,6 +1813,14 @@
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() {
@@ -1920,7 +1969,8 @@
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;
@@ -2319,7 +2369,7 @@
*/
public void requestFaceAuth(boolean userInitiatedRequest,
@FaceAuthApiRequestReason String reason) {
- mLogger.logFaceAuthRequested(userInitiatedRequest);
+ mLogger.logFaceAuthRequested(userInitiatedRequest, reason);
updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
}
@@ -2442,7 +2492,7 @@
// Triggers:
final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
final boolean awakeKeyguard = mBouncerFullyShown || mUdfpsBouncerShowing
- || (mKeyguardIsVisible && !mGoingToSleep
+ || (isKeyguardVisible() && !mGoingToSleep
&& mStatusBarState != StatusBarState.SHADE_LOCKED);
// Gates:
@@ -2518,7 +2568,7 @@
final boolean userDoesNotHaveTrust = !getUserHasTrust(user);
final boolean shouldListenForFingerprintAssistant = shouldListenForFingerprintAssistant();
final boolean shouldListenKeyguardState =
- mKeyguardIsVisible
+ isKeyguardVisible()
|| !mDeviceInteractive
|| (mBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
|| mGoingToSleep
@@ -2567,7 +2617,7 @@
mFingerprintLockedOut,
mGoingToSleep,
mKeyguardGoingAway,
- mKeyguardIsVisible,
+ isKeyguardVisible(),
mKeyguardOccluded,
mOccludingAppRequestingFp,
mIsPrimaryUser,
@@ -2589,7 +2639,7 @@
}
final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
- final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive
+ final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
&& !statusBarShadeLocked;
final int user = getCurrentUser();
final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
@@ -3146,32 +3196,11 @@
callbacksRefreshCarrierInfo();
}
- public boolean isKeyguardVisible() {
- return mKeyguardIsVisible;
- }
-
/**
- * Notifies that the visibility state of Keyguard has changed.
- *
- * <p>Needs to be called from the main thread.
+ * Whether the keyguard is showing and not occluded.
*/
- public void onKeyguardVisibilityChanged(boolean showing) {
- Assert.isMainThread();
- mLogger.logKeyguardVisibilityChanged(showing);
- mKeyguardIsVisible = showing;
-
- if (showing) {
- mSecureCameraLaunched = false;
- }
-
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onKeyguardVisibilityChangedRaw(showing);
- }
- }
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED);
+ public boolean isKeyguardVisible() {
+ return mKeyguardShowing && !mKeyguardOccluded;
}
/**
@@ -3380,7 +3409,7 @@
callback.onTimeChanged();
callback.onPhoneStateChanged(mPhoneState);
callback.onRefreshCarrierInfo();
- callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
+ callback.onKeyguardVisibilityChanged(isKeyguardVisible());
callback.onTelephonyCapable(mTelephonyCapable);
for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7a42803..c06e1dc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
import android.hardware.biometrics.BiometricSourceType;
-import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.view.WindowManagerPolicyConstants;
@@ -32,10 +31,6 @@
*/
public class KeyguardUpdateMonitorCallback {
- private static final long VISIBILITY_CHANGED_COLLAPSE_MS = 1000;
- private long mVisibilityChangedCalled;
- private boolean mShowing;
-
/**
* Called when the battery status changes, e.g. when plugged in or unplugged, charge
* level, etc. changes.
@@ -75,21 +70,6 @@
public void onPhoneStateChanged(int phoneState) { }
/**
- * Called when the visibility of the keyguard changes.
- * @param showing Indicates if the keyguard is now visible.
- */
- public void onKeyguardVisibilityChanged(boolean showing) { }
-
- public void onKeyguardVisibilityChangedRaw(boolean showing) {
- final long now = SystemClock.elapsedRealtime();
- if (showing == mShowing
- && (now - mVisibilityChangedCalled) < VISIBILITY_CHANGED_COLLAPSE_MS) return;
- onKeyguardVisibilityChanged(showing);
- mVisibilityChangedCalled = now;
- mShowing = showing;
- }
-
- /**
* Called when the keyguard enters or leaves bouncer mode.
* @param bouncerIsOrWillBeShowing if true, keyguard is showing the bouncer or transitioning
* from/to bouncer mode.
@@ -97,6 +77,12 @@
public void onKeyguardBouncerStateChanged(boolean bouncerIsOrWillBeShowing) { }
/**
+ * Called when the keyguard visibility changes.
+ * @param visible whether the keyguard is showing and is NOT occluded
+ */
+ public void onKeyguardVisibilityChanged(boolean visible) { }
+
+ /**
* Called when the keyguard fully transitions to the bouncer or is no longer the bouncer
* @param bouncerIsFullyShowing if true, keyguard is fully showing the bouncer
*/
@@ -305,4 +291,9 @@
* Called when the notification shade is expanded or collapsed.
*/
public void onShadeExpandedChanged(boolean expanded) { }
+
+ /**
+ * Called when the non-strong biometric state changed.
+ */
+ public void onNonStrongBiometricAllowedChanged(int userId) { }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 8293c74..90f0446 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -24,10 +24,10 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
/**
* Interface to control Keyguard View. It should be implemented by KeyguardViewManagers, which
@@ -94,11 +94,6 @@
void setOccluded(boolean occluded, boolean animate);
/**
- * @return Whether the keyguard is showing
- */
- boolean isShowing();
-
- /**
* Dismisses the keyguard by going to the next screen or making it gone.
*/
void dismissAndCollapse();
@@ -185,7 +180,7 @@
*/
void registerCentralSurfaces(CentralSurfaces centralSurfaces,
NotificationPanelViewController notificationPanelViewController,
- @Nullable PanelExpansionStateManager panelExpansionStateManager,
+ @Nullable ShadeExpansionStateManager shadeExpansionStateManager,
BiometricUnlockController biometricUnlockController,
View notificationContainer,
KeyguardBypassController bypassController);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 2a36676..c41b752 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -447,14 +447,6 @@
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- // reset mIsBouncerShowing state in case it was preemptively set
- // onLongPress
- mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
- updateVisibility();
- }
-
- @Override
public void onKeyguardBouncerStateChanged(boolean bouncer) {
mIsBouncerShowing = bouncer;
updateVisibility();
@@ -507,6 +499,11 @@
// If biometrics were removed, local vars mCanDismissLockScreen and
// mUserUnlockedWithBiometric may not be updated.
mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
+
+ // reset mIsBouncerShowing state in case it was preemptively set
+ // onLongPress
+ mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
+
updateKeyguardShowing();
if (mIsKeyguardShowing) {
mUserUnlockedWithBiometric =
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index bf9f4c8..2eee957 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -108,10 +108,11 @@
}, { "Face help received, msgId: $int1 msg: $str1" })
}
- fun logFaceAuthRequested(userInitiatedRequest: Boolean) {
- logBuffer.log(TAG, DEBUG,
- { bool1 = userInitiatedRequest },
- { "requestFaceAuth() userInitiated=$bool1" })
+ fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = userInitiatedRequest
+ str1 = reason
+ }, { "requestFaceAuth() userInitiated=$bool1 reason=$str1" })
}
fun logFaceAuthSuccess(userId: Int) {
@@ -170,8 +171,14 @@
logBuffer.log(TAG, VERBOSE, { str1 = "$model" }, { str1!! })
}
- fun logKeyguardVisibilityChanged(showing: Boolean) {
- logBuffer.log(TAG, DEBUG, { bool1 = showing }, { "onKeyguardVisibilityChanged($bool1)" })
+ fun logKeyguardShowingChanged(showing: Boolean, occluded: Boolean, visible: Boolean) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = showing
+ bool2 = occluded
+ bool3 = visible
+ }, {
+ "keyguardShowingChanged(showing=$bool1 occluded=$bool2 visible=$bool3)"
+ })
}
fun logMissingSupervisorAppError(userId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
index 109be40..a89cbf5 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -3,6 +3,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -18,11 +19,11 @@
@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(
@@ -55,7 +56,11 @@
} else {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
}
- packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
+ try {
+ packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
+ } catch (e: IllegalArgumentException) {
+ Log.w("ChooserSelector", "Unable to set IntentResolver enabled=" + enabled, e)
+ }
}
suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine { }
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 0201cdc..929ebea 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 9cdce64..8f41956 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -46,7 +46,7 @@
* 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 @@
@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 632e11a..11d579d 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -105,7 +105,7 @@
* 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 @@
@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 @@
ThreadFactory threadFactory,
PrivacyDotDecorProviderFactory dotFactory,
FaceScanningProviderFactory faceScanningFactory) {
- super(context);
+ mContext = context;
mMainExecutor = mainExecutor;
mSecureSettings = secureSettings;
mBroadcastDispatcher = broadcastDispatcher;
@@ -941,7 +942,7 @@
}
@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 1f2de4c..5bd85a7 100644
--- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
@@ -38,16 +38,17 @@
* @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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index fe6dbe5..873a695 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -38,6 +38,8 @@
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.animation.Interpolators;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -66,7 +68,7 @@
private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
- static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
+ public static final float SWIPE_PROGRESS_FADE_END = 0.6f; // fraction of thumbnail width
// beyond which swipe progress->0
public static final float SWIPED_FAR_ENOUGH_SIZE_FRACTION = 0.6f;
static final float MAX_SCROLL_SIZE_FRACTION = 0.3f;
@@ -235,7 +237,11 @@
return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
}
- private float getSwipeAlpha(float progress) {
+ /**
+ * Returns the alpha value depending on the progress of the swipe.
+ */
+ @VisibleForTesting
+ public float getSwipeAlpha(float progress) {
if (mFadeDependingOnAmountSwiped) {
// The more progress has been fade, the lower the alpha value so that the view fades.
return Math.max(1 - progress, 0);
@@ -260,7 +266,7 @@
animView.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
- animView.setAlpha(getSwipeAlpha(swipeProgress));
+ updateSwipeProgressAlpha(animView, getSwipeAlpha(swipeProgress));
}
}
invalidateGlobalRegion(animView);
@@ -561,6 +567,14 @@
mCallback.onChildSnappedBack(animView, targetLeft);
}
+
+ /**
+ * Called to update the content alpha while the view is swiped
+ */
+ protected void updateSwipeProgressAlpha(View animView, float alpha) {
+ animView.setAlpha(alpha);
+ }
+
/**
* Give the swipe helper itself a chance to do something on snap back so NSSL doesn't have
* to tell us what to do
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 9138b23..8415ef8 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.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 @@
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 139448c0..a320939 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 a1288b5..9f1c9b4 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 @@
* 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 @@
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 @@
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 @@
@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 6e14ddc..ab11fce 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 @@
* 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 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/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 11353f6..403941f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -61,8 +61,8 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mIsKeyguardVisible = showing;
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ mIsKeyguardVisible = visible;
handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
index 33e6ca4..9b441ad 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
@@ -21,6 +21,8 @@
import android.view.Display;
import android.view.Surface;
+import com.android.systemui.R;
+
/**
* Utility class for determining screen and corner dimensions.
*/
@@ -82,17 +84,13 @@
* where the curve ends), in pixels.
*/
public static int getCornerRadiusBottom(Context context) {
- int radius = 0;
-
- int resourceId = context.getResources().getIdentifier("config_rounded_mask_size_bottom",
- "dimen", "com.android.systemui");
- if (resourceId > 0) {
- radius = context.getResources().getDimensionPixelSize(resourceId);
- }
+ int radius = context.getResources().getDimensionPixelSize(
+ R.dimen.config_rounded_mask_size_bottom);
if (radius == 0) {
radius = getCornerRadiusDefault(context);
}
+
return radius;
}
@@ -101,28 +99,17 @@
* the curve ends), in pixels.
*/
public static int getCornerRadiusTop(Context context) {
- int radius = 0;
-
- int resourceId = context.getResources().getIdentifier("config_rounded_mask_size_top",
- "dimen", "com.android.systemui");
- if (resourceId > 0) {
- radius = context.getResources().getDimensionPixelSize(resourceId);
- }
+ int radius = context.getResources().getDimensionPixelSize(
+ R.dimen.config_rounded_mask_size_top);
if (radius == 0) {
radius = getCornerRadiusDefault(context);
}
+
return radius;
}
private static int getCornerRadiusDefault(Context context) {
- int radius = 0;
-
- int resourceId = context.getResources().getIdentifier("config_rounded_mask_size",
- "dimen", "com.android.systemui");
- if (resourceId > 0) {
- radius = context.getResources().getDimensionPixelSize(resourceId);
- }
- return radius;
+ return context.getResources().getDimensionPixelSize(R.dimen.config_rounded_mask_size);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 46c12b2..e74d810 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -849,7 +849,7 @@
}
mContainerState = STATE_GONE;
if (isAttachedToWindow()) {
- mWindowManager.removeView(this);
+ mWindowManager.removeViewImmediate(this);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index aae92ad..d43e5d9 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 @@
* {@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 @@
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 @@
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor) {
- super(context);
+ mContext = context;
mExecution = execution;
mUserManager = userManager;
mLockPatternUtils = lockPatternUtils;
@@ -1099,8 +1100,7 @@
}
@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/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 4fee083..4363b88 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -117,7 +117,7 @@
}
fun showUnlockRipple(biometricSourceType: BiometricSourceType?) {
- if (!(keyguardUpdateMonitor.isKeyguardVisible || keyguardUpdateMonitor.isDreaming) ||
+ if (!keyguardStateController.isShowing ||
keyguardUpdateMonitor.userNeedsStrongAuth()) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 3ad2bef..4130cf5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -22,9 +22,9 @@
import com.android.systemui.animation.Interpolators
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.util.ViewController
import java.io.PrintWriter
@@ -41,7 +41,7 @@
abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
view: T,
protected val statusBarStateController: StatusBarStateController,
- protected val panelExpansionStateManager: PanelExpansionStateManager,
+ protected val shadeExpansionStateManager: ShadeExpansionStateManager,
protected val dialogManager: SystemUIDialogManager,
private val dumpManager: DumpManager
) : ViewController<T>(view), Dumpable {
@@ -54,7 +54,7 @@
private var dialogAlphaAnimator: ValueAnimator? = null
private val dialogListener = SystemUIDialogManager.Listener { runDialogAlphaAnimator() }
- private val panelExpansionListener = PanelExpansionListener { event ->
+ private val shadeExpansionListener = ShadeExpansionListener { event ->
// Notification shade can be expanded but not visible (fraction: 0.0), for example
// when a heads-up notification (HUN) is showing.
notificationShadeVisible = event.expanded && event.fraction > 0f
@@ -108,13 +108,13 @@
}
override fun onViewAttached() {
- panelExpansionStateManager.addExpansionListener(panelExpansionListener)
+ shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
dialogManager.registerListener(dialogListener)
dumpManager.registerDumpable(dumpTag, this)
}
override fun onViewDetached() {
- panelExpansionStateManager.removeExpansionListener(panelExpansionListener)
+ shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
dialogManager.unregisterListener(dialogListener)
dumpManager.unregisterDumpable(dumpTag)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index 4cd40d2..e6aeb43 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -17,8 +17,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
/**
* Class that coordinates non-HBM animations for biometric prompt.
@@ -26,13 +26,13 @@
class UdfpsBpViewController(
view: UdfpsBpView,
statusBarStateController: StatusBarStateController,
- panelExpansionStateManager: PanelExpansionStateManager,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsBpView>(
view,
statusBarStateController,
- panelExpansionStateManager,
+ shadeExpansionStateManager,
systemUIDialogManager,
dumpManager
) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 412dc05..a7648bf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -63,12 +63,12 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -111,7 +111,7 @@
private final WindowManager mWindowManager;
private final DelayableExecutor mFgExecutor;
@NonNull private final Executor mBiometricExecutor;
- @NonNull private final PanelExpansionStateManager mPanelExpansionStateManager;
+ @NonNull private final ShadeExpansionStateManager mShadeExpansionStateManager;
@NonNull private final StatusBarStateController mStatusBarStateController;
@NonNull private final KeyguardStateController mKeyguardStateController;
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@@ -205,7 +205,7 @@
mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
mWindowManager, mAccessibilityManager, mStatusBarStateController,
- mPanelExpansionStateManager, mKeyguardViewManager,
+ mShadeExpansionStateManager, mKeyguardViewManager,
mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
mLockscreenShadeTransitionController, mConfigurationController,
mSystemClock, mKeyguardStateController,
@@ -245,7 +245,7 @@
mAcquiredReceived = true;
final UdfpsView view = mOverlay.getOverlayView();
if (view != null) {
- view.unconfigureDisplay();
+ unconfigureDisplay(view);
}
if (acquiredGood) {
mOverlay.onAcquiredGood();
@@ -582,7 +582,7 @@
@NonNull WindowManager windowManager,
@NonNull StatusBarStateController statusBarStateController,
@Main DelayableExecutor fgExecutor,
- @NonNull PanelExpansionStateManager panelExpansionStateManager,
+ @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -615,7 +615,7 @@
mFingerprintManager = checkNotNull(fingerprintManager);
mWindowManager = windowManager;
mFgExecutor = fgExecutor;
- mPanelExpansionStateManager = panelExpansionStateManager;
+ mShadeExpansionStateManager = shadeExpansionStateManager;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
mKeyguardViewManager = statusBarKeyguardViewManager;
@@ -735,6 +735,19 @@
mOverlay = null;
mOrientationListener.disable();
+
+ }
+
+ private void unconfigureDisplay(@NonNull UdfpsView view) {
+ if (view.isDisplayConfigured()) {
+ view.unconfigureDisplay();
+
+ if (mCancelAodTimeoutAction != null) {
+ mCancelAodTimeoutAction.run();
+ mCancelAodTimeoutAction = null;
+ }
+ mIsAodInterruptActive = false;
+ }
}
/**
@@ -810,12 +823,12 @@
* sensors, this can result in illumination persisting for longer than necessary.
*/
void onCancelUdfps() {
- if (mOverlay != null && mOverlay.getOverlayView() != null) {
- onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
- }
if (!mIsAodInterruptActive) {
return;
}
+ if (mOverlay != null && mOverlay.getOverlayView() != null) {
+ onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
+ }
if (mCancelAodTimeoutAction != null) {
mCancelAodTimeoutAction.run();
mCancelAodTimeoutAction = null;
@@ -909,15 +922,8 @@
}
}
mOnFingerDown = false;
- if (view.isDisplayConfigured()) {
- view.unconfigureDisplay();
- }
+ unconfigureDisplay(view);
- if (mCancelAodTimeoutAction != null) {
- mCancelAodTimeoutAction.run();
- mCancelAodTimeoutAction = null;
- }
- mIsAodInterruptActive = false;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 1c62f8a..66a521c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -43,11 +43,11 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
@@ -67,7 +67,7 @@
private val windowManager: WindowManager,
private val accessibilityManager: AccessibilityManager,
private val statusBarStateController: StatusBarStateController,
- private val panelExpansionStateManager: PanelExpansionStateManager,
+ private val shadeExpansionStateManager: ShadeExpansionStateManager,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dialogManager: SystemUIDialogManager,
@@ -192,7 +192,7 @@
},
enrollHelper ?: throw IllegalStateException("no enrollment helper"),
statusBarStateController,
- panelExpansionStateManager,
+ shadeExpansionStateManager,
dialogManager,
dumpManager,
overlayParams.scaleFactor
@@ -202,7 +202,7 @@
UdfpsKeyguardViewController(
view.addUdfpsView(R.layout.udfps_keyguard_view),
statusBarStateController,
- panelExpansionStateManager,
+ shadeExpansionStateManager,
statusBarKeyguardViewManager,
keyguardUpdateMonitor,
dumpManager,
@@ -221,7 +221,7 @@
UdfpsBpViewController(
view.addUdfpsView(R.layout.udfps_bp_view),
statusBarStateController,
- panelExpansionStateManager,
+ shadeExpansionStateManager,
dialogManager,
dumpManager
)
@@ -231,7 +231,7 @@
UdfpsFpmOtherViewController(
view.addUdfpsView(R.layout.udfps_fpm_other_view),
statusBarStateController,
- panelExpansionStateManager,
+ shadeExpansionStateManager,
dialogManager,
dumpManager
)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 0b7bdde..e01273f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -22,8 +22,8 @@
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
/**
* Class that coordinates non-HBM animations during enrollment.
@@ -54,11 +54,11 @@
@NonNull UdfpsEnrollView view,
@NonNull UdfpsEnrollHelper enrollHelper,
@NonNull StatusBarStateController statusBarStateController,
- @NonNull PanelExpansionStateManager panelExpansionStateManager,
+ @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
@NonNull SystemUIDialogManager systemUIDialogManager,
@NonNull DumpManager dumpManager,
float scaleFactor) {
- super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager,
+ super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
dumpManager);
mEnrollProgressBarRadius = (int) (scaleFactor * getContext().getResources().getInteger(
R.integer.config_udfpsEnrollProgressBar));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.kt
index 98205cf..7c23278 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.kt
@@ -17,8 +17,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
/**
* Class that coordinates non-HBM animations for non keyguard, enrollment or biometric prompt
@@ -29,13 +29,13 @@
class UdfpsFpmOtherViewController(
view: UdfpsFpmOtherView,
statusBarStateController: StatusBarStateController,
- panelExpansionStateManager: PanelExpansionStateManager,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsFpmOtherView>(
view,
statusBarStateController,
- panelExpansionStateManager,
+ shadeExpansionStateManager,
systemUIDialogManager,
dumpManager
) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 24b8933..4d7f89d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -31,6 +31,9 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -38,9 +41,6 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.time.SystemClock;
@@ -88,7 +88,7 @@
protected UdfpsKeyguardViewController(
@NonNull UdfpsKeyguardView view,
@NonNull StatusBarStateController statusBarStateController,
- @NonNull PanelExpansionStateManager panelExpansionStateManager,
+ @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@NonNull DumpManager dumpManager,
@@ -100,7 +100,7 @@
@NonNull SystemUIDialogManager systemUIDialogManager,
@NonNull UdfpsController udfpsController,
@NonNull ActivityLaunchAnimator activityLaunchAnimator) {
- super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager,
+ super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
dumpManager);
mKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -153,7 +153,7 @@
mQsExpansion = mKeyguardViewManager.getQsExpansion();
updateGenericBouncerVisibility();
mConfigurationController.addCallback(mConfigurationListener);
- getPanelExpansionStateManager().addExpansionListener(mPanelExpansionListener);
+ getShadeExpansionStateManager().addExpansionListener(mShadeExpansionListener);
updateScaleFactor();
mView.updatePadding();
updateAlpha();
@@ -174,7 +174,7 @@
mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
mConfigurationController.removeCallback(mConfigurationListener);
- getPanelExpansionStateManager().removeExpansionListener(mPanelExpansionListener);
+ getShadeExpansionStateManager().removeExpansionListener(mShadeExpansionListener);
if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
}
@@ -219,7 +219,7 @@
mView.animateInUdfpsBouncer(null);
}
- if (mKeyguardViewManager.isOccluded()) {
+ if (mKeyguardStateController.isOccluded()) {
mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
}
@@ -502,9 +502,9 @@
}
};
- private final PanelExpansionListener mPanelExpansionListener = new PanelExpansionListener() {
+ private final ShadeExpansionListener mShadeExpansionListener = new ShadeExpansionListener() {
@Override
- public void onPanelExpansionChanged(PanelExpansionChangeEvent event) {
+ public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
float fraction = event.getFraction();
mPanelExpansionFraction =
mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
index ca36375..379c819 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
@@ -52,7 +52,7 @@
private val userId: Int,
private val registerAction: BroadcastReceiver.(IntentFilter) -> Unit,
private val unregisterAction: BroadcastReceiver.() -> Unit,
- private val bgExecutor: Executor,
+ private val workerExecutor: Executor,
private val logger: BroadcastDispatcherLogger,
private val testPendingRemovalAction: (BroadcastReceiver, Int) -> Boolean
) : BroadcastReceiver(), Dumpable {
@@ -112,7 +112,7 @@
val id = index.getAndIncrement()
logger.logBroadcastReceived(id, userId, intent)
// Immediately return control to ActivityManager
- bgExecutor.execute {
+ workerExecutor.execute {
receiverDatas.forEach {
if (it.filter.matchCategories(intent.categories) == null &&
!testPendingRemovalAction(it.receiver, userId)) {
@@ -138,4 +138,4 @@
println("Categories: ${activeCategories.joinToString(", ")}")
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index eb8cb47..537cbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -34,7 +34,8 @@
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.BroadcastRunning
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserTracker
import java.io.PrintWriter
@@ -55,7 +56,6 @@
private const val MSG_REMOVE_RECEIVER = 1
private const val MSG_REMOVE_RECEIVER_FOR_USER = 2
private const val TAG = "BroadcastDispatcher"
-private const val DEBUG = true
/**
* SystemUI master Broadcast Dispatcher.
@@ -73,15 +73,16 @@
@SysUISingleton
open class BroadcastDispatcher @Inject constructor(
private val context: Context,
- @Background private val bgLooper: Looper,
- @Background private val bgExecutor: Executor,
+ @Main private val mainExecutor: Executor,
+ @BroadcastRunning private val broadcastLooper: Looper,
+ @BroadcastRunning private val broadcastExecutor: Executor,
private val dumpManager: DumpManager,
private val logger: BroadcastDispatcherLogger,
private val userTracker: UserTracker,
private val removalPendingStore: PendingRemovalStore
) : Dumpable {
- // Only modify in BG thread
+ // Only modify in BroadcastRunning thread
private val receiversByUser = SparseArray<UserBroadcastDispatcher>(20)
fun initialize() {
@@ -148,7 +149,7 @@
val data = ReceiverData(
receiver,
filter,
- executor ?: context.mainExecutor,
+ executor ?: mainExecutor,
user ?: context.user,
permission
)
@@ -181,7 +182,7 @@
registerReceiver(
receiver,
filter,
- bgExecutor,
+ broadcastExecutor,
user,
flags,
permission,
@@ -246,8 +247,8 @@
UserBroadcastDispatcher(
context,
userId,
- bgLooper,
- bgExecutor,
+ broadcastLooper,
+ broadcastExecutor,
logger,
removalPendingStore
)
@@ -265,7 +266,7 @@
ipw.decreaseIndent()
}
- private val handler = object : Handler(bgLooper) {
+ private val handler = object : Handler(broadcastLooper) {
override fun handleMessage(msg: Message) {
when (msg.what) {
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt
index d7b263a..c536e81 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt
@@ -16,16 +16,14 @@
package com.android.systemui.broadcast
-import android.content.Context
import com.android.systemui.CoreStartable
import javax.inject.Inject
class BroadcastDispatcherStartable @Inject constructor(
- context: Context,
val broadcastDispatcher: BroadcastDispatcher
-) : CoreStartable(context) {
+) : CoreStartable {
override fun start() {
broadcastDispatcher.initialize()
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 6b15188..22dc94a 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -16,6 +16,7 @@
package com.android.systemui.broadcast
+import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.os.Handler
@@ -46,8 +47,8 @@
open class UserBroadcastDispatcher(
private val context: Context,
private val userId: Int,
- private val bgLooper: Looper,
- private val bgExecutor: Executor,
+ private val workerLooper: Looper,
+ private val workerExecutor: Executor,
private val logger: BroadcastDispatcherLogger,
private val removalPendingStore: PendingRemovalStore
) : Dumpable {
@@ -66,9 +67,11 @@
val permission: String?
)
- private val bgHandler = Handler(bgLooper)
+ private val wrongThreadErrorMsg = "This method should only be called from the worker thread " +
+ "(which is expected to be the BroadcastRunning thread)"
+ private val workerHandler = Handler(workerLooper)
- // Only modify in BG thread
+ // Only modify in BroadcastRunning thread
@VisibleForTesting
internal val actionsToActionsReceivers = ArrayMap<ReceiverProperties, ActionReceiver>()
private val receiverToActions = ArrayMap<BroadcastReceiver, MutableSet<String>>()
@@ -97,8 +100,7 @@
}
private fun handleRegisterReceiver(receiverData: ReceiverData, flags: Int) {
- Preconditions.checkState(bgLooper.isCurrentThread,
- "This method should only be called from BG thread")
+ Preconditions.checkState(workerLooper.isCurrentThread, wrongThreadErrorMsg)
if (DEBUG) Log.w(TAG, "Register receiver: ${receiverData.receiver}")
receiverToActions
.getOrPut(receiverData.receiver, { ArraySet() })
@@ -113,6 +115,7 @@
logger.logReceiverRegistered(userId, receiverData.receiver, flags)
}
+ @SuppressLint("RegisterReceiverViaContextDetector")
@VisibleForTesting
internal open fun createActionReceiver(
action: String,
@@ -128,7 +131,7 @@
UserHandle.of(userId),
it,
permission,
- bgHandler,
+ workerHandler,
flags
)
logger.logContextReceiverRegistered(userId, flags, it)
@@ -143,15 +146,14 @@
IllegalStateException(e))
}
},
- bgExecutor,
+ workerExecutor,
logger,
removalPendingStore::isPendingRemoval
)
}
private fun handleUnregisterReceiver(receiver: BroadcastReceiver) {
- Preconditions.checkState(bgLooper.isCurrentThread,
- "This method should only be called from BG thread")
+ Preconditions.checkState(workerLooper.isCurrentThread, wrongThreadErrorMsg)
if (DEBUG) Log.w(TAG, "Unregister receiver: $receiver")
receiverToActions.getOrDefault(receiver, mutableSetOf()).forEach {
actionsToActionsReceivers.forEach { (key, value) ->
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index d53e56f..500f280 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 @@
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 @@
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 f526277..05e3f1c 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 @@
* 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 @@
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 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/common/ui/view/LaunchableImageView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
new file mode 100644
index 0000000..f95a8ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.common.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageView
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+class LaunchableImageView : ImageView, LaunchableView {
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ superSetTransitionVisibility = { super.setTransitionVisibility(it) },
+ )
+
+ constructor(context: Context?) : super(context)
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int,
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ delegate.setVisibility(visibility)
+ }
+
+ override fun setTransitionVisibility(visibility: Int) {
+ delegate.setTransitionVisibility(visibility)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a996699..48bef97 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -22,25 +22,19 @@
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.os.Handler;
-import android.os.PowerManager;
import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dagger.MediaModule;
import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
@@ -62,8 +56,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BatteryControllerImpl;
+import com.android.systemui.statusbar.policy.AospPolicyModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
@@ -97,6 +90,7 @@
* SystemUI code that variants of SystemUI _must_ include to function correctly.
*/
@Module(includes = {
+ AospPolicyModule.class,
GestureModule.class,
MediaModule.class,
PowerModule.class,
@@ -121,30 +115,6 @@
@Provides
@SysUISingleton
- static BatteryController provideBatteryController(
- Context context,
- EnhancedEstimates enhancedEstimates,
- PowerManager powerManager,
- BroadcastDispatcher broadcastDispatcher,
- DemoModeController demoModeController,
- DumpManager dumpManager,
- @Main Handler mainHandler,
- @Background Handler bgHandler) {
- BatteryController bC = new BatteryControllerImpl(
- context,
- enhancedEstimates,
- powerManager,
- broadcastDispatcher,
- demoModeController,
- dumpManager,
- mainHandler,
- bgHandler);
- bC.init();
- return bC;
- }
-
- @Provides
- @SysUISingleton
static SensorPrivacyController provideSensorPrivacyController(
SensorPrivacyManager sensorPrivacyManager) {
SensorPrivacyController spC = new SensorPrivacyControllerImpl(sensorPrivacyManager);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 0d06c51..d05bd51 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -27,10 +27,8 @@
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
-import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
-import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
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;
@@ -66,6 +64,7 @@
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
+ QsFrameTranslateModule.class,
SystemUIBinder.class,
SystemUIModule.class,
SystemUICoreStartableModule.class,
@@ -133,9 +132,6 @@
});
getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
// No init method needed, just needs to be gotten so that it's created.
- getMediaTttChipControllerSender();
- getMediaTttChipControllerReceiver();
- getMediaTttCommandLineHelper();
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
getUnfoldLatencyTracker().init();
@@ -206,15 +202,6 @@
Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();
/** */
- Optional<MediaTttChipControllerSender> getMediaTttChipControllerSender();
-
- /** */
- Optional<MediaTttChipControllerReceiver> getMediaTttChipControllerReceiver();
-
- /** */
- Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper();
-
- /** */
Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 8bb27a7..721c0ba 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -32,12 +32,16 @@
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.log.SessionTracker
import com.android.systemui.media.RingtonePlayer
+import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
+import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
import com.android.systemui.power.PowerUI
import com.android.systemui.recents.Recents
import com.android.systemui.settings.dagger.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.KeyguardLiftController
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
import com.android.systemui.toast.ToastUI
import com.android.systemui.usb.StorageNotification
@@ -213,4 +217,30 @@
@IntoMap
@ClassKey(KeyguardLiftController::class)
abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable
+
+ /** Inject into MediaTttSenderCoordinator. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaTttSenderCoordinator::class)
+ abstract fun bindMediaTttSenderCoordinator(sysui: MediaTttSenderCoordinator): CoreStartable
+
+ /** Inject into MediaTttChipControllerReceiver. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaTttChipControllerReceiver::class)
+ abstract fun bindMediaTttChipControllerReceiver(
+ sysui: MediaTttChipControllerReceiver
+ ): CoreStartable
+
+ /** Inject into MediaTttCommandLineHelper. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaTttCommandLineHelper::class)
+ abstract fun bindMediaTttCommandLineHelper(sysui: MediaTttCommandLineHelper): CoreStartable
+
+ /** Inject into ChipbarCoordinator. */
+ @Binds
+ @IntoMap
+ @ClassKey(ChipbarCoordinator::class)
+ abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 06dbab9..dc3dadb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,7 +43,7 @@
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.data.BouncerViewModule;
import com.android.systemui.log.dagger.LogModule;
-import com.android.systemui.media.dagger.MediaProjectionModule;
+import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent;
import com.android.systemui.people.PeopleModule;
@@ -61,7 +61,6 @@
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 @@
PeopleModule.class,
PluginModule.class,
PrivacyModule.class,
- QsFrameTranslateModule.class,
ScreenshotModule.class,
SensorModule.class,
MultiUserUtilsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
index ca667dd..5f8e540 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.dagger.qualifiers;
-/** A listener interface to be notified of state change events for the notification panel. */
-fun interface PanelStateListener {
- /** Called when the panel's expansion state has changed. */
- fun onPanelStateChanged(@PanelState state: Int)
+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 BroadcastRunning {
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 8ae305b..2e51b51 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -431,8 +431,8 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- traceKeyguard(showing);
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ traceKeyguard(visible);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 21a2c3b..cc57662 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -155,11 +155,11 @@
})
}
- fun logKeyguardVisibilityChange(isShowing: Boolean) {
+ fun logKeyguardVisibilityChange(isVisible: Boolean) {
buffer.log(TAG, INFO, {
- bool1 = isShowing
+ bool1 = isVisible
}, {
- "Keyguard visibility change, isShowing=$bool1"
+ "Keyguard visibility change, isVisible=$bool1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
index 99ca3c7..d145f5c 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 @@
* {@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 @@
@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/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 696fc72..d1b7368 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -64,29 +64,26 @@
private final Executor mExecutor;
// A controller for the dream overlay container view (which contains both the status bar and the
// content area).
- private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
+ private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Nullable
private final ComponentName mLowLightDreamComponent;
private final UiEventLogger mUiEventLogger;
+ private final WindowManager mWindowManager;
// A reference to the {@link Window} used to hold the dream overlay.
private Window mWindow;
- // True if the service has been destroyed.
- private boolean mDestroyed;
+ // True if a dream has bound to the service and dream overlay service has started.
+ private boolean mStarted = false;
- private final Complication.Host mHost = new Complication.Host() {
- @Override
- public void requestExitDream() {
- mExecutor.execute(DreamOverlayService.this::requestExit);
- }
- };
+ // True if the service has been destroyed.
+ private boolean mDestroyed = false;
+
+ private final DreamOverlayComponent mDreamOverlayComponent;
private final LifecycleRegistry mLifecycleRegistry;
- private ViewModelStore mViewModelStore = new ViewModelStore();
-
private DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
private final KeyguardUpdateMonitorCallback mKeyguardCallback =
@@ -103,7 +100,7 @@
}
};
- private DreamOverlayStateController mStateController;
+ private final DreamOverlayStateController mStateController;
@VisibleForTesting
public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
@@ -128,6 +125,7 @@
public DreamOverlayService(
Context context,
@Main Executor executor,
+ WindowManager windowManager,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -136,19 +134,19 @@
ComponentName lowLightDreamComponent) {
mContext = context;
mExecutor = executor;
+ mWindowManager = windowManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLowLightDreamComponent = lowLightDreamComponent;
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
mStateController = stateController;
mUiEventLogger = uiEventLogger;
- final DreamOverlayComponent component =
- dreamOverlayComponentFactory.create(mViewModelStore, mHost);
- mDreamOverlayContainerViewController = component.getDreamOverlayContainerViewController();
+ final ViewModelStore viewModelStore = new ViewModelStore();
+ final Complication.Host host =
+ () -> mExecutor.execute(DreamOverlayService.this::requestExit);
+ mDreamOverlayComponent = dreamOverlayComponentFactory.create(viewModelStore, host);
+ mLifecycleRegistry = mDreamOverlayComponent.getLifecycleRegistry();
setCurrentState(Lifecycle.State.CREATED);
- mLifecycleRegistry = component.getLifecycleRegistry();
- mDreamOverlayTouchMonitor = component.getDreamOverlayTouchMonitor();
- mDreamOverlayTouchMonitor.init();
}
private void setCurrentState(Lifecycle.State state) {
@@ -159,34 +157,48 @@
public void onDestroy() {
mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
setCurrentState(Lifecycle.State.DESTROYED);
- final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- if (mWindow != null) {
- windowManager.removeView(mWindow.getDecorView());
- }
- mStateController.setOverlayActive(false);
- mStateController.setLowLightActive(false);
+
+ resetCurrentDreamOverlay();
+
mDestroyed = true;
super.onDestroy();
}
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
- mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
setCurrentState(Lifecycle.State.STARTED);
- final ComponentName dreamComponent = getDreamComponent();
- mStateController.setLowLightActive(
- dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
+
mExecutor.execute(() -> {
+ mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
+
if (mDestroyed) {
// The task could still be executed after the service has been destroyed. Bail if
// that is the case.
return;
}
+
+ if (mStarted) {
+ // Reset the current dream overlay before starting a new one. This can happen
+ // when two dreams overlap (briefly, for a smoother dream transition) and both
+ // dreams are bound to the dream overlay service.
+ resetCurrentDreamOverlay();
+ }
+
+ mDreamOverlayContainerViewController =
+ mDreamOverlayComponent.getDreamOverlayContainerViewController();
+ mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
+ mDreamOverlayTouchMonitor.init();
+
mStateController.setShouldShowComplications(shouldShowComplications());
addOverlayWindowLocked(layoutParams);
setCurrentState(Lifecycle.State.RESUMED);
mStateController.setOverlayActive(true);
+ final ComponentName dreamComponent = getDreamComponent();
+ mStateController.setLowLightActive(
+ dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
+
+ mStarted = true;
});
}
@@ -222,8 +234,7 @@
removeContainerViewFromParent();
mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
- final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+ mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
}
private void removeContainerViewFromParent() {
@@ -238,4 +249,18 @@
Log.w(TAG, "Removing dream overlay container view parent!");
parentView.removeView(containerView);
}
+
+ private void resetCurrentDreamOverlay() {
+ if (mStarted && mWindow != null) {
+ mWindowManager.removeView(mWindow.getDecorView());
+ }
+
+ mStateController.setOverlayActive(false);
+ mStateController.setLowLightActive(false);
+
+ mDreamOverlayContainerViewController = null;
+ mDreamOverlayTouchMonitor = null;
+
+ mStarted = false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 9cd149b..5694f6d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -18,7 +18,7 @@
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_IN_DURATION;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DURATION;
-import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN;
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
import android.animation.Animator;
@@ -67,7 +67,7 @@
private final Parent mParent;
@Complication.Category
private final int mCategory;
- private final int mMargin;
+ private final int mDefaultMargin;
/**
* Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
@@ -75,7 +75,7 @@
*/
ViewEntry(View view, ComplicationLayoutParams layoutParams,
TouchInsetManager.TouchInsetSession touchSession, int category, Parent parent,
- int margin) {
+ int defaultMargin) {
mView = view;
// Views that are generated programmatically do not have a unique id assigned to them
// at construction. A new id is assigned here to enable ConstraintLayout relative
@@ -86,7 +86,7 @@
mTouchInsetSession = touchSession;
mCategory = category;
mParent = parent;
- mMargin = margin;
+ mDefaultMargin = defaultMargin;
touchSession.addViewToTracking(mView);
}
@@ -195,18 +195,19 @@
}
if (!isRoot) {
+ final int margin = mLayoutParams.getMargin(mDefaultMargin);
switch(direction) {
case ComplicationLayoutParams.DIRECTION_DOWN:
- params.setMargins(0, mMargin, 0, 0);
+ params.setMargins(0, margin, 0, 0);
break;
case ComplicationLayoutParams.DIRECTION_UP:
- params.setMargins(0, 0, 0, mMargin);
+ params.setMargins(0, 0, 0, margin);
break;
case ComplicationLayoutParams.DIRECTION_END:
- params.setMarginStart(mMargin);
+ params.setMarginStart(margin);
break;
case ComplicationLayoutParams.DIRECTION_START:
- params.setMarginEnd(mMargin);
+ params.setMarginEnd(margin);
break;
}
}
@@ -263,7 +264,7 @@
private final ComplicationLayoutParams mLayoutParams;
private final int mCategory;
private Parent mParent;
- private int mMargin;
+ private int mDefaultMargin;
Builder(View view, TouchInsetManager.TouchInsetSession touchSession,
ComplicationLayoutParams lp, @Complication.Category int category) {
@@ -302,8 +303,8 @@
* Sets the margin that will be applied in the direction the complication is laid out
* towards.
*/
- Builder setMargin(int margin) {
- mMargin = margin;
+ Builder setDefaultMargin(int margin) {
+ mDefaultMargin = margin;
return this;
}
@@ -312,7 +313,7 @@
*/
ViewEntry build() {
return new ViewEntry(mView, mLayoutParams, mTouchSession, mCategory, mParent,
- mMargin);
+ mDefaultMargin);
}
}
@@ -472,7 +473,7 @@
}
private final ConstraintLayout mLayout;
- private final int mMargin;
+ private final int mDefaultMargin;
private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
private final TouchInsetManager.TouchInsetSession mSession;
@@ -483,12 +484,12 @@
/** */
@Inject
public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout,
- @Named(COMPLICATION_MARGIN) int margin,
+ @Named(COMPLICATION_MARGIN_DEFAULT) int defaultMargin,
TouchInsetManager.TouchInsetSession session,
@Named(COMPLICATIONS_FADE_IN_DURATION) int fadeInDuration,
@Named(COMPLICATIONS_FADE_OUT_DURATION) int fadeOutDuration) {
mLayout = layout;
- mMargin = margin;
+ mDefaultMargin = defaultMargin;
mSession = session;
mFadeInDuration = fadeInDuration;
mFadeOutDuration = fadeOutDuration;
@@ -537,7 +538,7 @@
}
final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, mSession, lp, category)
- .setMargin(mMargin);
+ .setDefaultMargin(mDefaultMargin);
// Add position group if doesn't already exist
final int position = lp.getPosition();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index 8e8cb72..a21eb19 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -51,6 +51,8 @@
private static final int FIRST_POSITION = POSITION_TOP;
private static final int LAST_POSITION = POSITION_END;
+ private static final int MARGIN_UNSPECIFIED = 0xFFFFFFFF;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
DIRECTION_UP,
@@ -77,6 +79,8 @@
private final int mWeight;
+ private final int mMargin;
+
private final boolean mSnapToGuide;
// Do not allow specifying opposite positions
@@ -106,7 +110,24 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight) {
- this(width, height, position, direction, weight, false);
+ this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, false);
+ }
+
+ /**
+ * Constructs a {@link ComplicationLayoutParams}.
+ * @param width The width {@link android.view.View.MeasureSpec} for the view.
+ * @param height The height {@link android.view.View.MeasureSpec} for the view.
+ * @param position The place within the parent container where the view should be positioned.
+ * @param direction The direction the view should be laid out from either the parent container
+ * or preceding view.
+ * @param weight The weight that should be considered for this view when compared to other
+ * views. This has an impact on the placement of the view but not the rendering of
+ * the view.
+ * @param margin The margin to apply between complications.
+ */
+ public ComplicationLayoutParams(int width, int height, @Position int position,
+ @Direction int direction, int weight, int margin) {
+ this(width, height, position, direction, weight, margin, false);
}
/**
@@ -127,6 +148,28 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight, boolean snapToGuide) {
+ this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, snapToGuide);
+ }
+
+ /**
+ * Constructs a {@link ComplicationLayoutParams}.
+ * @param width The width {@link android.view.View.MeasureSpec} for the view.
+ * @param height The height {@link android.view.View.MeasureSpec} for the view.
+ * @param position The place within the parent container where the view should be positioned.
+ * @param direction The direction the view should be laid out from either the parent container
+ * or preceding view.
+ * @param weight The weight that should be considered for this view when compared to other
+ * views. This has an impact on the placement of the view but not the rendering of
+ * the view.
+ * @param margin The margin to apply between complications.
+ * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
+ * will be automatically set to align with a predetermined guide for that
+ * side. For example, if the complication is aligned to the top end and
+ * direction is down, then the width of the complication will be set to span
+ * from the end of the parent to the guide.
+ */
+ public ComplicationLayoutParams(int width, int height, @Position int position,
+ @Direction int direction, int weight, int margin, boolean snapToGuide) {
super(width, height);
if (!validatePosition(position)) {
@@ -142,6 +185,8 @@
mWeight = weight;
+ mMargin = margin;
+
mSnapToGuide = snapToGuide;
}
@@ -153,6 +198,7 @@
mPosition = source.mPosition;
mDirection = source.mDirection;
mWeight = source.mWeight;
+ mMargin = source.mMargin;
mSnapToGuide = source.mSnapToGuide;
}
@@ -215,6 +261,14 @@
}
/**
+ * Returns the margin to apply between complications, or the given default if no margin is
+ * specified.
+ */
+ public int getMargin(int defaultMargin) {
+ return mMargin == MARGIN_UNSPECIFIED ? defaultMargin : mMargin;
+ }
+
+ /**
* Returns whether the complication's dimension perpendicular to direction should be
* automatically set.
*/
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 bbcab60..ee2f1af 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 @@
* 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 @@
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 675a2f4..77e1fc9 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 @@
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 @@
* {@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 @@
* 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 821e13e..0ccb222 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 @@
/**
* {@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 @@
};
@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 a981f25..c3aaf0c 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 @@
* {@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 @@
* 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/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index 11d89d2..c9fecc9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -37,7 +37,7 @@
@Module
public abstract class ComplicationHostViewModule {
public static final String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout";
- public static final String COMPLICATION_MARGIN = "complication_margin";
+ public static final String COMPLICATION_MARGIN_DEFAULT = "complication_margin_default";
public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
@@ -58,7 +58,7 @@
}
@Provides
- @Named(COMPLICATION_MARGIN)
+ @Named(COMPLICATION_MARGIN_DEFAULT)
@DreamOverlayComponent.DreamOverlayScope
static int providesComplicationPadding(@Main Resources resources) {
return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 759d6ec..7d2ce51 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -59,11 +59,11 @@
@Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
static ComplicationLayoutParams provideClockTimeLayoutParams() {
return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
}
/**
@@ -73,12 +73,12 @@
@Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) {
return new ComplicationLayoutParams(
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_END,
- DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
}
/**
@@ -103,11 +103,12 @@
@Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
static ComplicationLayoutParams provideSmartspaceLayoutParams() {
return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
- true /*snapToGuide*/);
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
+ 0,
+ true /*snapToGuide*/);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index f769a23..0dba4ff 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -36,11 +36,11 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
import com.android.wm.shell.animation.FlingAnimationUtils;
import java.util.Optional;
@@ -154,8 +154,8 @@
private void setPanelExpansion(float expansion, float dragDownAmount) {
mCurrentExpansion = expansion;
- PanelExpansionChangeEvent event =
- new PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent event =
+ new ShadeExpansionChangeEvent(
/* fraction= */ mCurrentExpansion,
/* expanded= */ false,
/* tracking= */ true,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
index dfa3bcd..fb4fc92 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -16,12 +16,14 @@
package com.android.systemui.flags
+import android.util.Dumpable
+
/**
* Class to manage simple DeviceConfig-based feature flags.
*
* See [Flags] for instructions on defining new flags.
*/
-interface FeatureFlags : FlagListenable {
+interface FeatureFlags : FlagListenable, Dumpable {
/** Returns a boolean value for the given flag. */
fun isEnabled(flag: UnreleasedFlag): Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 00c1a99..b983e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -30,29 +30,21 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.commandline.Command;
-import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.settings.SecureSettings;
import org.jetbrains.annotations.NotNull;
import java.io.PrintWriter;
-import java.lang.reflect.Field;
import java.util.ArrayList;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
@@ -75,10 +67,9 @@
* To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/
@SysUISingleton
-public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
- private static final String TAG = "SysUIFlags";
+public class FeatureFlagsDebug implements FeatureFlags {
+ static final String TAG = "SysUIFlags";
static final String ALL_FLAGS = "all_flags";
- private static final String FLAG_COMMAND = "flag";
private final FlagManager mFlagManager;
private final SecureSettings mSecureSettings;
@@ -89,7 +80,7 @@
private final Map<Integer, Flag<?>> mAllFlags;
private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
- private final IStatusBarService mBarService;
+ private final Restarter mRestarter;
@Inject
public FeatureFlagsDebug(
@@ -98,12 +89,10 @@
SecureSettings secureSettings,
SystemPropertiesHelper systemProperties,
@Main Resources resources,
- DumpManager dumpManager,
DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
- CommandRegistry commandRegistry,
- IStatusBarService barService) {
+ Restarter barService) {
mFlagManager = flagManager;
mSecureSettings = secureSettings;
mResources = resources;
@@ -111,7 +100,7 @@
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
- mBarService = barService;
+ mRestarter = barService;
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG);
@@ -120,8 +109,6 @@
flagManager.setClearCacheAction(this::removeFromCache);
context.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
- dumpManager.registerDumpable(TAG, this);
- commandRegistry.registerCommand(FLAG_COMMAND, FlagCommand::new);
}
@Override
@@ -266,7 +253,7 @@
mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
}
- private <T> void eraseFlag(Flag<T> flag) {
+ <T> void eraseFlag(Flag<T> flag) {
if (flag instanceof SysPropFlag) {
mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid);
@@ -319,13 +306,10 @@
return;
}
Log.i(TAG, "Restarting Android");
- try {
- mBarService.restart();
- } catch (RemoteException e) {
- }
+ mRestarter.restart();
}
- private void setBooleanFlagInternal(Flag<?> flag, boolean value) {
+ void setBooleanFlagInternal(Flag<?> flag, boolean value) {
if (flag instanceof BooleanFlag) {
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceBooleanFlag) {
@@ -342,7 +326,7 @@
}
}
- private void setStringFlagInternal(Flag<?> flag, String value) {
+ void setStringFlagInternal(Flag<?> flag, String value) {
if (flag instanceof StringFlag) {
setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceStringFlag) {
@@ -476,154 +460,4 @@
+ ": [length=" + value.length() + "] \"" + value + "\""));
}
- class FlagCommand implements Command {
- private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
- private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
-
- @Override
- public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
- if (args.size() == 0) {
- pw.println("Error: no flag id supplied");
- help(pw);
- pw.println();
- printKnownFlags(pw);
- return;
- }
-
- if (args.size() > 2) {
- pw.println("Invalid number of arguments.");
- help(pw);
- return;
- }
-
- int id = 0;
- try {
- id = Integer.parseInt(args.get(0));
- if (!mAllFlags.containsKey(id)) {
- pw.println("Unknown flag id: " + id);
- pw.println();
- printKnownFlags(pw);
- return;
- }
- } catch (NumberFormatException e) {
- id = flagNameToId(args.get(0));
- if (id == 0) {
- pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0));
- return;
- }
- }
- Flag<?> flag = mAllFlags.get(id);
-
- String cmd = "";
- if (args.size() == 2) {
- cmd = args.get(1).toLowerCase();
- }
-
- if ("erase".equals(cmd) || "reset".equals(cmd)) {
- eraseFlag(flag);
- return;
- }
-
- boolean newValue = true;
- if (args.size() == 1 || "toggle".equals(cmd)) {
- boolean enabled = isBooleanFlagEnabled(flag);
-
- if (args.size() == 1) {
- pw.println("Flag " + id + " is " + enabled);
- return;
- }
-
- newValue = !enabled;
- } else {
- newValue = mOnCommands.contains(cmd);
- if (!newValue && !mOffCommands.contains(cmd)) {
- pw.println("Invalid on/off argument supplied");
- help(pw);
- return;
- }
- }
-
- pw.flush(); // Next command will restart sysui, so flush before we do so.
- setBooleanFlagInternal(flag, newValue);
- }
-
- @Override
- public void help(PrintWriter pw) {
- pw.println(
- "Usage: adb shell cmd statusbar flag <id> "
- + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
- pw.println("The id can either be a numeric integer or the corresponding field name");
- pw.println(
- "If no argument is supplied after the id, the flags runtime value is output");
- }
-
- private boolean isBooleanFlagEnabled(Flag<?> flag) {
- if (flag instanceof ReleasedFlag) {
- return isEnabled((ReleasedFlag) flag);
- } else if (flag instanceof UnreleasedFlag) {
- return isEnabled((UnreleasedFlag) flag);
- } else if (flag instanceof ResourceBooleanFlag) {
- return isEnabled((ResourceBooleanFlag) flag);
- } else if (flag instanceof SysPropFlag) {
- return isEnabled((SysPropBooleanFlag) flag);
- }
-
- return false;
- }
-
- private int flagNameToId(String flagName) {
- List<Field> fields = Flags.getFlagFields();
- for (Field field : fields) {
- if (flagName.equals(field.getName())) {
- return fieldToId(field);
- }
- }
-
- return 0;
- }
-
- private int fieldToId(Field field) {
- try {
- Flag<?> flag = (Flag<?>) field.get(null);
- return flag.getId();
- } catch (IllegalAccessException e) {
- // no-op
- }
-
- return 0;
- }
-
- private void printKnownFlags(PrintWriter pw) {
- List<Field> fields = Flags.getFlagFields();
-
- int longestFieldName = 0;
- for (Field field : fields) {
- longestFieldName = Math.max(longestFieldName, field.getName().length());
- }
-
- pw.println("Known Flags:");
- pw.print("Flag Name");
- for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
- pw.print(" ");
- }
- pw.println("ID Enabled?");
- for (int i = 0; i < longestFieldName; i++) {
- pw.print("=");
- }
- pw.println(" ==== ========");
- for (Field field : fields) {
- int id = fieldToId(field);
- if (id == 0 || !mAllFlags.containsKey(id)) {
- continue;
- }
- pw.print(field.getName());
- int fieldWidth = field.getName().length();
- for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
- pw.print(" ");
- }
- pw.printf("%-4d ", id);
- pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
new file mode 100644
index 0000000..560dcbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.flags
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+
+class FeatureFlagsDebugStartable
+@Inject
+constructor(
+ dumpManager: DumpManager,
+ private val commandRegistry: CommandRegistry,
+ private val flagCommand: FlagCommand,
+ featureFlags: FeatureFlags
+) : CoreStartable {
+
+ init {
+ dumpManager.registerDumpable(FeatureFlagsDebug.TAG) { pw, args ->
+ featureFlags.dump(pw, args)
+ }
+ }
+
+ override fun start() {
+ commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
+ }
+}
+
+@Module
+abstract class FeatureFlagsDebugStartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(FeatureFlagsDebugStartable::class)
+ abstract fun bind(impl: FeatureFlagsDebugStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 049b17d..40a8a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -24,10 +24,8 @@
import androidx.annotation.NonNull;
-import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.DeviceConfigProxy;
import org.jetbrains.annotations.NotNull;
@@ -44,27 +42,26 @@
* how to set flags.
*/
@SysUISingleton
-public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
+public class FeatureFlagsRelease implements FeatureFlags {
+ static final String TAG = "SysUIFlags";
+
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader;
SparseBooleanArray mBooleanCache = new SparseBooleanArray();
SparseArray<String> mStringCache = new SparseArray<>();
- private boolean mInited;
@Inject
public FeatureFlagsRelease(
@Main Resources resources,
SystemPropertiesHelper systemProperties,
DeviceConfigProxy deviceConfigProxy,
- ServerFlagReader serverFlagReader,
- DumpManager dumpManager) {
+ ServerFlagReader serverFlagReader) {
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
- dumpManager.registerDumpable("SysUIFlags", this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
new file mode 100644
index 0000000..e7d8cc3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.flags
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dump.DumpManager
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+
+class FeatureFlagsReleaseStartable
+@Inject
+constructor(dumpManager: DumpManager, featureFlags: FeatureFlags) : CoreStartable {
+
+ init {
+ dumpManager.registerDumpable(FeatureFlagsRelease.TAG) { pw, args ->
+ featureFlags.dump(pw, args)
+ }
+ }
+
+ override fun start() {
+ // no-op
+ }
+}
+
+@Module
+abstract class FeatureFlagsReleaseStartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(FeatureFlagsReleaseStartable::class)
+ abstract fun bind(impl: FeatureFlagsReleaseStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
new file mode 100644
index 0000000..4d25431
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -0,0 +1,196 @@
+/*
+ * 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.flags;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.statusbar.commandline.Command;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * A {@link Command} used to flip flags in SystemUI.
+ */
+public class FlagCommand implements Command {
+ public static final String FLAG_COMMAND = "flag";
+
+ private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
+ private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
+ private final FeatureFlagsDebug mFeatureFlags;
+ private final Map<Integer, Flag<?>> mAllFlags;
+
+ @Inject
+ FlagCommand(
+ FeatureFlagsDebug featureFlags,
+ @Named(FeatureFlagsDebug.ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+ ) {
+ mFeatureFlags = featureFlags;
+ mAllFlags = allFlags;
+ }
+
+ @Override
+ public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
+ if (args.size() == 0) {
+ pw.println("Error: no flag id supplied");
+ help(pw);
+ pw.println();
+ printKnownFlags(pw);
+ return;
+ }
+
+ if (args.size() > 2) {
+ pw.println("Invalid number of arguments.");
+ help(pw);
+ return;
+ }
+
+ int id = 0;
+ try {
+ id = Integer.parseInt(args.get(0));
+ if (!mAllFlags.containsKey(id)) {
+ pw.println("Unknown flag id: " + id);
+ pw.println();
+ printKnownFlags(pw);
+ return;
+ }
+ } catch (NumberFormatException e) {
+ id = flagNameToId(args.get(0));
+ if (id == 0) {
+ pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0));
+ return;
+ }
+ }
+ Flag<?> flag = mAllFlags.get(id);
+
+ String cmd = "";
+ if (args.size() == 2) {
+ cmd = args.get(1).toLowerCase();
+ }
+
+ if ("erase".equals(cmd) || "reset".equals(cmd)) {
+ mFeatureFlags.eraseFlag(flag);
+ return;
+ }
+
+ boolean newValue = true;
+ if (args.size() == 1 || "toggle".equals(cmd)) {
+ boolean enabled = isBooleanFlagEnabled(flag);
+
+ if (args.size() == 1) {
+ pw.println("Flag " + id + " is " + enabled);
+ return;
+ }
+
+ newValue = !enabled;
+ } else {
+ newValue = mOnCommands.contains(cmd);
+ if (!newValue && !mOffCommands.contains(cmd)) {
+ pw.println("Invalid on/off argument supplied");
+ help(pw);
+ return;
+ }
+ }
+
+ pw.flush(); // Next command will restart sysui, so flush before we do so.
+ mFeatureFlags.setBooleanFlagInternal(flag, newValue);
+ }
+
+ @Override
+ public void help(PrintWriter pw) {
+ pw.println(
+ "Usage: adb shell cmd statusbar flag <id> "
+ + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
+ pw.println("The id can either be a numeric integer or the corresponding field name");
+ pw.println(
+ "If no argument is supplied after the id, the flags runtime value is output");
+ }
+
+ private boolean isBooleanFlagEnabled(Flag<?> flag) {
+ if (flag instanceof ReleasedFlag) {
+ return mFeatureFlags.isEnabled((ReleasedFlag) flag);
+ } else if (flag instanceof UnreleasedFlag) {
+ return mFeatureFlags.isEnabled((UnreleasedFlag) flag);
+ } else if (flag instanceof ResourceBooleanFlag) {
+ return mFeatureFlags.isEnabled((ResourceBooleanFlag) flag);
+ } else if (flag instanceof SysPropFlag) {
+ return mFeatureFlags.isEnabled((SysPropBooleanFlag) flag);
+ }
+
+ return false;
+ }
+
+ private int flagNameToId(String flagName) {
+ List<Field> fields = Flags.getFlagFields();
+ for (Field field : fields) {
+ if (flagName.equals(field.getName())) {
+ return fieldToId(field);
+ }
+ }
+
+ return 0;
+ }
+
+ private int fieldToId(Field field) {
+ try {
+ Flag<?> flag = (Flag<?>) field.get(null);
+ return flag.getId();
+ } catch (IllegalAccessException e) {
+ // no-op
+ }
+
+ return 0;
+ }
+
+ private void printKnownFlags(PrintWriter pw) {
+ List<Field> fields = Flags.getFlagFields();
+
+ int longestFieldName = 0;
+ for (Field field : fields) {
+ longestFieldName = Math.max(longestFieldName, field.getName().length());
+ }
+
+ pw.println("Known Flags:");
+ pw.print("Flag Name");
+ for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
+ pw.print(" ");
+ }
+ pw.println("ID Enabled?");
+ for (int i = 0; i < longestFieldName; i++) {
+ pw.print("=");
+ }
+ pw.println(" ==== ========");
+ for (Field field : fields) {
+ int id = fieldToId(field);
+ if (id == 0 || !mAllFlags.containsKey(id)) {
+ continue;
+ }
+ pw.print(field.getName());
+ int fieldWidth = field.getName().length();
+ for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
+ pw.print(" ");
+ }
+ pw.printf("%-4d ", id);
+ pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 8b8d57f..5506f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -68,7 +68,12 @@
public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
- // next id: 112
+ public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
+ false);
+
+ public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true);
+
+ // next id: 114
/***************************************/
// 200 - keyguard/lockscreen
@@ -100,18 +105,14 @@
*/
public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208);
- /** Whether UserSwitcherActivity should use modern architecture. */
- public static final ReleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
- new ReleasedFlag(209, true);
-
/**
* Whether the user interactor and repository should use `UserSwitcherController`.
*
* <p>If this is {@code false}, the interactor and repo skip the controller and directly access
* the framework APIs.
*/
- public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
- new UnreleasedFlag(210, true);
+ public static final ReleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
+ new ReleasedFlag(210);
/**
* Whether `UserSwitcherController` should use the user interactor.
@@ -122,8 +123,7 @@
* <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is
* {@code true} as it would created a cycle between controller -> interactor -> controller.
*/
- public static final UnreleasedFlag USER_CONTROLLER_USES_INTERACTOR =
- new UnreleasedFlag(211, false);
+ public static final UnreleasedFlag USER_CONTROLLER_USES_INTERACTOR = new UnreleasedFlag(211);
/***************************************/
// 300 - power menu
@@ -206,26 +206,30 @@
public static final UnreleasedFlag REGION_SAMPLING = new UnreleasedFlag(801);
// 802 - wallpaper rendering
- public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802);
+ public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802, true);
// 803 - screen contents translation
public static final UnreleasedFlag SCREEN_CONTENTS_TRANSLATION = new UnreleasedFlag(803);
/***************************************/
// 900 - media
- public static final UnreleasedFlag MEDIA_TAP_TO_TRANSFER = new UnreleasedFlag(900);
+ public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900);
public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901);
public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903);
public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904);
public static final UnreleasedFlag 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 =
new ReleasedFlag(1000);
public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001);
- public static final UnreleasedFlag ROUNDED_BOX_RIPPLE = new UnreleasedFlag(1002, false);
+ 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
@@ -294,8 +298,8 @@
public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301);
- // 1400 - columbus, b/242800729
- public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
+ // 1400 - columbus
+ public static final ReleasedFlag QUICK_TAP_IN_PCC = new ReleasedFlag(1400);
// 1500 - chooser
public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
copy to packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
index ca667dd..8f095a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,11 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.flags
-package com.android.systemui.statusbar.phone.panelstate
-
-/** A listener interface to be notified of state change events for the notification panel. */
-fun interface PanelStateListener {
- /** Called when the panel's expansion state has changed. */
- fun onPanelStateChanged(@PanelState state: Int)
-}
+interface Restarter {
+ fun restart()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index 74d5bd5..9f321d8 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 @@
* 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 @@
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 568143c..4f1a2b3 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.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 @@
/** */
@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 @@
@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 @@
}
@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 @@
}
@Override
- protected void onBootCompleted() {
+ public void onBootCompleted() {
mHandler.sendEmptyMessage(MSG_ON_BOOT_COMPLETED);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 5f96a3b..a908e94 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -666,7 +666,7 @@
return
}
- if (keyguardViewController.isShowing && !playingCannedUnlockAnimation) {
+ if (keyguardStateController.isShowing && !playingCannedUnlockAnimation) {
showOrHideSurfaceIfDismissAmountThresholdsReached()
// If the surface is visible or it's about to be, start updating its appearance to
@@ -726,7 +726,7 @@
private fun finishKeyguardExitRemoteAnimationIfReachThreshold() {
// no-op if keyguard is not showing or animation is not enabled.
if (!KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation ||
- !keyguardViewController.isShowing) {
+ !keyguardStateController.isShowing) {
return
}
@@ -849,7 +849,7 @@
* animation.
*/
fun hideKeyguardViewAfterRemoteAnimation() {
- if (keyguardViewController.isShowing) {
+ if (keyguardStateController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
keyguardViewController.hide(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 38b98eb..a363270 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -25,7 +25,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
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_LOCKOUT;
@@ -125,6 +124,7 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -135,7 +135,6 @@
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
@@ -187,7 +186,7 @@
* 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;
@@ -273,6 +272,7 @@
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 */
@@ -512,9 +512,9 @@
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
synchronized (KeyguardViewMediator.this) {
- if (!showing && mPendingPinLock) {
+ if (!visible && mPendingPinLock) {
Log.i(TAG, "PIN lock requested, starting keyguard");
// Bring the keyguard back in order to show the PIN lock
@@ -794,6 +794,8 @@
KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker =
mUpdateMonitor.getStrongAuthTracker();
int strongAuth = strongAuthTracker.getStrongAuthForUser(currentUser);
+ boolean allowedNonStrongAfterIdleTimeout =
+ strongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(currentUser);
if (any && !strongAuthTracker.hasUserAuthenticatedSinceBoot()) {
return KeyguardSecurityView.PROMPT_REASON_RESTART;
@@ -804,9 +806,6 @@
} else if (trustAgentsEnabled
&& (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
- } else if (trustAgentsEnabled
- && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) {
- return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
} else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
|| mUpdateMonitor.isFingerprintLockedOut())) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
@@ -815,6 +814,8 @@
} 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;
}
@@ -987,9 +988,11 @@
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
- if (mUnoccludeAnimator != null) {
- mUnoccludeAnimator.cancel();
- }
+ mContext.getMainExecutor().execute(() -> {
+ if (mUnoccludeAnimator != null) {
+ mUnoccludeAnimator.cancel();
+ }
+ });
setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
@@ -1130,7 +1133,7 @@
DreamOverlayStateController dreamOverlayStateController,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
- super(context);
+ mContext = context;
mFalsingCollector = falsingCollector;
mLockPatternUtils = lockPatternUtils;
mBroadcastDispatcher = broadcastDispatcher;
@@ -1808,7 +1811,6 @@
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
- mUpdateMonitor.setKeyguardOccluded(isOccluded);
mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
&& mDeviceInteractive);
adjustStatusBarLocked();
@@ -1884,7 +1886,7 @@
// if the keyguard is already showing, don't bother. check flags in both files
// to account for the hiding animation which results in a delay and discrepancy
// between flags
- if (mShowing && mKeyguardViewControllerLazy.get().isShowing()) {
+ if (mShowing && mKeyguardStateController.isShowing()) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
resetStateLocked();
return;
@@ -2975,14 +2977,14 @@
*/
public KeyguardViewController registerCentralSurfaces(CentralSurfaces centralSurfaces,
NotificationPanelViewController panelView,
- @Nullable PanelExpansionStateManager panelExpansionStateManager,
+ @Nullable ShadeExpansionStateManager shadeExpansionStateManager,
BiometricUnlockController biometricUnlockController,
View notificationContainer, KeyguardBypassController bypassController) {
mCentralSurfaces = centralSurfaces;
mKeyguardViewControllerLazy.get().registerCentralSurfaces(
centralSurfaces,
panelView,
- panelExpansionStateManager,
+ shadeExpansionStateManager,
biometricUnlockController,
notificationContainer,
bypassController);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 56f1ac4..56a1f1a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,6 +43,7 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -72,6 +73,7 @@
FalsingModule.class,
KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
+ StartKeyguardTransitionModule.class,
})
public class KeyguardModule {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 4c4b588..b186ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -21,6 +21,7 @@
import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.doze.DozeHost
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
@@ -85,6 +86,9 @@
*/
val dozeAmount: Flow<Float>
+ /** Observable for the [StatusBarState] */
+ val statusBarState: Flow<StatusBarState>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -157,7 +161,11 @@
}
}
dozeHost.addCallback(callback)
- trySendWithFailureLogging(false, TAG, "initial isDozing: false")
+ trySendWithFailureLogging(
+ statusBarStateController.isDozing,
+ TAG,
+ "initial isDozing",
+ )
awaitClose { dozeHost.removeCallback(callback) }
}
@@ -181,6 +189,24 @@
return keyguardStateController.isShowing
}
+ override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(state: Int) {
+ trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state")
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(
+ statusBarStateIntToObject(statusBarStateController.getState()),
+ TAG,
+ "initial state"
+ )
+
+ awaitClose { statusBarStateController.removeCallback(callback) }
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate
}
@@ -193,6 +219,15 @@
_clockPosition.value = Position(x, y)
}
+ private fun statusBarStateIntToObject(value: Int): StatusBarState {
+ return when (value) {
+ 0 -> StatusBarState.SHADE
+ 1 -> StatusBarState.KEYGUARD
+ 2 -> StatusBarState.SHADE_LOCKED
+ else -> throw IllegalArgumentException("Invalid StatusBarState value: $value")
+ }
+ }
+
companion object {
private const val TAG = "KeyguardRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
new file mode 100644
index 0000000..e8532ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.data.repository
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
+import android.annotation.FloatRange
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filter
+
+@SysUISingleton
+class KeyguardTransitionRepository @Inject constructor() {
+ /*
+ * Each transition between [KeyguardState]s will have an associated Flow.
+ * In order to collect these events, clients should call [transition].
+ */
+ private val _transitions = MutableStateFlow(TransitionStep())
+ val transitions = _transitions.asStateFlow()
+
+ /* Information about the active transition. */
+ private var currentTransitionInfo: TransitionInfo? = null
+ /*
+ * When manual control of the transition is requested, a unique [UUID] is used as the handle
+ * to permit calls to [updateTransition]
+ */
+ private var updateTransitionId: UUID? = null
+
+ /**
+ * Interactors that require information about changes between [KeyguardState]s will call this to
+ * register themselves for flowable [TransitionStep]s when that transition occurs.
+ */
+ fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
+ return transitions.filter { step -> step.from == from && step.to == to }
+ }
+
+ /**
+ * Begin a transition from one state to another. The [info.from] must match
+ * [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid
+ * unplanned transitions.
+ */
+ fun startTransition(info: TransitionInfo): UUID? {
+ if (currentTransitionInfo != null) {
+ // Open questions:
+ // * Queue of transitions? buffer of 1?
+ // * Are transitions cancellable if a new one is triggered?
+ // * What validation does this need to do?
+ Log.wtf(TAG, "Transition still active: $currentTransitionInfo")
+ return null
+ }
+ currentTransitionInfo?.animator?.cancel()
+
+ currentTransitionInfo = info
+ info.animator?.let { animator ->
+ // An animator was provided, so use it to run the transition
+ animator.setFloatValues(0f, 1f)
+ val updateListener =
+ object : AnimatorUpdateListener {
+ override fun onAnimationUpdate(animation: ValueAnimator) {
+ emitTransition(
+ info,
+ (animation.getAnimatedValue() as Float),
+ TransitionState.RUNNING
+ )
+ }
+ }
+ val adapter =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ Log.i(TAG, "Starting transition: $info")
+ emitTransition(info, 0f, TransitionState.STARTED)
+ }
+ override fun onAnimationCancel(animation: Animator) {
+ Log.i(TAG, "Cancelling transition: $info")
+ }
+ override fun onAnimationEnd(animation: Animator) {
+ Log.i(TAG, "Ending transition: $info")
+ emitTransition(info, 1f, TransitionState.FINISHED)
+ animator.removeListener(this)
+ animator.removeUpdateListener(updateListener)
+ }
+ }
+ animator.addListener(adapter)
+ animator.addUpdateListener(updateListener)
+ animator.start()
+ return@startTransition null
+ }
+ ?: run {
+ Log.i(TAG, "Starting transition (manual): $info")
+ emitTransition(info, 0f, TransitionState.STARTED)
+
+ // No animator, so it's manual. Provide a mechanism to callback
+ updateTransitionId = UUID.randomUUID()
+ return@startTransition updateTransitionId
+ }
+ }
+
+ /**
+ * Allows manual control of a transition. When calling [startTransition], the consumer must pass
+ * in a null animator. In return, it will get a unique [UUID] that will be validated to allow
+ * further updates.
+ *
+ * When the transition is over, TransitionState.FINISHED must be passed into the [state]
+ * parameter.
+ */
+ fun updateTransition(
+ transitionId: UUID,
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ state: TransitionState
+ ) {
+ if (updateTransitionId != transitionId) {
+ Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
+ return
+ }
+
+ if (currentTransitionInfo == null) {
+ Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'")
+ return
+ }
+
+ currentTransitionInfo?.let { info ->
+ if (state == TransitionState.FINISHED) {
+ updateTransitionId = null
+ Log.i(TAG, "Ending transition: $info")
+ }
+
+ emitTransition(info, value, state)
+ }
+ }
+
+ private fun emitTransition(
+ info: TransitionInfo,
+ value: Float,
+ transitionState: TransitionState
+ ) {
+ if (transitionState == TransitionState.FINISHED) {
+ currentTransitionInfo = null
+ }
+ _transitions.value = TransitionStep(info.from, info.to, value, transitionState)
+ }
+
+ companion object {
+ private const val TAG = "KeyguardTransitionRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
new file mode 100644
index 0000000..4003766
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodLockscreenTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor("AOD<->LOCKSCREEN") {
+
+ override fun start() {
+ scope.launch {
+ keyguardRepository.isDozing.collect { isDozing ->
+ if (isDozing) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD,
+ getAnimator(),
+ )
+ )
+ } else {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 192919e..fc2269c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -38,7 +38,7 @@
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
- /** Whether the keyguard is showing ot not. */
+ /** Whether the keyguard is showing to not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
fun isKeyguardShowing(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 01cd3e2..f663b0d 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 @@
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 @@
*
* @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 @@
KeyguardQuickAffordanceModel.Visible(
configKey = configs[index]::class,
icon = visibleState.icon,
+ toggle = visibleState.toggle,
)
} else {
KeyguardQuickAffordanceModel.Hidden
@@ -114,7 +115,7 @@
private fun launchQuickAffordance(
intent: Intent,
canShowWhileLocked: Boolean,
- animationController: ActivityLaunchAnimator.Controller?,
+ expandable: Expandable?,
) {
@LockPatternUtils.StrongAuthTracker.StrongAuthFlags
val strongAuthFlags =
@@ -130,13 +131,13 @@
activityStarter.postStartActivityDismissingKeyguard(
intent,
0 /* delay */,
- animationController
+ expandable?.activityLaunchController(),
)
} else {
activityStarter.startActivity(
intent,
true /* dismissShade */,
- animationController,
+ expandable?.activityLaunchController(),
true /* showOverLockscreenWhenLocked */,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
new file mode 100644
index 0000000..b166681
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.domain.interactor
+
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import java.util.Set
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardTransitionCoreStartable
+@Inject
+constructor(
+ private val interactors: Set<TransitionInteractor>,
+) : CoreStartable {
+
+ override fun start() {
+ // By listing the interactors in a when, the compiler will help enforce all classes
+ // extending the sealed class [TransitionInteractor] will be initialized.
+ interactors.forEach {
+ // `when` needs to be an expression in order for the compiler to enforce it being
+ // exhaustive
+ val ret =
+ when (it) {
+ is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
+ is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+ }
+ it.start()
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardTransitionCoreStartable"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
new file mode 100644
index 0000000..59bb22786
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business-logic related to the keyguard transitions. */
+@SysUISingleton
+class KeyguardTransitionInteractor
+@Inject
+constructor(
+ repository: KeyguardTransitionRepository,
+) {
+ /** AOD->LOCKSCREEN transition information. */
+ val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
new file mode 100644
index 0000000..3c2a12e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class LockscreenBouncerTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
+ private val shadeRepository: ShadeRepository,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
+
+ private var transitionId: UUID? = null
+
+ override fun start() {
+ scope.launch {
+ shadeRepository.shadeModel.sample(
+ combine(
+ keyguardTransitionRepository.transitions,
+ keyguardRepository.statusBarState,
+ ) { transitions, statusBarState ->
+ Pair(transitions, statusBarState)
+ }
+ ) { shadeModel, pair ->
+ val (transitions, statusBarState) = pair
+
+ val id = transitionId
+ if (id != null) {
+ // An existing `id` means a transition is started, and calls to
+ // `updateTransition` will control it until FINISHED
+ keyguardTransitionRepository.updateTransition(
+ id,
+ shadeModel.expansionAmount,
+ if (shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f) {
+ transitionId = null
+ TransitionState.FINISHED
+ } else {
+ TransitionState.RUNNING
+ }
+ )
+ } else {
+ // TODO (b/251849525): Remove statusbarstate check when that state is integrated
+ // into KeyguardTransitionRepository
+ val isOnLockscreen =
+ transitions.transitionState == TransitionState.FINISHED &&
+ transitions.to == KeyguardState.LOCKSCREEN
+ if (
+ isOnLockscreen &&
+ shadeModel.isUserDragging &&
+ statusBarState != SHADE_LOCKED
+ ) {
+ transitionId =
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.BOUNCER,
+ animator = null,
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
new file mode 100644
index 0000000..74c542c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+abstract class StartKeyguardTransitionModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardTransitionCoreStartable::class)
+ abstract fun bind(impl: KeyguardTransitionCoreStartable): CoreStartable
+
+ @Binds
+ @IntoSet
+ abstract fun lockscreenBouncer(
+ impl: LockscreenBouncerTransitionInteractor
+ ): TransitionInteractor
+
+ @Binds
+ @IntoSet
+ abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
new file mode 100644
index 0000000..a2a46d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+/**
+ * Each TransitionInteractor is responsible for determining under which conditions to notify
+ * [KeyguardTransitionRepository] to signal a transition. When (and if) the transition occurs is
+ * determined by [KeyguardTransitionRepository].
+ *
+ * [name] field should be a unique identifiable string representing this state, used primarily for
+ * logging
+ *
+ * MUST list implementing classes in dagger module [StartKeyguardTransitionModule] and also in the
+ * 'when' clause of [KeyguardTransitionCoreStartable]
+ */
+sealed class TransitionInteractor(val name: String) {
+
+ abstract fun start()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index eb6bb92..e56b259 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 @@
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 @@
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 89604f0..8384260 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 @@
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 @@
}
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 8e1c6b7..95027d0 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 @@
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 @@
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 d97deaf..502a607 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 @@
}
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 9196b09..a24a0d6 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.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 @@
}
override fun onQuickAffordanceClicked(
- animationController: ActivityLaunchAnimator.Controller?,
+ expandable: Expandable?,
): KeyguardQuickAffordanceConfig.OnClickedResult {
walletController.startQuickAccessUiIntent(
activityStarter,
- animationController,
+ expandable?.activityLaunchController(),
/* hasCard= */ true,
)
return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
new file mode 100644
index 0000000..f66d5d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** List of all possible states to transition to/from */
+enum class KeyguardState {
+ /** For initialization only */
+ NONE,
+ /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
+ AOD,
+ /*
+ * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
+ * (Fingerprint Sensor) variations, for the user to verify their credentials
+ */
+ BOUNCER,
+ /*
+ * Device is actively displaying keyguard UI and is not in low-power mode. Device may be
+ * unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
+ */
+ LOCKSCREEN,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt
new file mode 100644
index 0000000..bb95347
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** See [com.android.systemui.statusbar.StatusBarState] for definitions */
+enum class StatusBarState {
+ SHADE,
+ KEYGUARD,
+ SHADE_LOCKED,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
new file mode 100644
index 0000000..bfccf3fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+import android.animation.ValueAnimator
+
+/** Tracks who is controlling the current transition, and how to run it. */
+data class TransitionInfo(
+ val ownerName: String,
+ val from: KeyguardState,
+ val to: KeyguardState,
+ val animator: ValueAnimator?, // 'null' animator signal manual control
+) {
+ override fun toString(): String =
+ "TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
+ (if (animator != null) {
+ "animated"
+ } else {
+ "manual"
+ }) +
+ ")"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
new file mode 100644
index 0000000..d8691c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Possible states for a running transition between [State] */
+enum class TransitionState {
+ NONE,
+ STARTED,
+ RUNNING,
+ FINISHED
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
new file mode 100644
index 0000000..688ec91
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
+data class TransitionStep(
+ val from: KeyguardState = KeyguardState.NONE,
+ val to: KeyguardState = KeyguardState.NONE,
+ val value: Float = 0f, // constrained [0.0, 1.0]
+ val transitionState: TransitionState = TransitionState.NONE,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
new file mode 100644
index 0000000..55d38a4
--- /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 65b85c0..2c99ca5 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.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 @@
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 @@
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 970ee4c..535ca72 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.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 @@
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 0971f13..bf598ba 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 @@
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 8f9357a..c7e4c5e 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_KEYGUARD;
import android.annotation.Nullable;
-import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
@@ -48,7 +47,7 @@
* 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 @@
@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 d298ff9..1b81e88 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -322,7 +322,7 @@
@SysUISingleton
@KeyguardUpdateMonitorLog
public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
- return factory.create("KeyguardUpdateMonitorLog", 200);
+ return factory.create("KeyguardUpdateMonitorLog", 400);
}
/**
@@ -344,4 +344,14 @@
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/statusbar/phone/panelstate/PanelStateListener.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java
index ca667dd..14000e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.log.dagger;
-/** A listener interface to be notified of state change events for the notification panel. */
-fun interface PanelStateListener {
- /** Called when the panel's expansion state has changed. */
- fun onPanelStateChanged(@PanelState state: Int)
+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 2cd564f..5977ed0 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.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 @@
* finished
*/
@MediaLocation
- private var currentEndLocation: Int = -1
+ @VisibleForTesting
+ var currentEndLocation: Int = -1
/**
* The ending location of the view where it ends when all animations and transitions have
@@ -126,11 +128,12 @@
lateinit var settingsButton: View
private set
private val mediaContent: ViewGroup
- private val pageIndicator: PageIndicator
+ @VisibleForTesting
+ val pageIndicator: PageIndicator
private val visualStabilityCallback: OnReorderingAllowedListener
private var needsReordering: Boolean = false
private var keysNeedRemoval = mutableSetOf<String>()
- var shouldScrollToActivePlayer: Boolean = false
+ var shouldScrollToKey: Boolean = false
private var isRtl: Boolean = false
set(value) {
if (value != field) {
@@ -149,6 +152,27 @@
}
}
}
+
+ companion object {
+ const val ANIMATION_BASE_DURATION = 2200f
+ const val DURATION = 167f
+ const val DETAILS_DELAY = 1067f
+ const val CONTROLS_DELAY = 1400f
+ const val PAGINATION_DELAY = 1900f
+ const val MEDIATITLES_DELAY = 1000f
+ const val MEDIACONTAINERS_DELAY = 967f
+ val TRANSFORM_BEZIER = PathInterpolator (0.68F, 0F, 0F, 1F)
+ val REVERSE_BEZIER = PathInterpolator (0F, 0.68F, 1F, 0F)
+
+ fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float {
+ val transformStartFraction = delay / ANIMATION_BASE_DURATION
+ val transformDurationFraction = duration / ANIMATION_BASE_DURATION
+ val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction)
+ return MathUtils.constrain((squishinessToTime - transformStartFraction) /
+ transformDurationFraction, 0F, 1F)
+ }
+ }
+
private val configListener = object : ConfigurationController.ConfigurationListener {
override fun onDensityOrFontScaleChanged() {
// System font changes should only happen when UMO is offscreen or a flicker may occur
@@ -412,7 +436,10 @@
return mediaCarousel
}
- private fun reorderAllPlayers(previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?) {
+ private fun reorderAllPlayers(
+ previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?,
+ key: String? = null
+ ) {
mediaContent.removeAllViews()
for (mediaPlayer in MediaPlayerData.players()) {
mediaPlayer.mediaViewHolder?.let {
@@ -422,18 +449,18 @@
}
}
mediaCarouselScrollHandler.onPlayersChanged()
-
+ MediaPlayerData.updateVisibleMediaPlayers()
// Automatically scroll to the active player if needed
- if (shouldScrollToActivePlayer) {
- shouldScrollToActivePlayer = false
- val activeMediaIndex = MediaPlayerData.firstActiveMediaIndex()
- if (activeMediaIndex != -1) {
+ if (shouldScrollToKey) {
+ shouldScrollToKey = false
+ val mediaIndex = key?.let { MediaPlayerData.getMediaPlayerIndex(it) } ?: -1
+ if (mediaIndex != -1) {
previousVisiblePlayerKey?.let {
val previousVisibleIndex = MediaPlayerData.playerKeys()
.indexOfFirst { key -> it == key }
mediaCarouselScrollHandler
- .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
- } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex)
+ .scrollToPlayer(previousVisibleIndex, mediaIndex)
+ } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
}
}
}
@@ -447,9 +474,8 @@
): Boolean = traceSection("MediaCarouselController#addOrUpdatePlayer") {
MediaPlayerData.moveIfExists(oldKey, key)
val existingPlayer = MediaPlayerData.getMediaPlayer(key)
- val curVisibleMediaKey = MediaPlayerData.playerKeys()
+ val curVisibleMediaKey = MediaPlayerData.visiblePlayerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
- val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying
if (existingPlayer == null) {
val newPlayer = mediaControlPanelFactory.get()
newPlayer.attachPlayer(MediaViewHolder.create(
@@ -464,8 +490,10 @@
key, data, newPlayer, systemClock, isSsReactivated, debugLogger
)
updatePlayerToState(newPlayer, noAnimation = true)
- if (data.active) {
- reorderAllPlayers(curVisibleMediaKey)
+ // Media data added from a recommendation card should starts playing.
+ if ((shouldScrollToKey && data.isPlaying == true) ||
+ (!shouldScrollToKey && data.active)) {
+ reorderAllPlayers(curVisibleMediaKey, key)
} else {
needsReordering = true
}
@@ -474,14 +502,16 @@
MediaPlayerData.addMediaPlayer(
key, data, existingPlayer, systemClock, isSsReactivated, debugLogger
)
- // Check the playing status of both current visible and new media players
- // To make sure we scroll to the active playing media card.
+ val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
+ // In case of recommendations hits.
+ // Check the playing status of media player and the package name.
+ // To make sure we scroll to the right app's media player.
if (isReorderingAllowed ||
- shouldScrollToActivePlayer &&
+ shouldScrollToKey &&
data.isPlaying == true &&
- isCurVisibleMediaPlaying == false
+ packageName == data.packageName
) {
- reorderAllPlayers(curVisibleMediaKey)
+ reorderAllPlayers(curVisibleMediaKey, key)
} else {
needsReordering = true
}
@@ -510,7 +540,7 @@
val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
existingSmartspaceMediaKey?.let {
- val removedPlayer = MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey)
+ val removedPlayer = MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey, true)
removedPlayer?.run { debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey) }
}
@@ -522,7 +552,7 @@
ViewGroup.LayoutParams.WRAP_CONTENT)
newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
newRecs.bindRecommendation(data)
- val curVisibleMediaKey = MediaPlayerData.playerKeys()
+ val curVisibleMediaKey = MediaPlayerData.visiblePlayerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
MediaPlayerData.addMediaRecommendation(
key, data, newRecs, shouldPrioritize, systemClock, debugLogger
@@ -548,7 +578,10 @@
logger.logRecommendationRemoved(it.packageName, it.instanceId)
}
}
- val removed = MediaPlayerData.removeMediaPlayer(key)
+ val removed = MediaPlayerData.removeMediaPlayer(
+ key,
+ dismissMediaData || dismissRecommendation
+ )
removed?.apply {
mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
mediaContent.removeView(removed.mediaViewHolder?.player)
@@ -633,12 +666,17 @@
}
}
- private fun updatePageIndicatorAlpha() {
+ @VisibleForTesting
+ fun updatePageIndicatorAlpha() {
val hostStates = mediaHostStatesManager.mediaHostStates
val endIsVisible = hostStates[currentEndLocation]?.visible ?: false
val startIsVisible = hostStates[currentStartLocation]?.visible ?: false
val startAlpha = if (startIsVisible) 1.0f else 0.0f
- val endAlpha = if (endIsVisible) 1.0f else 0.0f
+ // when squishing in split shade, only use endState, which keeps changing
+ // to provide squishFraction
+ val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F
+ val endAlpha = (if (endIsVisible) 1.0f else 0.0f) *
+ calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION)
var alpha = 1.0f
if (!endIsVisible || !startIsVisible) {
var progress = currentTransitionProgress
@@ -687,6 +725,7 @@
mediaCarouselScrollHandler.setCarouselBounds(
currentCarouselWidth, currentCarouselHeight)
updatePageIndicatorLocation()
+ updatePageIndicatorAlpha()
}
}
@@ -805,18 +844,20 @@
fun logSmartspaceImpression(qsExpanded: Boolean) {
val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
if (MediaPlayerData.players().size > visibleMediaIndex) {
- val mediaControlPanel = MediaPlayerData.players().elementAt(visibleMediaIndex)
+ val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex)
val hasActiveMediaOrRecommendationCard =
MediaPlayerData.hasActiveMediaOrRecommendationCard()
if (!hasActiveMediaOrRecommendationCard && !qsExpanded) {
// Skip logging if on LS or QQS, and there is no active media card
return
}
- logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
- mediaControlPanel.mSmartspaceId,
- mediaControlPanel.mUid,
- intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging))
- mediaControlPanel.mIsImpressed = true
+ mediaControlPanel?.let {
+ logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
+ it.mSmartspaceId,
+ it.mUid,
+ intArrayOf(it.surfaceForSmartspaceLogging))
+ it.mIsImpressed = true
+ }
}
}
@@ -855,7 +896,7 @@
return
}
- val mediaControlKey = MediaPlayerData.playerKeys().elementAt(rank)
+ val mediaControlKey = MediaPlayerData.visiblePlayerKeys().elementAt(rank)
// Only log media resume card when Smartspace data is available
if (!mediaControlKey.isSsMediaRec &&
!mediaManager.smartspaceMediaData.isActive &&
@@ -886,7 +927,8 @@
interactedSubcardRank,
interactedSubcardCardinality,
receivedLatencyMillis,
- null // Media cards cannot have subcards.
+ null, // Media cards cannot have subcards.
+ null // Media cards don't have dimensions today.
)
/* ktlint-disable max-line-length */
if (DEBUG) {
@@ -929,7 +971,8 @@
pw.apply {
println("keysNeedRemoval: $keysNeedRemoval")
println("dataKeys: ${MediaPlayerData.dataKeys()}")
- println("playerSortKeys: ${MediaPlayerData.playerKeys()}")
+ println("orderedPlayerSortKeys: ${MediaPlayerData.playerKeys()}")
+ println("visiblePlayerSortKeys: ${MediaPlayerData.visiblePlayerKeys()}")
println("smartspaceMediaData: ${MediaPlayerData.smartspaceMediaData}")
println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}")
println("current size: $currentCarouselWidth x $currentCarouselHeight")
@@ -969,6 +1012,7 @@
data class MediaSortKey(
val isSsMediaRec: Boolean, // Whether the item represents a Smartspace media recommendation.
val data: MediaData,
+ val key: String,
val updateTime: Long = 0,
val isSsReactivated: Boolean = false
)
@@ -987,6 +1031,8 @@
private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
+ // A map that tracks order of visible media players before they get reordered.
+ private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>()
fun addMediaPlayer(
key: String,
@@ -1001,9 +1047,10 @@
debugLogger?.logPotentialMemoryLeak(key)
}
val sortKey = MediaSortKey(isSsMediaRec = false,
- data, clock.currentTimeMillis(), isSsReactivated = isSsReactivated)
+ data, key, clock.currentTimeMillis(), isSsReactivated = isSsReactivated)
mediaData.put(key, sortKey)
mediaPlayers.put(sortKey, player)
+ visibleMediaPlayers.put(key, sortKey)
}
fun addMediaRecommendation(
@@ -1019,10 +1066,16 @@
if (removedPlayer != null && removedPlayer != player) {
debugLogger?.logPotentialMemoryLeak(key)
}
- val sortKey = MediaSortKey(isSsMediaRec = true,
- EMPTY.copy(isPlaying = false), clock.currentTimeMillis(), isSsReactivated = true)
+ val sortKey = MediaSortKey(
+ isSsMediaRec = true,
+ EMPTY.copy(isPlaying = false),
+ key,
+ clock.currentTimeMillis(),
+ isSsReactivated = true
+ )
mediaData.put(key, sortKey)
mediaPlayers.put(sortKey, player)
+ visibleMediaPlayers.put(key, sortKey)
smartspaceMediaData = data
}
@@ -1036,12 +1089,18 @@
}
mediaData.remove(oldKey)?.let {
+ // MediaPlayer should not be visible
+ // no need to set isDismissed flag.
val removedPlayer = removeMediaPlayer(newKey)
removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
mediaData.put(newKey, it)
}
}
+ fun getMediaControlPanel(visibleIndex: Int): MediaControlPanel? {
+ return mediaPlayers.get(visiblePlayerKeys().elementAt(visibleIndex))
+ }
+
fun getMediaPlayer(key: String): MediaControlPanel? {
return mediaData.get(key)?.let { mediaPlayers.get(it) }
}
@@ -1056,10 +1115,17 @@
return -1
}
- fun removeMediaPlayer(key: String) = mediaData.remove(key)?.let {
+ /**
+ * Removes media player given the key.
+ * @param isDismissed determines whether the media player is removed from the carousel.
+ */
+ fun removeMediaPlayer(key: String, isDismissed: Boolean = false) = mediaData.remove(key)?.let {
if (it.isSsMediaRec) {
smartspaceMediaData = null
}
+ if (isDismissed) {
+ visibleMediaPlayers.remove(key)
+ }
mediaPlayers.remove(it)
}
@@ -1071,6 +1137,8 @@
fun playerKeys() = mediaPlayers.keys
+ fun visiblePlayerKeys() = visibleMediaPlayers.values
+
/** Returns the index of the first non-timeout media. */
fun firstActiveMediaIndex(): Int {
mediaPlayers.entries.forEachIndexed { index, e ->
@@ -1095,6 +1163,7 @@
fun clear() {
mediaData.clear()
mediaPlayers.clear()
+ visibleMediaPlayers.clear()
}
/* Returns true if there is active media player card or recommendation card */
@@ -1109,4 +1178,16 @@
}
fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.isSsReactivated ?: false
+
+ /**
+ * This method is called when media players are reordered.
+ * To make sure we have the new version of the order of
+ * media players visible to user.
+ */
+ fun updateVisibleMediaPlayers() {
+ visibleMediaPlayers.clear()
+ playerKeys().forEach {
+ visibleMediaPlayers.put(it.key, it)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index ef49fd3..a776897 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 @@
* 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 @@
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/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 759795f..fba51dd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -1441,7 +1441,7 @@
}
// Automatically scroll to the active player once the media is loaded.
- mMediaCarouselController.setShouldScrollToActivePlayer(true);
+ mMediaCarouselController.setShouldScrollToKey(true);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 5096a797..896fb47 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -1130,7 +1130,7 @@
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaEntries.remove(key)
- if (useMediaResumption && removed?.resumeAction != null && removed?.isLocalSession()) {
+ if (useMediaResumption && removed?.resumeAction != null && removed.isLocalSession()) {
Log.d(TAG, "Not removing $key because resumable")
// Move to resume key (aka package name) if that key doesn't already exist.
val resumeAction = getResumeMediaAction(removed.resumeAction!!)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index bffb0fd..8645922 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 @@
}
}
+ 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 @@
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 @@
if (expansion != other.expansion) {
return false
}
+ if (squishFraction != other.squishFraction) {
+ return false
+ }
if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
return false
}
@@ -289,6 +301,7 @@
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 @@
var expansion: Float
/**
+ * Fraction of the height animation.
+ */
+ var squishFraction: Float
+
+ /**
* Is this host only showing active media or is it showing all of them including resumption?
*/
var showsOnlyActiveMedia: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index c6bd777..1ac2a07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -17,6 +17,7 @@
import android.app.ActivityOptions
import android.content.Intent
+import android.content.res.Configuration
import android.media.projection.IMediaProjection
import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
import android.os.Binder
@@ -24,85 +25,73 @@
import android.os.IBinder
import android.os.ResultReceiver
import android.os.UserHandle
-import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.app.ChooserActivity
import com.android.internal.app.ResolverListController
import com.android.internal.app.chooser.NotSelectableTargetInfo
import com.android.internal.app.chooser.TargetInfo
import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorView
import com.android.systemui.mediaprojection.appselector.data.RecentTask
-import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter
-import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
+import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.AsyncActivityLauncher
-import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
import javax.inject.Inject
class MediaProjectionAppSelectorActivity(
+ private val componentFactory: MediaProjectionAppSelectorComponent.Factory,
private val activityLauncher: AsyncActivityLauncher,
- private val controller: MediaProjectionAppSelectorController,
- private val recentTasksAdapterFactory: RecentTasksAdapter.Factory,
/** This is used to override the dependency in a screenshot test */
@VisibleForTesting
private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
-) : ChooserActivity(), MediaProjectionAppSelectorView, RecentTaskClickListener {
+) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler {
@Inject
constructor(
+ componentFactory: MediaProjectionAppSelectorComponent.Factory,
activityLauncher: AsyncActivityLauncher,
- controller: MediaProjectionAppSelectorController,
- recentTasksAdapterFactory: RecentTasksAdapter.Factory,
- ) : this(activityLauncher, controller, recentTasksAdapterFactory, null)
+ ) : this(componentFactory, activityLauncher, null)
- private var recentsRoot: ViewGroup? = null
- private var recentsProgress: View? = null
- private var recentsRecycler: RecyclerView? = null
+ private lateinit var configurationController: ConfigurationController
+ private lateinit var controller: MediaProjectionAppSelectorController
+ private lateinit var recentsViewController: MediaProjectionRecentsViewController
- override fun getLayoutResource() =
- R.layout.media_projection_app_selector
+ override fun getLayoutResource() = R.layout.media_projection_app_selector
public override fun onCreate(bundle: Bundle?) {
- val queryIntent = Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_LAUNCHER)
+ val component =
+ componentFactory.create(
+ activity = this,
+ view = this,
+ resultHandler = this
+ )
+
+ // Create a separate configuration controller for this activity as the configuration
+ // might be different from the global one
+ configurationController = component.configurationController
+ controller = component.controller
+ recentsViewController = component.recentsViewController
+
+ val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
// TODO(b/240939253): update copies
val title = getString(R.string.media_projection_dialog_service_title)
intent.putExtra(Intent.EXTRA_TITLE, title)
super.onCreate(bundle)
- controller.init(this)
+ controller.init()
}
- private fun createRecentsView(parent: ViewGroup): ViewGroup {
- val recentsRoot = LayoutInflater.from(this)
- .inflate(R.layout.media_projection_recent_tasks, parent,
- /* attachToRoot= */ false) as ViewGroup
-
- recentsProgress = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_loader)
- recentsRecycler = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_recycler)
- recentsRecycler?.layoutManager = LinearLayoutManager(
- this, LinearLayoutManager.HORIZONTAL,
- /* reverseLayout= */false
- )
-
- val itemDecoration = HorizontalSpacerItemDecoration(
- resources.getDimensionPixelOffset(
- R.dimen.media_projection_app_selector_recents_padding
- )
- )
- recentsRecycler?.addItemDecoration(itemDecoration)
-
- return recentsRoot
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ configurationController.onConfigurationChanged(newConfig)
}
- override fun appliedThemeResId(): Int =
- R.style.Theme_SystemUI_MediaProjectionAppSelector
+ override fun appliedThemeResId(): Int = R.style.Theme_SystemUI_MediaProjectionAppSelector
override fun createListController(userHandle: UserHandle): ResolverListController =
listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
@@ -124,9 +113,9 @@
// is typically very fast, so we don't show any loaders.
// We wait for the activity to be launched to make sure that the window of the activity
// is created and ready to be captured.
- val activityStarted = activityLauncher
- .startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
- onTargetActivityLaunched(launchToken)
+ val activityStarted =
+ activityLauncher.startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
+ returnSelectedApp(launchToken)
}
// Rely on the ActivityManager to pop up a dialog regarding app suspension
@@ -160,44 +149,27 @@
}
override fun bind(recentTasks: List<RecentTask>) {
- val recents = recentsRoot ?: return
- val progress = recentsProgress ?: return
- val recycler = recentsRecycler ?: return
-
- if (recentTasks.isEmpty()) {
- recents.visibility = View.GONE
- return
- }
-
- progress.visibility = View.GONE
- recycler.visibility = View.VISIBLE
- recents.visibility = View.VISIBLE
-
- recycler.adapter = recentTasksAdapterFactory.create(recentTasks, this)
+ recentsViewController.bind(recentTasks)
}
- override fun onRecentClicked(task: RecentTask, view: View) {
- // TODO(b/240924732) Handle clicking on a recent task
- }
-
- private fun onTargetActivityLaunched(launchToken: IBinder) {
+ override fun returnSelectedApp(launchCookie: IBinder) {
if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
// The client requested to return the result in the result receiver instead of
// activity result, let's send the media projection to the result receiver
- val resultReceiver = intent
- .getParcelableExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
- ResultReceiver::class.java) as ResultReceiver
- val captureRegion = MediaProjectionCaptureTarget(launchToken)
- val data = Bundle().apply {
- putParcelable(KEY_CAPTURE_TARGET, captureRegion)
- }
+ val resultReceiver =
+ intent.getParcelableExtra(
+ EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
+ ResultReceiver::class.java
+ ) as ResultReceiver
+ val captureRegion = MediaProjectionCaptureTarget(launchCookie)
+ val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) }
resultReceiver.send(RESULT_OK, data)
} else {
// Return the media projection instance as activity result
val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
- projection.launchCookie = launchToken
+ projection.launchCookie = launchCookie
val intent = Intent()
intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
@@ -214,15 +186,13 @@
override fun shouldShowContentPreview() = false
override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
- recentsRoot ?: createRecentsView(parent).also {
- recentsRoot = it
- }
+ recentsViewController.createView(parent)
companion object {
/**
- * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra
- * the activity will send the [CaptureRegion] to the result receiver
- * instead of returning media projection instance through activity result.
+ * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra the activity will
+ * send the [CaptureRegion] to the result receiver instead of returning media projection
+ * instance through activity result.
*/
const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
const val KEY_CAPTURE_TARGET = "capture_region"
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index ac59175..faa7aae 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 @@
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 @@
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 @@
*/
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 @@
}
/**
+ * 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 @@
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())
+ 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 {
- // 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)
- }
+ 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 52ac4e0..8ae75fc 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 @@
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 0b9b32b..2a8168b 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 @@
* {@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 @@
@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 17ebfec..0f78a1e 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 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 @@
}
override fun onStopTrackingTouch(bar: SeekBar) {
- // in addition to the normal functionality of both functions.
- // isFalseTouch returns true if there is a real/false tap since it is not a move.
- // isFalseTap returns true if there is a real/false move since it is not a tap.
- if (falsingManager.isFalseTouch(MEDIA_SEEKBAR) &&
- falsingManager.isFalseTap(LOW_PENALTY)) {
+ if (falsingManager.isFalseTouch(MEDIA_SEEKBAR)) {
viewModel.onSeekFalse()
}
viewModel.onSeek(bar.progress.toLong())
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 66c036c..a8a8433 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -31,9 +31,7 @@
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
-import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
import java.util.Optional;
@@ -94,30 +92,6 @@
return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
}
- /** */
- @Provides
- @SysUISingleton
- static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
- MediaTttFlags mediaTttFlags,
- Lazy<MediaTttChipControllerSender> controllerSenderLazy) {
- if (!mediaTttFlags.isMediaTttEnabled()) {
- return Optional.empty();
- }
- return Optional.of(controllerSenderLazy.get());
- }
-
- /** */
- @Provides
- @SysUISingleton
- static Optional<MediaTttChipControllerReceiver> providesMediaTttChipControllerReceiver(
- MediaTttFlags mediaTttFlags,
- Lazy<MediaTttChipControllerReceiver> controllerReceiverLazy) {
- if (!mediaTttFlags.isMediaTttEnabled()) {
- return Optional.empty();
- }
- return Optional.of(controllerReceiverLazy.get());
- }
-
@Provides
@SysUISingleton
@MediaTttSenderLogger
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
deleted file mode 100644
index 185b4fc..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.dagger
-
-import android.app.Activity
-import android.content.ComponentName
-import android.content.Context
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.MediaProjectionAppSelectorActivity
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
-import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
-import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
-import dagger.Binds
-import dagger.Module
-import dagger.Provides
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import javax.inject.Qualifier
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class MediaProjectionAppSelector
-
-@Module
-abstract class MediaProjectionModule {
-
- @Binds
- @IntoMap
- @ClassKey(MediaProjectionAppSelectorActivity::class)
- abstract fun provideMediaProjectionAppSelectorActivity(
- activity: MediaProjectionAppSelectorActivity
- ): Activity
-
- @Binds
- abstract fun bindRecentTaskThumbnailLoader(
- impl: ActivityTaskManagerThumbnailLoader
- ): RecentTaskThumbnailLoader
-
- @Binds
- abstract fun bindRecentTaskListProvider(
- impl: ShellRecentTaskListProvider
- ): RecentTaskListProvider
-
- @Binds
- abstract fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
-
- companion object {
- @Provides
- fun provideController(
- recentTaskListProvider: RecentTaskListProvider,
- context: Context,
- @MediaProjectionAppSelector scope: CoroutineScope
- ): MediaProjectionAppSelectorController {
- val appSelectorComponentName =
- ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
-
- return MediaProjectionAppSelectorController(
- recentTaskListProvider,
- scope,
- appSelectorComponentName
- )
- }
-
- @MediaProjectionAppSelector
- @Provides
- fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
- CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
- }
-}
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 53b4d43..91e7b49 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 @@
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 @@
* {@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 @@
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 d60172a..0ba5f28 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 @@
* 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 @@
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 00b0ff9..a4a96806 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -22,6 +22,7 @@
import android.media.MediaRoute2Info
import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
@@ -39,14 +40,10 @@
*/
@SysUISingleton
class MediaTttCommandLineHelper @Inject constructor(
- commandRegistry: CommandRegistry,
+ private val commandRegistry: CommandRegistry,
private val context: Context,
@Main private val mainExecutor: Executor
-) {
- init {
- commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
- commandRegistry.registerCommand(RECEIVER_COMMAND) { ReceiverCommand() }
- }
+) : CoreStartable {
/** All commands for the sender device. */
inner class SenderCommand : Command {
@@ -56,7 +53,7 @@
val displayState: Int?
try {
displayState = ChipStateSender.getSenderStateIdFromName(commandName)
- } catch (ex: IllegalArgumentException) {
+ } catch (ex: IllegalArgumentException) {
pw.println("Invalid command name $commandName")
return
}
@@ -150,6 +147,11 @@
"<chipState> useAppIcon=[true|false]")
}
}
+
+ override fun start() {
+ commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
+ commandRegistry.registerCommand(RECEIVER_COMMAND) { ReceiverCommand() }
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
index 6379960..b5a0483 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
@@ -41,3 +41,5 @@
## Testing
If you want to test out the tap-to-transfer chip without using the `@SystemApi`s, you can use adb
commands instead. Refer to `MediaTttCommandLineHelper` for information about adb commands.
+
+TODO(b/245610654): Update this page once the chipbar migration is complete.
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 792ae7c..c3de94f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
@@ -76,29 +75,6 @@
isAppIcon = false
)
}
-
- /**
- * Sets an icon to be displayed by the given view.
- *
- * @param iconSize the size in pixels that the icon should be. If null, the size of
- * [appIconView] will not be adjusted.
- */
- fun setIcon(
- appIconView: CachingIconView,
- icon: Drawable,
- iconContentDescription: CharSequence,
- iconSize: Int? = null,
- ) {
- iconSize?.let { size ->
- val lp = appIconView.layoutParams
- lp.width = size
- lp.height = size
- appIconView.layoutParams = lp
- }
-
- appIconView.contentDescription = iconContentDescription
- appIconView.setImageDrawable(icon)
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index dfd9e22..089625c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -19,6 +19,7 @@
import android.annotation.SuppressLint
import android.app.StatusBarManager
import android.content.Context
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
@@ -30,10 +31,12 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
@@ -43,16 +46,19 @@
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
/**
* A controller to display and hide the Media Tap-To-Transfer chip on the **receiving** device.
*
* This chip is shown when a user is transferring media to/from a sending device and this device.
+ *
+ * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator.
*/
@SysUISingleton
class MediaTttChipControllerReceiver @Inject constructor(
- commandQueue: CommandQueue,
+ private val commandQueue: CommandQueue,
context: Context,
@MediaTttReceiverLogger logger: MediaTttLogger,
windowManager: WindowManager,
@@ -61,7 +67,9 @@
configurationController: ConfigurationController,
powerManager: PowerManager,
@Main private val mainHandler: Handler,
+ private val mediaTttFlags: MediaTttFlags,
private val uiEventLogger: MediaTttReceiverUiEventLogger,
+ private val viewUtil: ViewUtil,
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
context,
logger,
@@ -82,7 +90,6 @@
height = WindowManager.LayoutParams.MATCH_PARENT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
fitInsetsTypes = 0 // Ignore insets from all system bars
- flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
}
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
@@ -98,10 +105,6 @@
}
}
- init {
- commandQueue.addCallback(commandQueueCallbacks)
- }
-
private fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
routeInfo: MediaRoute2Info,
@@ -119,7 +122,7 @@
uiEventLogger.logReceiverStateChange(chipState)
if (chipState == ChipStateReceiver.FAR_FROM_SENDER) {
- removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER::class.simpleName!!)
+ removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
return
}
if (appIcon == null) {
@@ -138,32 +141,33 @@
)
}
- override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
- super.updateView(newInfo, currentView)
+ override fun start() {
+ if (mediaTttFlags.isMediaTttEnabled()) {
+ commandQueue.addCallback(commandQueueCallbacks)
+ }
+ }
+ override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger
)
val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable
val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription
- val iconSize = context.resources.getDimensionPixelSize(
+ val iconPadding =
if (iconInfo.isAppIcon) {
- R.dimen.media_ttt_icon_size_receiver
+ 0
} else {
- R.dimen.media_ttt_generic_icon_size_receiver
+ context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding)
}
- )
- MediaTttUtils.setIcon(
- currentView.requireViewById(R.id.app_icon),
- iconDrawable,
- iconContentDescription,
- iconSize,
- )
+ val iconView = currentView.getAppIconView()
+ iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
+ iconView.setImageDrawable(iconDrawable)
+ iconView.contentDescription = iconContentDescription
}
override fun animateViewIn(view: ViewGroup) {
- val appIconView = view.requireViewById<View>(R.id.app_icon)
+ val appIconView = view.getAppIconView()
appIconView.animate()
.translationYBy(-1 * getTranslationAmount().toFloat())
.setDuration(30.frames)
@@ -177,6 +181,12 @@
startRipple(view.requireViewById(R.id.ripple))
}
+ override fun getTouchableRegion(view: View, outRect: Rect) {
+ // Even though the app icon view isn't touchable, users might think it is. So, use it as the
+ // touchable region to ensure that touches don't get passed to the window below.
+ viewUtil.setRectToViewWindowLocation(view.getAppIconView(), outRect)
+ }
+
/** Returns the amount that the chip will be translated by in its intro animation. */
private fun getTranslationAmount(): Int {
return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
@@ -204,16 +214,19 @@
private fun layoutRipple(rippleView: ReceiverChipRippleView) {
val windowBounds = windowManager.currentWindowMetrics.bounds
- val height = windowBounds.height()
- val width = windowBounds.width()
+ val height = windowBounds.height().toFloat()
+ val width = windowBounds.width().toFloat()
- val maxDiameter = height / 2.5f
- rippleView.setMaxSize(maxDiameter, maxDiameter)
+ rippleView.setMaxSize(width / 2f, height / 2f)
// Center the ripple on the bottom of the screen in the middle.
- rippleView.setCenter(width * 0.5f, height.toFloat())
+ rippleView.setCenter(width * 0.5f, height)
val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
rippleView.setColor(color, 70)
}
+
+ private fun View.getAppIconView(): CachingIconView {
+ return this.requireViewById(R.id.app_icon)
+ }
}
data class ChipReceiverInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 6a505f0..e354a03 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.util.AttributeSet
+import com.android.systemui.ripple.RippleShader
import com.android.systemui.ripple.RippleView
/**
@@ -25,9 +26,9 @@
*/
class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
init {
- // TODO: use RippleShape#ELLIPSE when calling setupShader.
- setupShader()
+ setupShader(RippleShader.RippleShape.ELLIPSE)
setRippleFill(true)
+ setSparkleStrength(0f)
duration = 3000L
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index aae973d..c24b030 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -27,6 +27,8 @@
import com.android.systemui.R
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
+import com.android.systemui.temporarydisplay.chipbar.ChipSenderInfo
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
/**
* A class enumerating all the possible states of the media tap-to-transfer chip on the sender
@@ -105,7 +107,7 @@
transferStatus = TransferStatus.SUCCEEDED,
) {
override fun undoClickListener(
- controllerSender: MediaTttChipControllerSender,
+ chipbarCoordinator: ChipbarCoordinator,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
uiEventLogger: MediaTttSenderUiEventLogger,
@@ -123,9 +125,9 @@
undoCallback.onUndoTriggered()
// The external service should eventually send us a TransferToThisDeviceTriggered
// state, but that may take too long to go through the binder and the user may be
- // confused ast o why the UI hasn't changed yet. So, we immediately change the UI
+ // confused as to why the UI hasn't changed yet. So, we immediately change the UI
// here.
- controllerSender.displayView(
+ chipbarCoordinator.displayView(
ChipSenderInfo(
TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, undoCallback
)
@@ -144,7 +146,7 @@
transferStatus = TransferStatus.SUCCEEDED,
) {
override fun undoClickListener(
- controllerSender: MediaTttChipControllerSender,
+ chipbarCoordinator: ChipbarCoordinator,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
uiEventLogger: MediaTttSenderUiEventLogger,
@@ -164,7 +166,7 @@
// state, but that may take too long to go through the binder and the user may be
// confused as to why the UI hasn't changed yet. So, we immediately change the UI
// here.
- controllerSender.displayView(
+ chipbarCoordinator.displayView(
ChipSenderInfo(
TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo, undoCallback
)
@@ -213,13 +215,13 @@
* Returns a click listener for the undo button on the chip. Returns null if this chip state
* doesn't have an undo button.
*
- * @param controllerSender passed as a parameter in case we want to display a new chip state
+ * @param chipbarCoordinator passed as a parameter in case we want to display a new chipbar
* when undo is clicked.
* @param undoCallback if present, the callback that should be called when the user clicks the
* undo button. The undo button will only be shown if this is non-null.
*/
open fun undoClickListener(
- controllerSender: MediaTttChipControllerSender,
+ chipbarCoordinator: ChipbarCoordinator,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
uiEventLogger: MediaTttSenderUiEventLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
new file mode 100644
index 0000000..224303a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.sender
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.media.MediaRoute2Info
+import android.util.Log
+import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.temporarydisplay.chipbar.ChipSenderInfo
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.SENDER_TAG
+import javax.inject.Inject
+
+/**
+ * A coordinator for showing/hiding the Media Tap-To-Transfer UI on the **sending** device. This UI
+ * is shown when a user is transferring media to/from this device and a receiver device.
+ */
+@SysUISingleton
+class MediaTttSenderCoordinator
+@Inject
+constructor(
+ private val chipbarCoordinator: ChipbarCoordinator,
+ private val commandQueue: CommandQueue,
+ private val context: Context,
+ @MediaTttSenderLogger private val logger: MediaTttLogger,
+ private val mediaTttFlags: MediaTttFlags,
+ private val uiEventLogger: MediaTttSenderUiEventLogger,
+) : CoreStartable {
+
+ private var displayedState: ChipStateSender? = null
+
+ private val commandQueueCallbacks =
+ object : CommandQueue.Callbacks {
+ override fun updateMediaTapToTransferSenderDisplay(
+ @StatusBarManager.MediaTransferSenderState displayState: Int,
+ routeInfo: MediaRoute2Info,
+ undoCallback: IUndoMediaTransferCallback?
+ ) {
+ this@MediaTttSenderCoordinator.updateMediaTapToTransferSenderDisplay(
+ displayState,
+ routeInfo,
+ undoCallback
+ )
+ }
+ }
+
+ override fun start() {
+ if (mediaTttFlags.isMediaTttEnabled()) {
+ commandQueue.addCallback(commandQueueCallbacks)
+ }
+ }
+
+ private fun updateMediaTapToTransferSenderDisplay(
+ @StatusBarManager.MediaTransferSenderState displayState: Int,
+ routeInfo: MediaRoute2Info,
+ undoCallback: IUndoMediaTransferCallback?
+ ) {
+ val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState)
+ val stateName = chipState?.name ?: "Invalid"
+ logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
+
+ if (chipState == null) {
+ Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
+ return
+ }
+ uiEventLogger.logSenderStateChange(chipState)
+
+ if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
+ // Return early if we're not displaying a chip anyway
+ val currentDisplayedState = displayedState ?: return
+
+ val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name
+ if (
+ currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS ||
+ currentDisplayedState.transferStatus == TransferStatus.SUCCEEDED
+ ) {
+ // Don't remove the chip if we're in progress or succeeded, since the user should
+ // still be able to see the status of the transfer.
+ logger.logRemovalBypass(
+ removalReason,
+ bypassReason = "transferStatus=${currentDisplayedState.transferStatus.name}"
+ )
+ return
+ }
+
+ displayedState = null
+ chipbarCoordinator.removeView(removalReason)
+ } else {
+ displayedState = chipState
+ chipbarCoordinator.displayView(ChipSenderInfo(chipState, routeInfo, undoCallback))
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
new file mode 100644
index 0000000..7fd100f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.mediaprojection.appselector
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
+import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
+import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.Binds
+import dagger.BindsInstance
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Qualifier
+import javax.inject.Scope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
+
+@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
+
+@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
+interface MediaProjectionModule {
+ @Binds
+ @IntoMap
+ @ClassKey(MediaProjectionAppSelectorActivity::class)
+ fun provideMediaProjectionAppSelectorActivity(
+ activity: MediaProjectionAppSelectorActivity
+ ): Activity
+}
+
+/** Scoped values for [MediaProjectionAppSelectorComponent].
+ * We create a scope for the activity so certain dependencies like [TaskPreviewSizeProvider]
+ * could be reused. */
+@Module
+interface MediaProjectionAppSelectorModule {
+
+ @Binds
+ @MediaProjectionAppSelectorScope
+ fun bindRecentTaskThumbnailLoader(
+ impl: ActivityTaskManagerThumbnailLoader
+ ): RecentTaskThumbnailLoader
+
+ @Binds
+ @MediaProjectionAppSelectorScope
+ fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider
+
+ @Binds
+ @MediaProjectionAppSelectorScope
+ fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
+
+ companion object {
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
+ fun provideAppSelectorComponentName(context: Context): ComponentName =
+ ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
+
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
+ fun bindConfigurationController(
+ activity: MediaProjectionAppSelectorActivity
+ ): ConfigurationController = ConfigurationControllerImpl(activity)
+
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
+ fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
+ CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+ }
+}
+
+@Subcomponent(modules = [MediaProjectionAppSelectorModule::class])
+@MediaProjectionAppSelectorScope
+interface MediaProjectionAppSelectorComponent {
+
+ /** Generates [MediaProjectionAppSelectorComponent]. */
+ @Subcomponent.Factory
+ interface Factory {
+ /**
+ * Create a factory to inject the activity into the graph
+ */
+ fun create(
+ @BindsInstance activity: MediaProjectionAppSelectorActivity,
+ @BindsInstance view: MediaProjectionAppSelectorView,
+ @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
+ ): MediaProjectionAppSelectorComponent
+ }
+
+ val controller: MediaProjectionAppSelectorController
+ val recentsViewController: MediaProjectionRecentsViewController
+
+ @MediaProjectionAppSelector val configurationController: ConfigurationController
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 2b381a9..d744a40b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -17,20 +17,22 @@
package com.android.systemui.mediaprojection.appselector
import android.content.ComponentName
-import com.android.systemui.media.dagger.MediaProjectionAppSelector
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
+import javax.inject.Inject
-class MediaProjectionAppSelectorController(
+@MediaProjectionAppSelectorScope
+class MediaProjectionAppSelectorController @Inject constructor(
private val recentTaskListProvider: RecentTaskListProvider,
+ private val view: MediaProjectionAppSelectorView,
@MediaProjectionAppSelector private val scope: CoroutineScope,
- private val appSelectorComponentName: ComponentName
+ @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
) {
- fun init(view: MediaProjectionAppSelectorView) {
+ fun init() {
scope.launch {
val tasks = recentTaskListProvider.loadRecentTasks().sortTasks()
view.bind(tasks)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
new file mode 100644
index 0000000..93c3bce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.mediaprojection.appselector
+
+import android.os.IBinder
+
+/**
+ * Interface that allows to continue the media projection flow and return the selected app
+ * result to the original caller.
+ */
+interface MediaProjectionAppSelectorResultHandler {
+ /**
+ * Return selected app to the original caller of the media projection app picker.
+ * @param launchCookie launch cookie of the launched activity of the target app
+ */
+ fun returnSelectedApp(launchCookie: IBinder)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
new file mode 100644
index 0000000..c816446
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.app.ActivityOptions
+import android.app.IActivityTaskManager
+import android.graphics.Rect
+import android.os.Binder
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import javax.inject.Inject
+
+/**
+ * Controller that handles view of the recent apps selector in the media projection activity.
+ * It is responsible for creating and updating recent apps view.
+ */
+@MediaProjectionAppSelectorScope
+class MediaProjectionRecentsViewController
+@Inject
+constructor(
+ private val recentTasksAdapterFactory: RecentTasksAdapter.Factory,
+ private val taskViewSizeProvider: TaskPreviewSizeProvider,
+ private val activityTaskManager: IActivityTaskManager,
+ private val resultHandler: MediaProjectionAppSelectorResultHandler,
+) : RecentTaskClickListener, TaskPreviewSizeListener {
+
+ private var views: Views? = null
+ private var lastBoundData: List<RecentTask>? = null
+
+ init {
+ taskViewSizeProvider.addCallback(this)
+ }
+
+ fun createView(parent: ViewGroup): ViewGroup =
+ views?.root ?: createRecentViews(parent).also {
+ views = it
+ lastBoundData?.let { recents -> bind(recents) }
+ }.root
+
+ fun bind(recentTasks: List<RecentTask>) {
+ views?.apply {
+ if (recentTasks.isEmpty()) {
+ root.visibility = View.GONE
+ return
+ }
+
+ progress.visibility = View.GONE
+ recycler.visibility = View.VISIBLE
+ root.visibility = View.VISIBLE
+
+ recycler.adapter =
+ recentTasksAdapterFactory.create(
+ recentTasks,
+ this@MediaProjectionRecentsViewController
+ )
+ }
+
+ lastBoundData = recentTasks
+ }
+
+ private fun createRecentViews(parent: ViewGroup): Views {
+ val recentsRoot =
+ LayoutInflater.from(parent.context)
+ .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
+ as ViewGroup
+
+ val container = recentsRoot.findViewById<View>(R.id.media_projection_recent_tasks_container)
+ container.setTaskHeightSize()
+
+ val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
+ val recycler =
+ recentsRoot.requireViewById<RecyclerView>(R.id.media_projection_recent_tasks_recycler)
+ recycler.layoutManager =
+ LinearLayoutManager(
+ parent.context,
+ LinearLayoutManager.HORIZONTAL,
+ /* reverseLayout= */ false
+ )
+
+ val itemDecoration =
+ HorizontalSpacerItemDecoration(
+ parent.resources.getDimensionPixelOffset(
+ R.dimen.media_projection_app_selector_recents_padding
+ )
+ )
+ recycler.addItemDecoration(itemDecoration)
+
+ return Views(recentsRoot, container, progress, recycler)
+ }
+
+ override fun onRecentAppClicked(task: RecentTask, view: View) {
+ val launchCookie = Binder()
+ val activityOptions =
+ ActivityOptions.makeScaleUpAnimation(
+ view,
+ /* startX= */ 0,
+ /* startY= */ 0,
+ view.width,
+ view.height
+ )
+ activityOptions.launchCookie = launchCookie
+
+ activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
+ resultHandler.returnSelectedApp(launchCookie)
+ }
+
+ override fun onTaskSizeChanged(size: Rect) {
+ views?.recentsContainer?.setTaskHeightSize()
+ }
+
+ private fun View.setTaskHeightSize() {
+ val thumbnailHeight = taskViewSizeProvider.size.height()
+ val itemHeight =
+ thumbnailHeight +
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_icon_size
+ ) +
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_icon_margin
+ ) * 2
+
+ layoutParams = layoutParams.apply { height = itemHeight }
+ }
+
+ private class Views(
+ val root: ViewGroup,
+ val recentsContainer: View,
+ val progress: View,
+ val recycler: RecyclerView
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
new file mode 100644
index 0000000..b682bd1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.graphics.BitmapShader
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.Shader
+import android.util.AttributeSet
+import android.view.View
+import android.view.WindowManager
+import androidx.core.content.getSystemService
+import androidx.core.content.res.use
+import com.android.internal.R as AndroidR
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+
+/**
+ * Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData].
+ * It handles proper cropping and positioning of the thumbnail using [PreviewPositionHelper].
+ */
+class MediaProjectionTaskView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ View(context, attrs, defStyleAttr) {
+
+ private val defaultBackgroundColor: Int
+
+ init {
+ val backgroundColorAttribute = intArrayOf(android.R.attr.colorBackgroundFloating)
+ defaultBackgroundColor =
+ context.obtainStyledAttributes(backgroundColorAttribute).use {
+ it.getColor(/* index= */ 0, /* defValue= */ Color.BLACK)
+ }
+ }
+
+ private val windowManager: WindowManager = context.getSystemService()!!
+ private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val backgroundPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply { color = defaultBackgroundColor }
+ private val cornerRadius =
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_rounded_corners
+ )
+ private val previewPositionHelper = PreviewPositionHelper()
+ private val previewRect = Rect()
+
+ private var task: RecentTask? = null
+ private var thumbnailData: ThumbnailData? = null
+
+ private var bitmapShader: BitmapShader? = null
+
+ fun bindTask(task: RecentTask?, thumbnailData: ThumbnailData?) {
+ this.task = task
+ this.thumbnailData = thumbnailData
+
+ // Strip alpha channel to make sure that the color is not semi-transparent
+ val color = (task?.colorBackground ?: Color.BLACK) or 0xFF000000.toInt()
+
+ paint.color = color
+ backgroundPaint.color = color
+
+ refresh()
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ updateThumbnailMatrix()
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ // Always draw the background since the snapshots might be translucent or partially empty
+ // (For example, tasks been reparented out of dismissing split root when drag-to-dismiss
+ // split screen).
+ canvas.drawRoundRect(
+ 0f,
+ 1f,
+ width.toFloat(),
+ (height - 1).toFloat(),
+ cornerRadius.toFloat(),
+ cornerRadius.toFloat(),
+ backgroundPaint
+ )
+
+ val drawBackgroundOnly = task == null || bitmapShader == null || thumbnailData == null
+ if (drawBackgroundOnly) {
+ return
+ }
+
+ // Draw the task thumbnail using bitmap shader in the paint
+ canvas.drawRoundRect(
+ 0f,
+ 0f,
+ width.toFloat(),
+ height.toFloat(),
+ cornerRadius.toFloat(),
+ cornerRadius.toFloat(),
+ paint
+ )
+ }
+
+ private fun refresh() {
+ val thumbnailBitmap = thumbnailData?.thumbnail
+
+ if (thumbnailBitmap != null) {
+ thumbnailBitmap.prepareToDraw()
+ bitmapShader =
+ BitmapShader(thumbnailBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+ paint.shader = bitmapShader
+ updateThumbnailMatrix()
+ } else {
+ bitmapShader = null
+ paint.shader = null
+ }
+
+ invalidate()
+ }
+
+ private fun updateThumbnailMatrix() {
+ previewPositionHelper.isOrientationChanged = false
+
+ val bitmapShader = bitmapShader ?: return
+ val thumbnailData = thumbnailData ?: return
+ val display = context.display ?: return
+ val windowMetrics = windowManager.maximumWindowMetrics
+
+ previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height)
+
+ val currentRotation: Int = display.rotation
+ val displayWidthPx = windowMetrics.bounds.width()
+ val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+ val isTablet = isTablet(context)
+ val taskbarSize =
+ if (isTablet) {
+ resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
+ } else {
+ 0
+ }
+
+ previewPositionHelper.updateThumbnailMatrix(
+ previewRect,
+ thumbnailData,
+ measuredWidth,
+ measuredHeight,
+ displayWidthPx,
+ taskbarSize,
+ isTablet,
+ currentRotation,
+ isRtl
+ )
+
+ bitmapShader.setLocalMatrix(previewPositionHelper.matrix)
+ paint.shader = bitmapShader
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index ec5abc7..15cfeee 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -16,15 +16,17 @@
package com.android.systemui.mediaprojection.appselector.view
+import android.graphics.Rect
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
-import com.android.systemui.media.dagger.MediaProjectionAppSelector
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -32,19 +34,27 @@
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
-class RecentTaskViewHolder @AssistedInject constructor(
- @Assisted root: ViewGroup,
+class RecentTaskViewHolder
+@AssistedInject
+constructor(
+ @Assisted private val root: ViewGroup,
private val iconLoader: AppIconLoader,
private val thumbnailLoader: RecentTaskThumbnailLoader,
+ private val taskViewSizeProvider: TaskPreviewSizeProvider,
@MediaProjectionAppSelector private val scope: CoroutineScope
-) : RecyclerView.ViewHolder(root) {
+) : RecyclerView.ViewHolder(root), ConfigurationListener, TaskPreviewSizeProvider.TaskPreviewSizeListener {
+ val thumbnailView: MediaProjectionTaskView = root.requireViewById(R.id.task_thumbnail)
private val iconView: ImageView = root.requireViewById(R.id.task_icon)
- private val thumbnailView: ImageView = root.requireViewById(R.id.task_thumbnail)
private var job: Job? = null
+ init {
+ updateThumbnailSize()
+ }
+
fun bind(task: RecentTask, onClick: (View) -> Unit) {
+ taskViewSizeProvider.addCallback(this)
job?.cancel()
job =
@@ -57,20 +67,33 @@
}
launch {
val thumbnail = thumbnailLoader.loadThumbnail(task.taskId)
- thumbnailView.setImageBitmap(thumbnail?.thumbnail)
+ thumbnailView.bindTask(task, thumbnail)
}
}
- thumbnailView.setOnClickListener(onClick)
+ root.setOnClickListener(onClick)
}
fun onRecycled() {
+ taskViewSizeProvider.removeCallback(this)
iconView.setImageDrawable(null)
- thumbnailView.setImageBitmap(null)
+ thumbnailView.bindTask(null, null)
job?.cancel()
job = null
}
+ override fun onTaskSizeChanged(size: Rect) {
+ updateThumbnailSize()
+ }
+
+ private fun updateThumbnailSize() {
+ thumbnailView.layoutParams =
+ thumbnailView.layoutParams.apply {
+ width = taskViewSizeProvider.size.width()
+ height = taskViewSizeProvider.size.height()
+ }
+ }
+
@AssistedFactory
fun interface Factory {
fun create(root: ViewGroup): RecentTaskViewHolder
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
index ec9cfa8..6af50a0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
@@ -26,7 +26,9 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-class RecentTasksAdapter @AssistedInject constructor(
+class RecentTasksAdapter
+@AssistedInject
+constructor(
@Assisted private val items: List<RecentTask>,
@Assisted private val listener: RecentTaskClickListener,
private val viewHolderFactory: RecentTaskViewHolder.Factory
@@ -34,8 +36,8 @@
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentTaskViewHolder {
val taskItem =
- LayoutInflater.from(parent.context)
- .inflate(R.layout.media_projection_task_item, null) as ViewGroup
+ LayoutInflater.from(parent.context)
+ .inflate(R.layout.media_projection_task_item, parent, false) as ViewGroup
return viewHolderFactory.create(taskItem)
}
@@ -43,7 +45,7 @@
override fun onBindViewHolder(holder: RecentTaskViewHolder, position: Int) {
val task = items[position]
holder.bind(task, onClick = {
- listener.onRecentClicked(task, holder.itemView)
+ listener.onRecentAppClicked(task, holder.itemView)
})
}
@@ -54,7 +56,7 @@
}
interface RecentTaskClickListener {
- fun onRecentClicked(task: RecentTask, view: View)
+ fun onRecentAppClicked(task: RecentTask, view: View)
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
new file mode 100644
index 0000000..88d5eaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.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.systemui.mediaprojection.appselector.view
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.WindowManager
+import com.android.internal.R as AndroidR
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+
+@MediaProjectionAppSelectorScope
+class TaskPreviewSizeProvider
+@Inject
+constructor(
+ private val context: Context,
+ private val windowManager: WindowManager,
+ configurationController: ConfigurationController
+) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener {
+
+ /** Returns the size of the task preview on the screen in pixels */
+ val size: Rect = calculateSize()
+
+ private val listeners = arrayListOf<TaskPreviewSizeListener>()
+
+ init {
+ configurationController.addCallback(this)
+ }
+
+ override fun onConfigChanged(newConfig: Configuration) {
+ val newSize = calculateSize()
+ if (newSize != size) {
+ size.set(newSize)
+ listeners.forEach { it.onTaskSizeChanged(size) }
+ }
+ }
+
+ private fun calculateSize(): Rect {
+ val windowMetrics = windowManager.maximumWindowMetrics
+ val maximumWindowHeight = windowMetrics.bounds.height()
+ val width = windowMetrics.bounds.width()
+ var height = maximumWindowHeight
+
+ val isTablet = isTablet(context)
+ if (isTablet) {
+ val taskbarSize =
+ context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
+ height -= taskbarSize
+ }
+
+ val previewSize = Rect(0, 0, width, height)
+ val scale = (height / maximumWindowHeight.toFloat()) / SCREEN_HEIGHT_TO_TASK_HEIGHT_RATIO
+ previewSize.scale(scale)
+
+ return previewSize
+ }
+
+ override fun addCallback(listener: TaskPreviewSizeListener) {
+ listeners += listener
+ }
+
+ override fun removeCallback(listener: TaskPreviewSizeListener) {
+ listeners -= listener
+ }
+
+ interface TaskPreviewSizeListener {
+ fun onTaskSizeChanged(size: Rect)
+ }
+}
+
+/**
+ * How many times smaller the task preview should be on the screen comparing to the height of the
+ * screen
+ */
+private const val SCREEN_HEIGHT_TO_TASK_HEIGHT_RATIO = 4f
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index da9fefa..33021e3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -48,7 +48,6 @@
import androidx.annotation.NonNull;
-import com.android.keyguard.KeyguardViewController;
import com.android.systemui.Dumpable;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -61,6 +60,7 @@
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -90,7 +90,7 @@
private final AccessibilityManager mAccessibilityManager;
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
- private final KeyguardViewController mKeyguardViewController;
+ private final KeyguardStateController mKeyguardStateController;
private final UserTracker mUserTracker;
private final SystemActions mSystemActions;
private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
@@ -125,7 +125,7 @@
OverviewProxyService overviewProxyService,
Lazy<AssistManager> assistManagerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
- KeyguardViewController keyguardViewController,
+ KeyguardStateController keyguardStateController,
NavigationModeController navigationModeController,
UserTracker userTracker,
DumpManager dumpManager) {
@@ -134,7 +134,7 @@
mAccessibilityManager = accessibilityManager;
mAssistManagerLazy = assistManagerLazy;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
- mKeyguardViewController = keyguardViewController;
+ mKeyguardStateController = keyguardStateController;
mUserTracker = userTracker;
mSystemActions = systemActions;
accessibilityManager.addAccessibilityServicesStateChangeListener(this);
@@ -326,7 +326,7 @@
shadeWindowView =
mCentralSurfacesOptionalLazy.get().get().getNotificationShadeWindowView();
}
- boolean isKeyguardShowing = mKeyguardViewController.isShowing();
+ boolean isKeyguardShowing = mKeyguardStateController.isShowing();
boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
&& shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
return imeVisibleOnShade
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 50a10bc..c089511 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.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 @@
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 @@
}
};
+ 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 @@
NavigationBarTransitions navigationBarTransitions,
EdgeBackGestureHandler edgeBackGestureHandler,
Optional<BackAnimation> backAnimation,
- UserContextProvider userContextProvider) {
+ UserContextProvider userContextProvider,
+ WakefulnessLifecycle wakefulnessLifecycle) {
super(navigationBarView);
mFrame = navigationBarFrame;
mContext = context;
@@ -529,6 +554,7 @@
mTelecomManagerOptional = telecomManagerOptional;
mInputMethodManager = inputMethodManager;
mUserContextProvider = userContextProvider;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
mNavColorSampleMargin = getResources()
.getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -653,7 +679,9 @@
public void onViewAttached() {
final Display display = mView.getDisplay();
mView.setComponents(mRecentsOptional);
- mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+ if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+ mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+ }
mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
mView.setOnVerticalChangedListener(this::onVerticalChanged);
mView.setOnTouchListener(this::onNavigationTouch);
@@ -682,11 +710,10 @@
prepareNavigationBarView();
checkNavBarModes();
- IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
+ IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
Handler.getMain(), UserHandle.ALL);
+ mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
notifyNavigationBarScreenOn();
mOverviewProxyService.addCallback(mOverviewProxyListener);
@@ -737,6 +764,7 @@
getBarTransitions().destroy();
mOverviewProxyService.removeCallback(mOverviewProxyListener);
mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
+ mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
if (mOrientationHandle != null) {
resetSecondaryHandle();
getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener);
@@ -1619,19 +1647,6 @@
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 3789cbb..3fd1aa7 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_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,13 +142,22 @@
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);
+ }
// If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
if (largeScreenChanged && updateNavbarForTaskbar()) {
return;
}
- if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+ if (willApplyConfig) {
for (int i = 0; i < mNavigationBars.size(); i++) {
recreateNavigationBar(mNavigationBars.keyAt(i));
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 9702488..403d276 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -148,6 +148,7 @@
private NavigationBarInflaterView mNavigationInflaterView;
private Optional<Recents> mRecentsOptional = Optional.empty();
+ @Nullable
private NotificationPanelViewController mPanelView;
private RotationContextButton mRotationContextButton;
private FloatingRotationButton mFloatingRotationButton;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 9e0c496..73fc21e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -40,7 +40,6 @@
import android.app.StatusBarManager;
import android.app.StatusBarManager.WindowVisibleState;
-import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -87,7 +86,7 @@
@SysUISingleton
public class TaskbarDelegate implements CommandQueue.Callbacks,
OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
- ComponentCallbacks, Dumpable {
+ Dumpable {
private static final String TAG = TaskbarDelegate.class.getSimpleName();
private final EdgeBackGestureHandler mEdgeBackGestureHandler;
@@ -225,7 +224,6 @@
// Initialize component callback
Display display = mDisplayManager.getDisplay(displayId);
mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
- mWindowContext.registerComponentCallbacks(this);
mScreenPinningNotify = new ScreenPinningNotify(mWindowContext);
// Set initial state for any listeners
updateSysuiFlags();
@@ -233,6 +231,7 @@
mLightBarController.setNavigationBar(mLightBarTransitionsController);
mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
+ mEdgeBackGestureHandler.onConfigurationChanged(mContext.getResources().getConfiguration());
mInitialized = true;
}
@@ -247,10 +246,7 @@
mNavBarHelper.destroy();
mEdgeBackGestureHandler.onNavBarDetached();
mScreenPinningNotify = null;
- if (mWindowContext != null) {
- mWindowContext.unregisterComponentCallbacks(this);
- mWindowContext = null;
- }
+ mWindowContext = null;
mAutoHideController.setNavigationBar(null);
mLightBarTransitionsController.destroy();
mLightBarController.setNavigationBar(null);
@@ -267,8 +263,9 @@
}
/**
- * Returns {@code true} if this taskBar is {@link #init(int)}. Returns {@code false} if this
- * taskbar has not yet been {@link #init(int)} or has been {@link #destroy()}.
+ * Returns {@code true} if this taskBar is {@link #init(int)}.
+ * Returns {@code false} if this taskbar has not yet been {@link #init(int)}
+ * or has been {@link #destroy()}.
*/
public boolean isInitialized() {
return mInitialized;
@@ -460,15 +457,11 @@
return mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
- @Override
public void onConfigurationChanged(Configuration configuration) {
mEdgeBackGestureHandler.onConfigurationChanged(configuration);
}
@Override
- public void onLowMemory() {}
-
- @Override
public void showPinningEnterExitToast(boolean entering) {
updateSysuiFlags();
if (mScreenPinningNotify == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 6424256..6e927b0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -417,6 +417,7 @@
stretchEntryBackIndicator(preThresholdStretchProgress(xTranslation))
GestureState.INACTIVE ->
mView.resetStretch()
+ else -> {}
}
// set y translation
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index b26b42c..709467f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -112,7 +113,7 @@
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() {
@@ -955,7 +956,7 @@
mStartingQuickstepRotation != rotation;
}
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
if (mStartingQuickstepRotation > -1) {
updateDisabledForQuickstep(newConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 67dae9e..1da866e 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.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 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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 5510eb1..cd32a10 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 @@
* 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 @@
@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 1ef6426..0fe3d16 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 @@
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/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 184089f7..6517ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -105,6 +105,7 @@
private final Rect mClippingRect = new Rect();
private ViewGroup mMediaHostView;
private boolean mShouldMoveMediaOnExpansion = true;
+ private boolean mUsingCombinedHeaders = false;
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -148,6 +149,10 @@
}
}
+ void setUsingCombinedHeaders(boolean usingCombinedHeaders) {
+ mUsingCombinedHeaders = usingCombinedHeaders;
+ }
+
protected void setHorizontalContentContainerClipping() {
mHorizontalContentContainer.setClipChildren(true);
mHorizontalContentContainer.setClipToPadding(false);
@@ -371,7 +376,9 @@
protected void updatePadding() {
final Resources res = mContext.getResources();
- int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
+ int paddingTop = res.getDimensionPixelSize(
+ mUsingCombinedHeaders ? R.dimen.qs_panel_padding_top_combined_headers
+ : R.dimen.qs_panel_padding_top);
int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
setPaddingRelative(getPaddingStart(),
paddingTop,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 18bd6b7..f6db775 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
+import static com.android.systemui.flags.Flags.COMBINED_QS_HEADERS;
import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
@@ -27,6 +28,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostState;
@@ -79,7 +81,8 @@
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSliderController.Factory brightnessSliderFactory,
FalsingManager falsingManager,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ FeatureFlags featureFlags) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager);
mTunerService = tunerService;
@@ -93,6 +96,7 @@
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mView.setUsingCombinedHeaders(featureFlags.isEnabled(COMBINED_QS_HEADERS));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ded466a..2727c83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -23,8 +23,8 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.res.Configuration;
+import android.content.res.Configuration.Orientation;
import android.metrics.LogMaker;
-import android.util.Log;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
@@ -75,6 +75,7 @@
@Nullable
private Consumer<Boolean> mMediaVisibilityChangedListener;
+ @Orientation
private int mLastOrientation;
private String mCachedSpecs = "";
@Nullable
@@ -88,21 +89,16 @@
new QSPanel.OnConfigurationChangedListener() {
@Override
public void onConfigurationChange(Configuration newConfig) {
+ mQSLogger.logOnConfigurationChanged(
+ /* lastOrientation= */ mLastOrientation,
+ /* newOrientation= */ newConfig.orientation,
+ /* containerName= */ mView.getDumpableTag());
+
mShouldUseSplitNotificationShade =
- LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
- // Logging to aid the investigation of b/216244185.
- Log.d(TAG,
- "onConfigurationChange: "
- + "mShouldUseSplitNotificationShade="
- + mShouldUseSplitNotificationShade + ", "
- + "newConfig.windowConfiguration="
- + newConfig.windowConfiguration);
- mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation,
- mView.getDumpableTag());
- if (newConfig.orientation != mLastOrientation) {
- mLastOrientation = newConfig.orientation;
- switchTileLayout(false);
- }
+ LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
+ mLastOrientation = newConfig.orientation;
+
+ switchTileLayoutIfNeeded();
onConfigurationChanged();
}
};
@@ -334,6 +330,10 @@
}
}
+ private void switchTileLayoutIfNeeded() {
+ switchTileLayout(/* force= */ false);
+ }
+
boolean switchTileLayout(boolean force) {
/* Whether or not the panel currently contains a media player. */
boolean horizontal = shouldUseHorizontalLayout();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 84d7e65..27d9da6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -25,6 +25,7 @@
import android.util.AttributeSet;
import android.util.Pair;
import android.view.DisplayCutout;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -231,6 +232,16 @@
}
}
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // If using combined headers, only react to touches inside QuickQSPanel
+ if (!mUseCombinedQSHeader || event.getY() > mHeaderQsPanel.getTop()) {
+ return super.onTouchEvent(event);
+ } else {
+ return false;
+ }
+ }
+
void updateResources() {
Resources resources = mContext.getResources();
boolean largeScreenHeaderActive =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 3e445dd..d393680 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.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 @@
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/FooterActionsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
index 5a8f684..9b5f683 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
@@ -25,7 +25,7 @@
* power buttons.
*/
data class FooterActionsButtonViewModel(
- val id: Int?,
+ val id: Int,
val icon: Icon,
val iconTint: Int?,
@DrawableRes val background: Int,
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 8b3f4b4..d3c06f6 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.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 @@
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 = null,
- Icon.Loaded(
- icon,
- ContentDescription.Loaded(userSwitcherContentDescription(status.currentUserName)),
- ),
- iconTint,
- R.drawable.qs_footer_action_circle,
- this::onUserSwitcherClicked,
+ id = R.id.multi_user_switch,
+ icon =
+ Icon.Loaded(
+ icon,
+ ContentDescription.Loaded(
+ userSwitcherContentDescription(status.currentUserName)
+ ),
+ ),
+ iconTint = null,
+ background = R.drawable.qs_footer_action_circle,
+ onClick = this::onUserSwitcherClicked,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 3c8775d0..9c0a087 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -197,7 +197,7 @@
};
protected List<SubscriptionInfo> getSubscriptionInfo() {
- return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+ return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo();
}
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 899e57d..66be00d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,15 +25,6 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_DESKTOP_MODE;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -110,16 +101,7 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.wm.shell.back.BackAnimation;
-import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.floating.FloatingTasks;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.ShellTransitions;
+import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -151,10 +133,8 @@
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
- private final Optional<Pip> mPipOptional;
+ private final ShellInterface mShellInterface;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
- private final Optional<SplitScreen> mSplitScreenOptional;
- private final Optional<FloatingTasks> mFloatingTasksOptional;
private SysUiState mSysUiState;
private final Handler mHandler;
private final Lazy<NavigationBarController> mNavBarControllerLazy;
@@ -164,14 +144,8 @@
private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
private final Intent mQuickStepIntent;
private final ScreenshotHelper mScreenshotHelper;
- private final Optional<OneHanded> mOneHandedOptional;
private final CommandQueue mCommandQueue;
- private final ShellTransitions mShellTransitions;
- private final Optional<StartingSurface> mStartingSurface;
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
- private final Optional<RecentTasks> mRecentTasks;
- private final Optional<BackAnimation> mBackAnimation;
- private final Optional<DesktopMode> mDesktopModeOptional;
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
@@ -456,36 +430,10 @@
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
-
- mPipOptional.ifPresent((pip) -> params.putBinder(
- KEY_EXTRA_SHELL_PIP,
- pip.createExternalInterface().asBinder()));
- mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(
- KEY_EXTRA_SHELL_SPLIT_SCREEN,
- splitscreen.createExternalInterface().asBinder()));
- mFloatingTasksOptional.ifPresent(floatingTasks -> params.putBinder(
- KEY_EXTRA_SHELL_FLOATING_TASKS,
- floatingTasks.createExternalInterface().asBinder()));
- mOneHandedOptional.ifPresent((onehanded) -> params.putBinder(
- KEY_EXTRA_SHELL_ONE_HANDED,
- onehanded.createExternalInterface().asBinder()));
- params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
- mShellTransitions.createExternalInterface().asBinder());
- mStartingSurface.ifPresent((startingwindow) -> params.putBinder(
- KEY_EXTRA_SHELL_STARTING_WINDOW,
- startingwindow.createExternalInterface().asBinder()));
- params.putBinder(
- KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
+ params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
mSysuiUnlockAnimationController.asBinder());
- mRecentTasks.ifPresent(recentTasks -> params.putBinder(
- KEY_EXTRA_RECENT_TASKS,
- recentTasks.createExternalInterface().asBinder()));
- mBackAnimation.ifPresent((backAnimation) -> params.putBinder(
- KEY_EXTRA_SHELL_BACK_ANIMATION,
- backAnimation.createExternalInterface().asBinder()));
- mDesktopModeOptional.ifPresent((desktopMode -> params.putBinder(
- KEY_EXTRA_SHELL_DESKTOP_MODE,
- desktopMode.createExternalInterface().asBinder())));
+ // Add all the interfaces exposed by the shell
+ mShellInterface.createExternalInterfaces(params);
try {
Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
@@ -559,21 +507,14 @@
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
- public OverviewProxyService(Context context, CommandQueue commandQueue,
+ public OverviewProxyService(Context context,
+ CommandQueue commandQueue,
+ ShellInterface shellInterface,
Lazy<NavigationBarController> navBarControllerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
- Optional<Pip> pipOptional,
- Optional<SplitScreen> splitScreenOptional,
- Optional<FloatingTasks> floatingTasksOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<RecentTasks> recentTasks,
- Optional<BackAnimation> backAnimation,
- Optional<StartingSurface> startingSurface,
- Optional<DesktopMode> desktopModeOptional,
BroadcastDispatcher broadcastDispatcher,
- ShellTransitions shellTransitions,
ScreenLifecycle screenLifecycle,
UiEventLogger uiEventLogger,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
@@ -587,7 +528,7 @@
}
mContext = context;
- mPipOptional = pipOptional;
+ mShellInterface = shellInterface;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
@@ -602,11 +543,6 @@
.supportsRoundedCornersOnWindows(mContext.getResources());
mSysUiState = sysUiState;
mSysUiState.addCallback(this::notifySystemUiStateFlags);
- mOneHandedOptional = oneHandedOptional;
- mShellTransitions = shellTransitions;
- mRecentTasks = recentTasks;
- mBackAnimation = backAnimation;
- mDesktopModeOptional = desktopModeOptional;
mUiEventLogger = uiEventLogger;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -636,9 +572,6 @@
});
mCommandQueue = commandQueue;
- mSplitScreenOptional = splitScreenOptional;
- mFloatingTasksOptional = floatingTasksOptional;
-
// Listen for user setup
startTracking();
@@ -647,7 +580,6 @@
// Connect to the service
updateEnabledState();
startConnectionToCurrentUser();
- mStartingSurface = startingSurface;
mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
// Listen for assistant changes
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 9b3b843..b041f95 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 @@
/**
* 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 0000000..017e57f
--- /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 0000000..5961635
--- /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 950806d..ead3b7b 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 @@
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 @@
}
});
mSwipeDetector.setIsLongpressEnabled(false);
+
+ mCallbacks = new SwipeDismissCallbacks() {
+ }; // default to unimplemented callbacks
}
public void setCallbacks(SwipeDismissCallbacks callbacks) {
@@ -119,16 +121,13 @@
@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 @@
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 @@
mGestureDetector = new GestureDetector(context, gestureListener);
mDisplayMetrics = new DisplayMetrics();
context.getDisplay().getRealMetrics(mDisplayMetrics);
- mCallbacks = new SwipeDismissCallbacks() {
- }; // default to unimplemented callbacks
}
@Override
@@ -230,7 +234,9 @@
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 @@
}
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 @@
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 @@
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 0000000..da83472
--- /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 0000000..e15030f
--- /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 f7c4dad..d2e3fbd 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 309059f..95cc0dc 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 @@
)
} 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 077ad35..7143ba2 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 @@
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 @@
// 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 @@
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 @@
// 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 @@
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 df32d20..231e415 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_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 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 @@
deleteAction = null;
smartActions = null;
quickShareAction = null;
+ subject = null;
}
}
@@ -272,6 +274,7 @@
private final ScreenshotNotificationSmartActionsProvider
mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler;
+ private final ActionIntentExecutor mActionExecutor;
private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap;
@@ -309,7 +312,8 @@
ActivityManager activityManager,
TimeoutHandler timeoutHandler,
BroadcastSender broadcastSender,
- ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider
+ ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
+ ActionIntentExecutor actionExecutor
) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
@@ -331,9 +335,7 @@
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 @@
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mWindowManager = mContext.getSystemService(WindowManager.class);
mFlags = flags;
+ mActionExecutor = actionExecutor;
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -361,8 +364,7 @@
@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);
}
}
};
@@ -408,44 +410,22 @@
}
/**
- * Displays a screenshot selector
- */
- @MainThread
- void takeScreenshotPartial(ComponentName topComponent,
- final Consumer<Uri> finisher, RequestCallback requestCallback) {
- Assert.isMainThread();
- mScreenshotView.reset();
- mCurrentRequestCallback = requestCallback;
-
- attachWindow();
- mWindow.setContentView(mScreenshotView);
- mScreenshotView.requestApplyInsets();
-
- mScreenshotView.takePartialScreenshot(
- rect -> takeScreenshotInternal(topComponent, finisher, rect));
- }
-
- /**
* 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() {
@@ -510,7 +490,7 @@
// 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) -> {
@@ -518,7 +498,7 @@
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 0000000..2e6c756
--- /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 9654e03..c41e2bc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -19,14 +19,17 @@
import android.content.Intent
import android.os.IBinder
import android.util.Log
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
+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: PanelExpansionStateManager
+ private val mExpansionMgr: ShadeExpansionStateManager,
+ private val mCentralSurfacesOptional: Optional<CentralSurfaces>,
) : Service() {
private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
@@ -38,6 +41,20 @@
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/ScreenshotSelectorView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java
deleted file mode 100644
index c793b5b..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.screenshot;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-
-import java.util.function.Consumer;
-
-/**
- * Draws a selection rectangle while taking screenshot
- */
-public class ScreenshotSelectorView extends View {
- private Point mStartPoint;
- private Rect mSelectionRect;
- private final Paint mPaintSelection, mPaintBackground;
-
- private Consumer<Rect> mOnScreenshotSelected;
-
- public ScreenshotSelectorView(Context context) {
- this(context, null);
- }
-
- public ScreenshotSelectorView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- mPaintBackground = new Paint(Color.BLACK);
- mPaintBackground.setAlpha(160);
- mPaintSelection = new Paint(Color.TRANSPARENT);
- mPaintSelection.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-
- setOnTouchListener((v, event) -> {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- startSelection((int) event.getX(), (int) event.getY());
- return true;
- case MotionEvent.ACTION_MOVE:
- updateSelection((int) event.getX(), (int) event.getY());
- return true;
- case MotionEvent.ACTION_UP:
- setVisibility(View.GONE);
- final Rect rect = getSelectionRect();
- if (mOnScreenshotSelected != null
- && rect != null
- && rect.width() != 0 && rect.height() != 0) {
- mOnScreenshotSelected.accept(rect);
- }
- stopSelection();
- return true;
- }
- return false;
- });
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.drawRect(mLeft, mTop, mRight, mBottom, mPaintBackground);
- if (mSelectionRect != null) {
- canvas.drawRect(mSelectionRect, mPaintSelection);
- }
- }
-
- void setOnScreenshotSelected(Consumer<Rect> onScreenshotSelected) {
- mOnScreenshotSelected = onScreenshotSelected;
- }
-
- void stop() {
- if (getSelectionRect() != null) {
- stopSelection();
- }
- }
-
- private void startSelection(int x, int y) {
- mStartPoint = new Point(x, y);
- mSelectionRect = new Rect(x, y, x, y);
- }
-
- private void updateSelection(int x, int y) {
- if (mSelectionRect != null) {
- mSelectionRect.left = Math.min(mStartPoint.x, x);
- mSelectionRect.right = Math.max(mStartPoint.x, x);
- mSelectionRect.top = Math.min(mStartPoint.y, y);
- mSelectionRect.bottom = Math.max(mStartPoint.y, y);
- invalidate();
- }
- }
-
- private Rect getSelectionRect() {
- return mSelectionRect;
- }
-
- private void stopSelection() {
- mStartPoint = null;
- mSelectionRect = null;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 360fc87..26cbcbf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -87,13 +87,14 @@
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;
import com.android.systemui.shared.system.QuickStepContract;
import java.util.ArrayList;
-import java.util.function.Consumer;
/**
* Handles the visual elements and animations for the screenshot flow.
@@ -141,7 +142,6 @@
private boolean mOrientationPortrait;
private boolean mDirectionLTR;
- private ScreenshotSelectorView mScreenshotSelectorView;
private ImageView mScrollingScrim;
private DraggableConstraintLayout mScreenshotStatic;
private ImageView mScreenshotPreview;
@@ -170,6 +170,8 @@
private final InteractionJankMonitor mInteractionJankMonitor;
private long mDefaultTimeoutOfTimeoutHandler;
+ private ActionIntentExecutor mActionExecutor;
+ private FeatureFlags mFlags;
private enum PendingInteraction {
PREVIEW,
@@ -361,7 +363,6 @@
mDismissButton = requireNonNull(findViewById(R.id.screenshot_dismiss_button));
mScrollablePreview = requireNonNull(findViewById(R.id.screenshot_scrollable_preview));
mScreenshotFlash = requireNonNull(findViewById(R.id.screenshot_flash));
- mScreenshotSelectorView = requireNonNull(findViewById(R.id.screenshot_selector));
mShareChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_share_chip));
mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
@@ -377,8 +378,6 @@
mActionsContainerBackground.setTouchDelegate(actionsDelegate);
setFocusable(true);
- mScreenshotSelectorView.setFocusable(true);
- mScreenshotSelectorView.setFocusableInTouchMode(true);
mActionsContainer.setScrollX(0);
mNavMode = getResources().getInteger(
@@ -427,15 +426,12 @@
* 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;
- }
-
- void takePartialScreenshot(Consumer<Rect> onPartialScreenshotSelected) {
- mScreenshotSelectorView.setOnScreenshotSelected(onPartialScreenshotSelected);
- mScreenshotSelectorView.setVisibility(View.VISIBLE);
- mScreenshotSelectorView.requestFocus();
+ mActionExecutor = actionExecutor;
+ mFlags = flags;
}
void setScreenshot(Bitmap bitmap, Insets screenInsets) {
@@ -770,18 +766,37 @@
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,
@@ -1031,7 +1046,6 @@
mQuickShareChip = null;
setAlpha(1);
mScreenshotStatic.setAlpha(1);
- mScreenshotSelectorView.stop();
}
private void startSharedTransition(ActionTransition transition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 695a80b..2176825 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 @@
Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
}
if (!mScreenshot.isPendingSharedTransition()) {
- mUiEventLogger.log(SCREENSHOT_DISMISSED_OTHER);
- mScreenshot.dismissScreenshot(false);
+ mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
}
}
}
@@ -249,12 +248,6 @@
}
mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, callback);
break;
- case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
- if (DEBUG_SERVICE) {
- Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION");
- }
- mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, callback);
- break;
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index ad073c0..d450afa 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 @@
@SysUISingleton
class UserFileManagerImpl @Inject constructor(
// Context of system process and system user.
- val context: Context,
+ private val context: Context,
val userManager: UserManager,
val broadcastDispatcher: BroadcastDispatcher,
@Background val backgroundExecutor: DelayableExecutor
-) : UserFileManager, CoreStartable(context) {
+) : UserFileManager, CoreStartable {
companion object {
private const val FILES = "files"
@VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index d3ed474..a494f42 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -280,6 +280,9 @@
context.getString(com.android.internal.R.string.status_bar_alarm_clock)
)
}
+ if (combinedHeaders) {
+ privacyIconsController.onParentVisible()
+ }
}
override fun onViewAttached() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index e0cd482..ba779c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -20,8 +20,8 @@
import android.view.ViewGroup
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
@@ -36,11 +36,11 @@
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
setOf(
- ViewIdToTranslate(R.id.quick_settings_panel, LEFT),
- ViewIdToTranslate(R.id.notification_stack_scroller, RIGHT),
- ViewIdToTranslate(R.id.rightLayout, RIGHT),
- ViewIdToTranslate(R.id.clock, LEFT),
- ViewIdToTranslate(R.id.date, LEFT)),
+ ViewIdToTranslate(R.id.quick_settings_panel, START),
+ ViewIdToTranslate(R.id.notification_stack_scroller, END),
+ ViewIdToTranslate(R.id.rightLayout, END),
+ ViewIdToTranslate(R.id.clock, START),
+ ViewIdToTranslate(R.id.date, START)),
progressProvider = progressProvider)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1110386..20f0655 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,15 @@
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;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -36,11 +45,10 @@
import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
-import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
-import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
-import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
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.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.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.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.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;
@@ -202,8 +216,6 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -232,8 +244,13 @@
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;
@@ -264,6 +281,22 @@
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();
@@ -335,6 +368,28 @@
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;
@@ -634,6 +689,7 @@
private int mScreenCornerRadius;
private boolean mQSAnimatingHiddenFromCollapsed;
private boolean mUseLargeScreenShadeHeader;
+ private boolean mEnableQsClipping;
private int mQsClipTop;
private int mQsClipBottom;
@@ -708,6 +764,54 @@
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,
@@ -760,7 +864,7 @@
LargeScreenShadeHeaderController largeScreenShadeHeaderController,
ScreenOffAnimationController screenOffAnimationController,
LockscreenGestureLogger lockscreenGestureLogger,
- PanelExpansionStateManager panelExpansionStateManager,
+ ShadeExpansionStateManager shadeExpansionStateManager,
NotificationRemoteInputManager remoteInputManager,
Optional<SysUIUnfoldComponent> unfoldComponent,
InteractionJankMonitor interactionJankMonitor,
@@ -777,32 +881,73 @@
CameraGestureHelper cameraGestureHelper,
KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
KeyguardBottomAreaInteractor keyguardBottomAreaInteractor) {
- super(view,
- falsingManager,
- dozeLog,
- keyguardStateController,
- (SysuiStatusBarStateController) statusBarStateController,
- notificationShadeWindowController,
- vibratorHelper,
- statusBarKeyguardViewManager,
- latencyTracker,
- flingAnimationUtilsBuilder.get(),
- statusBarTouchableRegionManager,
- lockscreenGestureLogger,
- panelExpansionStateManager,
- 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;
@@ -824,7 +969,6 @@
mLargeScreenShadeHeaderController = largeScreenShadeHeaderController;
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
- mFalsingManager = falsingManager;
mFalsingCollector = falsingCollector;
mPowerManager = powerManager;
mWakeUpCoordinator = coordinator;
@@ -840,7 +984,6 @@
mUserManager = userManager;
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
- mInteractionJankMonitor = interactionJankMonitor;
mSysUiState = sysUiState;
mPanelEventsEmitter = panelEventsEmitter;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
@@ -860,7 +1003,7 @@
new DynamicPrivacyControlListener();
dynamicPrivacyController.addListener(dynamicPrivacyControlListener);
- panelExpansionStateManager.addStateListener(this::onPanelStateChanged);
+ shadeExpansionStateManager.addStateListener(this::onPanelStateChanged);
mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
mBottomAreaShadeAlphaAnimator.addUpdateListener(animation -> {
@@ -1042,9 +1185,14 @@
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());
@@ -1151,6 +1299,8 @@
mSplitShadeFullTransitionDistance =
mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance);
+
+ mEnableQsClipping = mResources.getBoolean(R.bool.qs_enable_clipping);
}
private void onSplitShadeEnabledChanged() {
@@ -1717,11 +1867,10 @@
// 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;
@@ -1731,7 +1880,20 @@
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) {
@@ -1751,10 +1913,15 @@
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() {
@@ -1822,28 +1989,123 @@
}
}
- @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);
}
@@ -1905,7 +2167,8 @@
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");
@@ -1920,6 +2183,9 @@
mInitialTouchX = x;
mNotificationStackScrollLayoutController.cancelLongPress();
return true;
+ } else {
+ mShadeLog.logQsTrackingNotStarted(mInitialTouchY, y, h, touchSlop, mQsExpanded,
+ mCollapsedOnDown, mKeyguardShowing, isQsExpansionEnabled());
}
break;
@@ -1938,8 +2204,7 @@
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)
@@ -2072,9 +2337,8 @@
- mQsMinExpansionHeight));
}
- @Override
- protected boolean shouldExpandWhenNotFlinging() {
- if (super.shouldExpandWhenNotFlinging()) {
+ private boolean shouldExpandWhenNotFlinging() {
+ if (getExpandedFraction() > 0.5f) {
return true;
}
if (mAllowExpandForSmallExpansion) {
@@ -2086,8 +2350,7 @@
return false;
}
- @Override
- protected float getOpeningHeight() {
+ private float getOpeningHeight() {
return mNotificationStackScrollLayoutController.getOpeningHeight();
}
@@ -2238,9 +2501,20 @@
}
}
- @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) {
@@ -2249,8 +2523,7 @@
return expands;
}
- @Override
- protected boolean shouldGestureWaitForTouchSlop() {
+ private boolean shouldGestureWaitForTouchSlop() {
if (mExpectingSynthesizedDown) {
mExpectingSynthesizedDown = false;
return false;
@@ -2328,7 +2601,7 @@
}
}
- protected int getFalsingThreshold() {
+ private int getFalsingThreshold() {
float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
return (int) (mQsFalsingThreshold * factor);
}
@@ -2682,8 +2955,10 @@
mQsTranslationForFullShadeTransition = qsTranslation;
updateQsFrameTranslation();
float currentTranslation = mQsFrame.getTranslationY();
- mQsClipTop = (int) (top - currentTranslation - mQsFrame.getTop());
- mQsClipBottom = (int) (bottom - currentTranslation - mQsFrame.getTop());
+ mQsClipTop = mEnableQsClipping
+ ? (int) (top - currentTranslation - mQsFrame.getTop()) : 0;
+ mQsClipBottom = mEnableQsClipping
+ ? (int) (bottom - currentTranslation - mQsFrame.getTop()) : 0;
mQsVisible = qsVisible;
mQs.setQsVisible(mQsVisible);
mQs.setFancyClipping(
@@ -3068,8 +3343,8 @@
}
}
- @Override
- protected boolean canCollapsePanelOnTouch() {
+ @VisibleForTesting
+ boolean canCollapsePanelOnTouch() {
if (!isInSettings() && mBarState == KEYGUARD) {
return true;
}
@@ -3081,7 +3356,6 @@
return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
}
- @Override
public int getMaxPanelHeight() {
int min = mStatusBarMinHeight;
if (!(mBarState == KEYGUARD)
@@ -3115,8 +3389,7 @@
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.
@@ -3295,9 +3568,7 @@
mLockIconViewController.setAlpha(alpha);
}
- @Override
- protected void onExpandingStarted() {
- super.onExpandingStarted();
+ private void onExpandingStarted() {
mNotificationStackScrollLayoutController.onExpansionStarted();
mIsExpanding = true;
mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
@@ -3313,8 +3584,7 @@
mQs.setHeaderListening(true);
}
- @Override
- protected void onExpandingFinished() {
+ private void onExpandingFinished() {
mScrimController.onExpandingFinished();
mNotificationStackScrollLayoutController.onExpansionStopped();
mHeadsUpManager.onExpandingFinished();
@@ -3366,18 +3636,58 @@
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();
@@ -3385,14 +3695,18 @@
}
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);
@@ -3402,10 +3716,11 @@
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 */);
@@ -3422,37 +3737,48 @@
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
@@ -3477,8 +3803,8 @@
}
}
- @Override
- protected boolean isTrackingBlocked() {
+ @VisibleForTesting
+ boolean isTrackingBlocked() {
return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
}
@@ -3500,19 +3826,17 @@
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);
}
@@ -3525,7 +3849,6 @@
}
}
- @Override
public boolean isDozing() {
return mDozing;
}
@@ -3542,8 +3865,7 @@
mKeyguardStatusViewController.dozeTimeTick();
}
- @Override
- protected boolean onMiddleClicked() {
+ private boolean onMiddleClicked() {
switch (mBarState) {
case KEYGUARD:
if (!mDozingOnDown) {
@@ -3602,15 +3924,13 @@
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);
@@ -3624,8 +3944,7 @@
// otherwise we update the state when the expansion is finished
}
- @Override
- protected void onClosingFinished() {
+ private void onClosingFinished() {
mCentralSurfaces.onClosingFinished();
setClosingWithAlphaFadeout(false);
mMediaHierarchyManager.closeGuts();
@@ -3714,8 +4033,7 @@
mCentralSurfaces.clearNotificationEffects();
}
- @Override
- protected boolean isPanelVisibleBecauseOfHeadsUp() {
+ private boolean isPanelVisibleBecauseOfHeadsUp() {
return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
&& mBarState == StatusBarState.SHADE;
}
@@ -3830,9 +4148,15 @@
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);
}
@@ -4032,9 +4356,14 @@
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());
@@ -4177,126 +4506,13 @@
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()
- && mStatusBarKeyguardViewManager.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 =
@@ -4348,8 +4564,7 @@
}
};
- @Override
- protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
+ private OnConfigurationChangedListener createOnConfigurationChangedListener() {
return new OnConfigurationChangedListener();
}
@@ -4407,10 +4622,597 @@
}
mSysUiState.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
isFullyExpanded() && !isInSettings())
- .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isInSettings())
+ .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isFullyExpanded() && isInSettings())
.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) {
@@ -4819,13 +5621,18 @@
}
}
- 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());
@@ -5083,4 +5890,365 @@
}
}
}
+
+ /** 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/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 6be9bbb..65bd58d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -52,7 +52,6 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import java.io.PrintWriter;
@@ -91,7 +90,7 @@
private boolean mExpandingBelowNotch;
private final DockManager mDockManager;
private final NotificationPanelViewController mNotificationPanelViewController;
- private final PanelExpansionStateManager mPanelExpansionStateManager;
+ private final ShadeExpansionStateManager mShadeExpansionStateManager;
private boolean mIsTrackingBarGesture = false;
@@ -104,7 +103,7 @@
NotificationShadeDepthController depthController,
NotificationShadeWindowView notificationShadeWindowView,
NotificationPanelViewController notificationPanelViewController,
- PanelExpansionStateManager panelExpansionStateManager,
+ ShadeExpansionStateManager shadeExpansionStateManager,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
StatusBarWindowStateController statusBarWindowStateController,
@@ -124,7 +123,7 @@
mView = notificationShadeWindowView;
mDockManager = dockManager;
mNotificationPanelViewController = notificationPanelViewController;
- mPanelExpansionStateManager = panelExpansionStateManager;
+ mShadeExpansionStateManager = shadeExpansionStateManager;
mDepthController = depthController;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
@@ -404,7 +403,7 @@
setDragDownHelper(mLockscreenShadeTransitionController.getTouchHelper());
mDepthController.setRoot(mView);
- mPanelExpansionStateManager.addExpansionListener(mDepthController);
+ mShadeExpansionStateManager.addExpansionListener(mDepthController);
}
public NotificationShadeWindowView getView() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index 7dc9dc7..49709a8 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 b4ce95c..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ /dev/null
@@ -1,1494 +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.phone.panelstate.PanelExpansionStateManager;
-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 PanelExpansionStateManager mPanelExpansionStateManager;
- 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,
- PanelExpansionStateManager panelExpansionStateManager,
- 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;
- mPanelExpansionStateManager = panelExpansionStateManager;
- 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() {
- mPanelExpansionStateManager.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 PanelExpansionStateManager getPanelExpansionStateManager() {
- return mPanelExpansionStateManager;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
index 7c61b29..71dfafa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.shade
import android.annotation.FloatRange
-data class PanelExpansionChangeEvent(
+data class ShadeExpansionChangeEvent(
/** 0 when collapsed, 1 when fully expanded. */
@FloatRange(from = 0.0, to = 1.0) val fraction: Float,
/** Whether the panel should be considered expanded */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionListener.kt
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionListener.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionListener.kt
index d003824..a5a9ffd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionListener.kt
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.shade
/** A listener interface to be notified of expansion events for the notification panel. */
-fun interface PanelExpansionListener {
+fun interface ShadeExpansionListener {
/**
* Invoked whenever the notification panel expansion changes, at every animation frame. This is
* the main expansion that happens when the user is swiping up to dismiss the lock screen and
* swiping to pull down the notification shade.
*/
- fun onPanelExpansionChanged(event: PanelExpansionChangeEvent)
+ fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 6b7c42e..f617d47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.shade
import android.annotation.IntDef
import android.util.Log
@@ -29,10 +29,10 @@
* TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
*/
@SysUISingleton
-class PanelExpansionStateManager @Inject constructor() {
+class ShadeExpansionStateManager @Inject constructor() {
- private val expansionListeners = mutableListOf<PanelExpansionListener>()
- private val stateListeners = mutableListOf<PanelStateListener>()
+ private val expansionListeners = mutableListOf<ShadeExpansionListener>()
+ private val stateListeners = mutableListOf<ShadeStateListener>()
@PanelState private var state: Int = STATE_CLOSED
@FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
@@ -45,24 +45,25 @@
*
* Listener will also be immediately notified with the current values.
*/
- fun addExpansionListener(listener: PanelExpansionListener) {
+ fun addExpansionListener(listener: ShadeExpansionListener) {
expansionListeners.add(listener)
listener.onPanelExpansionChanged(
- PanelExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount))
+ ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
+ )
}
/** Removes an expansion listener. */
- fun removeExpansionListener(listener: PanelExpansionListener) {
+ fun removeExpansionListener(listener: ShadeExpansionListener) {
expansionListeners.remove(listener)
}
/** Adds a listener that will be notified when the panel state has changed. */
- fun addStateListener(listener: PanelStateListener) {
+ fun addStateListener(listener: ShadeStateListener) {
stateListeners.add(listener)
}
/** Removes a state listener. */
- fun removeStateListener(listener: PanelStateListener) {
+ fun removeStateListener(listener: ShadeStateListener) {
stateListeners.remove(listener)
}
@@ -110,25 +111,26 @@
debugLog(
"panelExpansionChanged:" +
- "start state=${oldState.panelStateToString()} " +
- "end state=${state.panelStateToString()} " +
- "f=$fraction " +
- "expanded=$expanded " +
- "tracking=$tracking " +
- "dragDownPxAmount=$dragDownPxAmount " +
- "${if (fullyOpened) " fullyOpened" else ""} " +
- if (fullyClosed) " fullyClosed" else ""
+ "start state=${oldState.panelStateToString()} " +
+ "end state=${state.panelStateToString()} " +
+ "f=$fraction " +
+ "expanded=$expanded " +
+ "tracking=$tracking " +
+ "dragDownPxAmount=$dragDownPxAmount " +
+ "${if (fullyOpened) " fullyOpened" else ""} " +
+ if (fullyClosed) " fullyClosed" else ""
)
val expansionChangeEvent =
- PanelExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
+ ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
}
/** Updates the panel state if necessary. */
fun updateState(@PanelState state: Int) {
debugLog(
- "update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
+ "update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}"
+ )
if (this.state != state) {
updateStateInternal(state)
}
@@ -165,5 +167,5 @@
}
}
-private val TAG = PanelExpansionStateManager::class.simpleName
+private val TAG = ShadeExpansionStateManager::class.simpleName
private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index f1e44ce..7bee0ba 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 @@
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/statusbar/phone/panelstate/PanelStateListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateListener.kt
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeStateListener.kt
index ca667dd..74468a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateListener.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.shade
/** A listener interface to be notified of state change events for the notification panel. */
-fun interface PanelStateListener {
- /** Called when the panel's expansion state has changed. */
+fun interface ShadeStateListener {
+ /** Called when the panel's expansion state has changed. */
fun onPanelStateChanged(@PanelState state: Int)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
new file mode 100644
index 0000000..09019a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.shade.data.repository
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/** Business logic for shade interactions */
+@SysUISingleton
+class ShadeRepository @Inject constructor(shadeExpansionStateManager: ShadeExpansionStateManager) {
+
+ val shadeModel: Flow<ShadeModel> =
+ conflatedCallbackFlow {
+ val callback =
+ object : ShadeExpansionListener {
+ override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
+ // Don't propagate ShadeExpansionChangeEvent.dragDownPxAmount field.
+ // It is too noisy and produces extra events that consumers won't care
+ // about
+ val info =
+ ShadeModel(
+ expansionAmount = event.fraction,
+ isExpanded = event.expanded,
+ isUserDragging = event.tracking
+ )
+ trySendWithFailureLogging(info, TAG, "updated shade expansion info")
+ }
+ }
+
+ shadeExpansionStateManager.addExpansionListener(callback)
+ trySendWithFailureLogging(ShadeModel(), TAG, "initial shade expansion info")
+
+ awaitClose { shadeExpansionStateManager.removeExpansionListener(callback) }
+ }
+ .distinctUntilChanged()
+
+ companion object {
+ private const val TAG = "ShadeRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt
copy to packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
index 7c61b29..ce0f4283 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,19 +11,18 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
*/
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.shade.domain.model
import android.annotation.FloatRange
-data class PanelExpansionChangeEvent(
+/** Information about shade (NotificationPanel) expansion */
+data class ShadeModel(
/** 0 when collapsed, 1 when fully expanded. */
- @FloatRange(from = 0.0, to = 1.0) val fraction: Float,
+ @FloatRange(from = 0.0, to = 1.0) val expansionAmount: Float = 0f,
/** Whether the panel should be considered expanded */
- val expanded: Boolean,
+ val isExpanded: Boolean = false,
/** Whether the user is actively dragging the panel. */
- val tracking: Boolean,
- /** The amount of pixels that the user has dragged during the expansion. */
- val dragDownPxAmount: Float
+ val isUserDragging: Boolean = false,
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 618c892..a77c21a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -7,12 +7,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.shade.PanelState
+import com.android.systemui.shade.STATE_OPENING
+import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.PanelState
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.LargeScreenUtils
@@ -35,7 +35,7 @@
private var inSplitShade = false
private var splitShadeScrimTransitionDistance = 0
private var lastExpansionFraction: Float? = null
- private var lastExpansionEvent: PanelExpansionChangeEvent? = null
+ private var lastExpansionEvent: ShadeExpansionChangeEvent? = null
private var currentPanelState: Int? = null
init {
@@ -61,8 +61,8 @@
onStateChanged()
}
- fun onPanelExpansionChanged(panelExpansionChangeEvent: PanelExpansionChangeEvent) {
- lastExpansionEvent = panelExpansionChangeEvent
+ fun onPanelExpansionChanged(shadeExpansionChangeEvent: ShadeExpansionChangeEvent) {
+ lastExpansionEvent = shadeExpansionChangeEvent
onStateChanged()
}
@@ -75,7 +75,7 @@
}
private fun calculateScrimExpansionFraction(
- expansionEvent: PanelExpansionChangeEvent,
+ expansionEvent: ShadeExpansionChangeEvent,
@PanelState panelState: Int?
): Float {
return if (canUseCustomFraction(panelState)) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
index 6c3a028..22e847d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
@@ -1,6 +1,6 @@
package com.android.systemui.shade.transition
-import com.android.systemui.statusbar.phone.panelstate.PanelState
+import com.android.systemui.shade.PanelState
/** Represents an over scroller for the non-lockscreen shade. */
interface ShadeOverScroller {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 58acfb4..1e8208f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -7,13 +7,13 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.shade.PanelState
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.panelStateToString
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
-import com.android.systemui.statusbar.phone.panelstate.PanelState
-import com.android.systemui.statusbar.phone.panelstate.panelStateToString
import com.android.systemui.statusbar.policy.ConfigurationController
import java.io.PrintWriter
import javax.inject.Inject
@@ -24,7 +24,7 @@
@Inject
constructor(
configurationController: ConfigurationController,
- panelExpansionStateManager: PanelExpansionStateManager,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
dumpManager: DumpManager,
private val context: Context,
private val splitShadeOverScrollerFactory: SplitShadeOverScroller.Factory,
@@ -39,7 +39,7 @@
private var inSplitShade = false
private var currentPanelState: Int? = null
- private var lastPanelExpansionChangeEvent: PanelExpansionChangeEvent? = null
+ private var lastShadeExpansionChangeEvent: ShadeExpansionChangeEvent? = null
private val splitShadeOverScroller by lazy {
splitShadeOverScrollerFactory.create({ qs }, { notificationStackScrollLayoutController })
@@ -60,8 +60,8 @@
updateResources()
}
})
- panelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
- panelExpansionStateManager.addStateListener(this::onPanelStateChanged)
+ shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+ shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
dumpManager.registerDumpable("ShadeTransitionController") { printWriter, _ ->
dump(printWriter)
}
@@ -77,8 +77,8 @@
scrimShadeTransitionController.onPanelStateChanged(state)
}
- private fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
- lastPanelExpansionChangeEvent = event
+ private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
+ lastShadeExpansionChangeEvent = event
shadeOverScroller.onDragDownAmountChanged(event.dragDownPxAmount)
scrimShadeTransitionController.onPanelExpansionChanged(event)
}
@@ -95,7 +95,7 @@
inSplitShade: $inSplitShade
isScreenUnlocked: ${isScreenUnlocked()}
currentPanelState: ${currentPanelState?.panelStateToString()}
- lastPanelExpansionChangeEvent: $lastPanelExpansionChangeEvent
+ lastPanelExpansionChangeEvent: $lastShadeExpansionChangeEvent
qs.isInitialized: ${this::qs.isInitialized}
npvc.isInitialized: ${this::notificationPanelViewController.isInitialized}
nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
index 204dd3c..8c57194 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
@@ -10,11 +10,11 @@
import com.android.systemui.animation.Interpolators
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.shade.PanelState
+import com.android.systemui.shade.STATE_CLOSED
+import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.phone.panelstate.PanelState
-import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -37,6 +37,7 @@
private var previousOverscrollAmount = 0
private var dragDownAmount: Float = 0f
@PanelState private var panelState: Int = STATE_CLOSED
+
private var releaseOverScrollAnimator: Animator? = null
private val qS: QS
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 6abf339..ff26766 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 @@
* 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 @@
@Inject
public ShortcutKeyDispatcher(Context context) {
- super(context);
+ mContext = context;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index 4d53064..ce730ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -20,14 +20,16 @@
import android.widget.FrameLayout
/**
- * A temporary base class that's shared between our old status bar wifi view implementation
- * ([StatusBarWifiView]) and our new status bar wifi view implementation
- * ([ModernStatusBarWifiView]).
+ * A temporary base class that's shared between our old status bar connectivity view implementations
+ * ([StatusBarWifiView], [StatusBarMobileView]) and our new status bar implementations (
+ * [ModernStatusBarWifiView], [ModernStatusBarMobileView]).
*
* Once our refactor is over, we should be able to delete this go-between class and the old view
* class.
*/
-abstract class BaseStatusBarWifiView @JvmOverloads constructor(
+abstract class BaseStatusBarFrameLayout
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttrs: Int = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 8699441..184dc25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -42,7 +42,6 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -97,6 +96,7 @@
private final List<UserChangedListener> mListeners = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
+ private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
private boolean mShowLockscreenNotifications;
private boolean mAllowLockscreenRemoteInput;
@@ -157,7 +157,7 @@
break;
case Intent.ACTION_USER_UNLOCKED:
// Start the overview connection to the launcher service
- Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+ mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
break;
case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
final IntentSender intentSender = intent.getParcelableExtra(
@@ -189,7 +189,6 @@
protected NotificationPresenter mPresenter;
protected ContentObserver mLockscreenSettingsObserver;
protected ContentObserver mSettingsObserver;
- private boolean mHideSilentNotificationsOnLockscreen;
@Inject
public NotificationLockscreenUserManagerImpl(Context context,
@@ -199,6 +198,7 @@
Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
Lazy<CommonNotifCollection> commonNotifCollectionLazy,
NotificationClickNotifier clickNotifier,
+ Lazy<OverviewProxyService> overviewProxyServiceLazy,
KeyguardManager keyguardManager,
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
@@ -214,6 +214,7 @@
mVisibilityProviderLazy = visibilityProviderLazy;
mCommonNotifCollectionLazy = commonNotifCollectionLazy;
mClickNotifier = clickNotifier;
+ mOverviewProxyServiceLazy = overviewProxyServiceLazy;
statusBarStateController.addCallback(this);
mLockPatternUtils = new LockPatternUtils(context);
mKeyguardManager = keyguardManager;
@@ -264,12 +265,6 @@
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);
@@ -338,9 +333,6 @@
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/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index c900c5a..4be5a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -43,7 +43,6 @@
import android.view.View;
import android.widget.ImageView;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -89,11 +88,9 @@
private static final String TAG = "NotificationMediaManager";
public static final boolean DEBUG_MEDIA = false;
- private final StatusBarStateController mStatusBarStateController
- = Dependency.get(StatusBarStateController.class);
- private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
- private final KeyguardStateController mKeyguardStateController = Dependency.get(
- KeyguardStateController.class);
+ private final StatusBarStateController mStatusBarStateController;
+ private final SysuiColorExtractor mColorExtractor;
+ private final KeyguardStateController mKeyguardStateController;
private final KeyguardBypassController mKeyguardBypassController;
private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
@@ -179,6 +176,9 @@
NotifCollection notifCollection,
@Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
+ StatusBarStateController statusBarStateController,
+ SysuiColorExtractor colorExtractor,
+ KeyguardStateController keyguardStateController,
DumpManager dumpManager) {
mContext = context;
mMediaArtworkProcessor = mediaArtworkProcessor;
@@ -192,6 +192,9 @@
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
+ mStatusBarStateController = statusBarStateController;
+ mColorExtractor = colorExtractor;
+ mKeyguardStateController = keyguardStateController;
setupNotifPipeline();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index cb13fcf..b5879ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -38,12 +38,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionListener
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.LargeScreenUtils
@@ -69,7 +69,7 @@
private val context: Context,
dumpManager: DumpManager,
configurationController: ConfigurationController
-) : PanelExpansionListener, Dumpable {
+) : ShadeExpansionListener, Dumpable {
companion object {
private const val WAKE_UP_ANIMATION_ENABLED = true
private const val VELOCITY_SCALE = 100f
@@ -338,7 +338,7 @@
/**
* Update blurs when pulling down the shade
*/
- override fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
+ override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
val rawFraction = event.fraction
val tracking = event.tracking
val timestamp = SystemClock.elapsedRealtimeNanos()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d67f94f..f961984 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -189,22 +189,22 @@
viewState.copyFrom(lastViewState);
viewState.height = getIntrinsicHeight();
- viewState.zTranslation = ambientState.getBaseZHeight();
+ viewState.setZTranslation(ambientState.getBaseZHeight());
viewState.clipTopAmount = 0;
if (ambientState.isExpansionChanging() && !ambientState.isOnKeyguard()) {
float expansion = ambientState.getExpansionFraction();
if (ambientState.isBouncerInTransit()) {
- viewState.alpha = aboutToShowBouncerProgress(expansion);
+ viewState.setAlpha(aboutToShowBouncerProgress(expansion));
} else {
- viewState.alpha = ShadeInterpolation.getContentAlpha(expansion);
+ viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
}
} else {
- viewState.alpha = 1f - ambientState.getHideAmount();
+ viewState.setAlpha(1f - ambientState.getHideAmount());
}
viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
viewState.hideSensitive = false;
- viewState.xTranslation = getTranslationX();
+ viewState.setXTranslation(getTranslationX());
viewState.hasItemsInStableShelf = lastViewState.inShelf;
viewState.firstViewInShelf = algorithmState.firstViewInShelf;
if (mNotGoneIndex != -1) {
@@ -230,7 +230,7 @@
}
final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
- viewState.yTranslation = stackEnd - viewState.height;
+ viewState.setYTranslation(stackEnd - viewState.height);
} else {
viewState.hidden = true;
viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -794,7 +794,7 @@
if (iconState == null) {
return;
}
- iconState.alpha = ICON_ALPHA_INTERPOLATOR.getInterpolation(transitionAmount);
+ iconState.setAlpha(ICON_ALPHA_INTERPOLATOR.getInterpolation(transitionAmount));
boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
iconState.hidden = isAppearing
|| (view instanceof ExpandableNotificationRow
@@ -809,12 +809,12 @@
// Fade in icons at shelf start
// This is important for conversation icons, which are badged and need x reset
- iconState.xTranslation = mShelfIcons.getActualPaddingStart();
+ iconState.setXTranslation(mShelfIcons.getActualPaddingStart());
final boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf();
if (stayingInShelf) {
iconState.iconAppearAmount = 1.0f;
- iconState.alpha = 1.0f;
+ iconState.setAlpha(1.0f);
iconState.hidden = false;
}
int backgroundColor = getBackgroundColorWithoutTint();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index bbff046..8222c9d 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 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 @@
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 7807738..59afb18 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 @@
/**
* 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 33e2245..85b522c 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 @@
/**
* 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 @@
}
@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/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 48c6e27..fdad101 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -29,7 +29,6 @@
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -43,7 +42,10 @@
import java.util.ArrayList;
-public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
+/**
+ * View group for the mobile icon in the status bar
+ */
+public class StatusBarMobileView extends BaseStatusBarFrameLayout implements DarkReceiver,
StatusIconDisplayable {
private static final String TAG = "StatusBarMobileView";
@@ -101,11 +103,6 @@
super(context, attrs, defStyleAttr);
}
- public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
@Override
public void getDrawingRect(Rect outRect) {
super.getDrawingRect(outRect);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index f3e74d9..decc70d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -40,7 +40,7 @@
/**
* Start small: StatusBarWifiView will be able to layout from a WifiIconState
*/
-public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkReceiver {
+public class StatusBarWifiView extends BaseStatusBarFrameLayout implements DarkReceiver {
private static final String TAG = "StatusBarWifiView";
/// Used to show etc dots
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 7cd79ca..11e3d17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -26,6 +26,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -130,6 +131,9 @@
NotifCollection notifCollection,
@Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
+ StatusBarStateController statusBarStateController,
+ SysuiColorExtractor colorExtractor,
+ KeyguardStateController keyguardStateController,
DumpManager dumpManager) {
return new NotificationMediaManager(
context,
@@ -142,6 +146,9 @@
notifCollection,
mainExecutor,
mediaDataManager,
+ statusBarStateController,
+ colorExtractor,
+ keyguardStateController,
dumpManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
index 1be4c04..b5c7ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification;
-import android.annotation.Nullable;
import android.util.ArraySet;
import androidx.annotation.VisibleForTesting;
@@ -25,7 +24,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
@@ -43,7 +41,6 @@
private boolean mLastDynamicUnlocked;
private boolean mCacheInvalid;
- @Nullable private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Inject
DynamicPrivacyController(NotificationLockscreenUserManager notificationLockscreenUserManager,
@@ -100,7 +97,7 @@
* contents aren't revealed yet?
*/
public boolean isInLockedDownShade() {
- if (!isStatusBarKeyguardShowing() || !mKeyguardStateController.isMethodSecure()) {
+ if (!mKeyguardStateController.isShowing() || !mKeyguardStateController.isMethodSecure()) {
return false;
}
int state = mStateController.getState();
@@ -113,16 +110,7 @@
return true;
}
- private boolean isStatusBarKeyguardShowing() {
- return mStatusBarKeyguardViewManager != null && mStatusBarKeyguardViewManager.isShowing();
- }
-
- public void setStatusBarKeyguardViewManager(
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- }
-
public interface Listener {
void onDynamicPrivacyChanged();
}
-}
\ No newline at end of file
+}
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 59022c0f..0a5e986 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 @@
* 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 @@
CommandQueue commandQueue,
@UiBackground Executor uiBgExecutor,
KeyguardStateController keyguardStateController) {
- super(context);
+ mContext = context;
mCommandQueue = commandQueue;
mUiBgExecutor = uiBgExecutor;
mKeyguardStateController = keyguardStateController;
@@ -289,7 +290,7 @@
.setComponent(aiaComponent)
.setAction(Intent.ACTION_VIEW)
.addCategory(Intent.CATEGORY_BROWSABLE)
- .addCategory("unique:" + System.currentTimeMillis())
+ .setIdentifier("unique:" + System.currentTimeMillis())
.putExtra(Intent.EXTRA_PACKAGE_NAME, appInfo.packageName)
.putExtra(
Intent.EXTRA_VERSION_CODE,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 126a986..7242506 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -21,6 +21,8 @@
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionListener
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -28,8 +30,6 @@
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import javax.inject.Inject
@@ -42,7 +42,7 @@
private val bypassController: KeyguardBypassController,
private val dozeParameters: DozeParameters,
private val screenOffAnimationController: ScreenOffAnimationController
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener {
+) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener {
private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
"notificationVisibility") {
@@ -293,7 +293,7 @@
this.state = newState
}
- override fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
+ override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
val collapsedEnough = event.fraction <= 0.9f
if (collapsedEnough != this.collapsedEnoughToHide) {
val couldShowPulsingHuns = canShowPulsingHuns
@@ -426,4 +426,4 @@
*/
@JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {}
}
-}
\ No newline at end of file
+}
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 ccf6fec..8f3eb4f 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 @@
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 204a494..8625cdb 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 @@
" 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/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 6e76691..93146f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -407,7 +407,10 @@
mLogger.logGroupInflationTookTooLong(group);
return false;
}
- if (mInflatingNotifs.contains(group.getSummary())) {
+ // Only delay release if the summary is not inflated.
+ // TODO(253454977): Once we ensure that all other pipeline filtering and pruning has been
+ // done by this point, we can revert back to checking for mInflatingNotifs.contains(...)
+ if (!isInflated(group.getSummary())) {
mLogger.logDelayingGroupRelease(group, group.getSummary());
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index a7719d3..e71d80c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -18,6 +18,8 @@
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+
import android.os.SystemClock;
import android.service.notification.NotificationStats;
@@ -70,6 +72,8 @@
dismissalSurface = NotificationStats.DISMISSAL_PEEK;
} else if (mStatusBarStateController.isDozing()) {
dismissalSurface = NotificationStats.DISMISSAL_AOD;
+ } else if (mStatusBarStateController.getState() == KEYGUARD) {
+ dismissalSurface = NotificationStats.DISMISSAL_LOCKSCREEN;
}
return new DismissedByUserStats(
dismissalSurface,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
index b6278d1..fde4ecb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.render
+import javax.inject.Inject
+
/** An interface by which the pipeline can make updates to the notification root view. */
interface NotifStackController {
/** Provides stats about the list of notifications attached to the shade */
@@ -42,6 +44,6 @@
* methods, rather than forcing us to add no-op implementations in their implementation every time
* a method is added.
*/
-open class DefaultNotifStackController : NotifStackController {
+open class DefaultNotifStackController @Inject constructor() : NotifStackController {
override fun setNotifStats(stats: NotifStats) {}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 1aa0295..8e646a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -19,6 +19,8 @@
import android.service.notification.StatusBarNotification
import com.android.systemui.ForegroundServiceNotificationListener
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationListener
@@ -36,6 +38,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
+import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -68,6 +71,8 @@
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
private val bubblesOptional: Optional<Bubbles>,
private val fgsNotifListener: ForegroundServiceNotificationListener,
+ private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
+ private val featureFlags: FeatureFlags
) : NotificationsController {
override fun initialize(
@@ -107,6 +112,9 @@
peopleSpaceWidgetManager.attach(notificationListener)
fgsNotifListener.init()
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) {
+ memoryMonitor.get().init()
+ }
}
// TODO: Convert all functions below this line into listeners instead of public methods
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 6f41425..9a7610d 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 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 d1feaa0..5dbec8d 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 @@
"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 c956a2e..e6dbcee 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 @@
@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 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 fun readShowSilentNotificationSetting() {
val showSilentNotifs =
secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
- true, UserHandle.USER_CURRENT)
+ false, UserHandle.USER_CURRENT)
hideSilentNotificationsOnLockscreen = !showSilentNotifs
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 3292a8f..6cf4bf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -37,6 +37,19 @@
boolean shouldHeadsUp(NotificationEntry entry);
/**
+ * Returns the value of whether this entry should peek (from shouldHeadsUp(entry)), but only
+ * optionally logs the status.
+ *
+ * This method should be used in cases where the caller needs to check whether a notification
+ * qualifies for a heads up, but is not necessarily guaranteed to make the heads-up happen.
+ *
+ * @param entry the entry to check
+ * @param log whether or not to log the results of this check
+ * @return true if the entry should heads up, false otherwise
+ */
+ boolean checkHeadsUp(NotificationEntry entry, boolean log);
+
+ /**
* Whether the notification should appear as a bubble with a fly-out on top of the screen.
*
* @param entry the entry to check
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 558fd62..c5a6921 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -137,11 +137,11 @@
public boolean shouldBubbleUp(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
- if (!canAlertCommon(entry)) {
+ if (!canAlertCommon(entry, true)) {
return false;
}
- if (!canAlertAwakeCommon(entry)) {
+ if (!canAlertAwakeCommon(entry, true)) {
return false;
}
@@ -163,10 +163,15 @@
@Override
public boolean shouldHeadsUp(NotificationEntry entry) {
+ return checkHeadsUp(entry, true);
+ }
+
+ @Override
+ public boolean checkHeadsUp(NotificationEntry entry, boolean log) {
if (mStatusBarStateController.isDozing()) {
- return shouldHeadsUpWhenDozing(entry);
+ return shouldHeadsUpWhenDozing(entry, log);
} else {
- return shouldHeadsUpWhenAwake(entry);
+ return shouldHeadsUpWhenAwake(entry, log);
}
}
@@ -263,61 +268,61 @@
}
}
- private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
+ private boolean shouldHeadsUpWhenAwake(NotificationEntry entry, boolean log) {
StatusBarNotification sbn = entry.getSbn();
if (!mUseHeadsUp) {
- mLogger.logNoHeadsUpFeatureDisabled();
+ if (log) mLogger.logNoHeadsUpFeatureDisabled();
return false;
}
- if (!canAlertCommon(entry)) {
+ if (!canAlertCommon(entry, log)) {
return false;
}
- if (!canAlertHeadsUpCommon(entry)) {
+ if (!canAlertHeadsUpCommon(entry, log)) {
return false;
}
- if (!canAlertAwakeCommon(entry)) {
+ if (!canAlertAwakeCommon(entry, log)) {
return false;
}
if (isSnoozedPackage(sbn)) {
- mLogger.logNoHeadsUpPackageSnoozed(entry);
+ if (log) mLogger.logNoHeadsUpPackageSnoozed(entry);
return false;
}
boolean inShade = mStatusBarStateController.getState() == SHADE;
if (entry.isBubble() && inShade) {
- mLogger.logNoHeadsUpAlreadyBubbled(entry);
+ if (log) mLogger.logNoHeadsUpAlreadyBubbled(entry);
return false;
}
if (entry.shouldSuppressPeek()) {
- mLogger.logNoHeadsUpSuppressedByDnd(entry);
+ if (log) mLogger.logNoHeadsUpSuppressedByDnd(entry);
return false;
}
if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
- mLogger.logNoHeadsUpNotImportant(entry);
+ if (log) mLogger.logNoHeadsUpNotImportant(entry);
return false;
}
boolean inUse = mPowerManager.isScreenOn() && !isDreaming();
if (!inUse) {
- mLogger.logNoHeadsUpNotInUse(entry);
+ if (log) mLogger.logNoHeadsUpNotInUse(entry);
return false;
}
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) {
- mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
+ if (log) mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
return false;
}
}
- mLogger.logHeadsUp(entry);
+ if (log) mLogger.logHeadsUp(entry);
return true;
}
@@ -328,37 +333,37 @@
* @param entry the entry to check
* @return true if the entry should ambient pulse, false otherwise
*/
- private boolean shouldHeadsUpWhenDozing(NotificationEntry entry) {
+ private boolean shouldHeadsUpWhenDozing(NotificationEntry entry, boolean log) {
if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
- mLogger.logNoPulsingSettingDisabled(entry);
+ if (log) mLogger.logNoPulsingSettingDisabled(entry);
return false;
}
if (mBatteryController.isAodPowerSave()) {
- mLogger.logNoPulsingBatteryDisabled(entry);
+ if (log) mLogger.logNoPulsingBatteryDisabled(entry);
return false;
}
- if (!canAlertCommon(entry)) {
- mLogger.logNoPulsingNoAlert(entry);
+ if (!canAlertCommon(entry, log)) {
+ if (log) mLogger.logNoPulsingNoAlert(entry);
return false;
}
- if (!canAlertHeadsUpCommon(entry)) {
- mLogger.logNoPulsingNoAlert(entry);
+ if (!canAlertHeadsUpCommon(entry, log)) {
+ if (log) mLogger.logNoPulsingNoAlert(entry);
return false;
}
if (entry.shouldSuppressAmbient()) {
- mLogger.logNoPulsingNoAmbientEffect(entry);
+ if (log) mLogger.logNoPulsingNoAmbientEffect(entry);
return false;
}
if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
- mLogger.logNoPulsingNotImportant(entry);
+ if (log) mLogger.logNoPulsingNotImportant(entry);
return false;
}
- mLogger.logPulsing(entry);
+ if (log) mLogger.logPulsing(entry);
return true;
}
@@ -366,18 +371,22 @@
* Common checks between regular & AOD heads up and bubbles.
*
* @param entry the entry to check
+ * @param log whether or not to log the results of these checks
* @return true if these checks pass, false if the notification should not alert
*/
- private boolean canAlertCommon(NotificationEntry entry) {
+ private boolean canAlertCommon(NotificationEntry entry, boolean log) {
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressInterruptions(entry)) {
- mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ false);
+ if (log) {
+ mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i),
+ /* awake */ false);
+ }
return false;
}
}
if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) {
- mLogger.keyguardHideNotification(entry);
+ if (log) mLogger.keyguardHideNotification(entry);
return false;
}
@@ -388,19 +397,20 @@
* Common checks for heads up notifications on regular and AOD displays.
*
* @param entry the entry to check
+ * @param log whether or not to log the results of these checks
* @return true if these checks pass, false if the notification should not alert
*/
- private boolean canAlertHeadsUpCommon(NotificationEntry entry) {
+ private boolean canAlertHeadsUpCommon(NotificationEntry entry, boolean log) {
StatusBarNotification sbn = entry.getSbn();
// Don't alert notifications that are suppressed due to group alert behavior
if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
- mLogger.logNoAlertingGroupAlertBehavior(entry);
+ if (log) mLogger.logNoAlertingGroupAlertBehavior(entry);
return false;
}
if (entry.hasJustLaunchedFullScreenIntent()) {
- mLogger.logNoAlertingRecentFullscreen(entry);
+ if (log) mLogger.logNoAlertingRecentFullscreen(entry);
return false;
}
@@ -413,12 +423,14 @@
* @param entry the entry to check
* @return true if these checks pass, false if the notification should not alert
*/
- private boolean canAlertAwakeCommon(NotificationEntry entry) {
+ private boolean canAlertAwakeCommon(NotificationEntry entry, boolean log) {
StatusBarNotification sbn = entry.getSbn();
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) {
- mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true);
+ if (log) {
+ mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true);
+ }
return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
new file mode 100644
index 0000000..832a739
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -0,0 +1,41 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+/** Describes usage of a notification. */
+data class NotificationMemoryUsage(
+ val packageName: String,
+ val notificationId: String,
+ val objectUsage: NotificationObjectUsage,
+)
+
+/**
+ * Describes current memory usage of a [android.app.Notification] object.
+ *
+ * The values are in bytes.
+ */
+data class NotificationObjectUsage(
+ val smallIcon: Int,
+ val largeIcon: Int,
+ val extras: Int,
+ val style: String?,
+ val styleIcon: Int,
+ val bigPicture: Int,
+ val extender: Int,
+ val hasCustomView: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
new file mode 100644
index 0000000..958978e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -0,0 +1,243 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.Person
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import android.util.Log
+import androidx.annotation.WorkerThread
+import androidx.core.util.contains
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** This class monitors and logs current Notification memory use. */
+@SysUISingleton
+class NotificationMemoryMonitor
+@Inject
+constructor(
+ val notificationPipeline: NotifPipeline,
+ val dumpManager: DumpManager,
+) : Dumpable {
+
+ companion object {
+ private const val TAG = "NotificationMemMonitor"
+ private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
+ private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
+ private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
+ private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
+ private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+ }
+
+ fun init() {
+ Log.d(TAG, "NotificationMemoryMonitor initialized.")
+ dumpManager.registerDumpable(javaClass.simpleName, this)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ currentNotificationMemoryUse().forEach { use -> pw.println(use.toString()) }
+ }
+
+ @WorkerThread
+ fun currentNotificationMemoryUse(): List<NotificationMemoryUsage> {
+ return notificationMemoryUse(notificationPipeline.allNotifs)
+ }
+
+ /** Returns a list of memory use entries for currently shown notifications. */
+ @WorkerThread
+ fun notificationMemoryUse(
+ notifications: Collection<NotificationEntry>
+ ): List<NotificationMemoryUsage> {
+ return notifications
+ .asSequence()
+ .map { entry ->
+ val packageName = entry.sbn.packageName
+ val notificationObjectUsage =
+ computeNotificationObjectUse(entry.sbn.notification, hashSetOf())
+ NotificationMemoryUsage(
+ packageName,
+ NotificationUtils.logKey(entry.sbn.key),
+ notificationObjectUsage
+ )
+ }
+ .toList()
+ }
+
+ /**
+ * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
+ * inspect Bitmaps in the object and provide summary of memory usage.
+ */
+ private fun computeNotificationObjectUse(
+ notification: Notification,
+ seenBitmaps: HashSet<Int>
+ ): NotificationObjectUsage {
+ val extras = notification.extras
+ val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
+ val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
+
+ // Collect memory usage of extra styles
+
+ // Big Picture
+ val bigPictureIconUse =
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps) +
+ computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
+ val bigPictureUse =
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
+
+ // People
+ val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
+ val peopleUse =
+ peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
+
+ // Calling
+ val callingPersonUse =
+ computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
+ val verificationIconUse =
+ computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
+
+ // Messages
+ val messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_MESSAGES)
+ )
+ val messagesUse =
+ messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+ val historicMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
+ )
+ val historyicMessagesUse =
+ historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+
+ // Extenders
+ val carExtender = extras.getBundle(CAR_EXTENSIONS)
+ val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
+ val carExtenderIcon =
+ computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
+
+ val tvExtender = extras.getBundle(TV_EXTENSIONS)
+ val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
+
+ val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
+ val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
+ val wearExtenderBackground =
+ computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
+
+ val style = notification.notificationStyle
+ val hasCustomView = notification.contentView != null || notification.bigContentView != null
+ val extrasSize = computeBundleSize(extras)
+
+ return NotificationObjectUsage(
+ smallIconUse,
+ largeIconUse,
+ extrasSize,
+ style?.simpleName,
+ bigPictureIconUse +
+ peopleUse +
+ callingPersonUse +
+ verificationIconUse +
+ messagesUse +
+ historyicMessagesUse,
+ bigPictureUse,
+ carExtenderSize +
+ carExtenderIcon +
+ tvExtenderSize +
+ wearExtenderSize +
+ wearExtenderBackground,
+ hasCustomView
+ )
+ }
+
+ /**
+ * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
+ * bitmaps). Can be slow.
+ */
+ private fun computeBundleSize(extras: Bundle): Int {
+ val parcel = Parcel.obtain()
+ try {
+ extras.writeToParcel(parcel, 0)
+ return parcel.dataSize()
+ } finally {
+ parcel.recycle()
+ }
+ }
+
+ /**
+ * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
+ * if the key does not exist in extras.
+ */
+ private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
+ return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
+ is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
+ is Icon -> computeIconUse(parcelable, seenBitmaps)
+ is Person -> computeIconUse(parcelable.icon, seenBitmaps)
+ else -> 0
+ }
+ }
+
+ /**
+ * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
+ * defined via Uri or a resource.
+ *
+ * @return memory usage in bytes or 0 if the icon is Uri/Resource based
+ */
+ private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+ when (icon?.type) {
+ Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+ Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+ Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
+ else -> 0
+ }
+
+ /**
+ * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
+ * seenBitmaps set, this method returns 0 to avoid double counting.
+ *
+ * @return memory usage of the bitmap in bytes
+ */
+ private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
+ val refId = System.identityHashCode(bitmap)
+ if (seenBitmaps?.contains(refId) == true) {
+ return 0
+ }
+
+ seenBitmaps?.add(refId)
+ return bitmap.allocationByteCount
+ }
+
+ private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
+ val refId = System.identityHashCode(icon.dataBytes)
+ if (seenBitmaps.contains(refId)) {
+ return 0
+ }
+
+ seenBitmaps.add(refId)
+ return icon.dataLength
+ }
+}
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 7c41800..d626c18 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.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -64,7 +65,7 @@
* 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 @@
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 6138265..087dc71 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
@@ -1477,6 +1477,20 @@
}
}
+ /**
+ * Sets the alpha on the content, while leaving the background of the row itself as is.
+ *
+ * @param alpha alpha value to apply to the notification content
+ */
+ public void setContentAlpha(float alpha) {
+ for (NotificationContentView l : mLayouts) {
+ l.setAlpha(alpha);
+ }
+ if (mChildrenContainer != null) {
+ mChildrenContainer.setAlpha(alpha);
+ }
+ }
+
public void setIsLowPriority(boolean isLowPriority) {
mIsLowPriority = isLowPriority;
mPrivateLayout.setIsLowPriority(isLowPriority);
@@ -3362,7 +3376,7 @@
private void handleFixedTranslationZ(ExpandableNotificationRow row) {
if (row.hasExpandingChild()) {
- zTranslation = row.getTranslationZ();
+ setZTranslation(row.getTranslationZ());
clipTopAmount = row.getClipTopAmount();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 1e09b8a..38f0c55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -621,12 +621,12 @@
// initialize with the default values of the view
mViewState.height = getIntrinsicHeight();
mViewState.gone = getVisibility() == View.GONE;
- mViewState.alpha = 1f;
+ mViewState.setAlpha(1f);
mViewState.notGoneIndex = -1;
- mViewState.xTranslation = getTranslationX();
+ mViewState.setXTranslation(getTranslationX());
mViewState.hidden = false;
- mViewState.scaleX = getScaleX();
- mViewState.scaleY = getScaleY();
+ mViewState.setScaleX(getScaleX());
+ mViewState.setScaleY(getScaleY());
mViewState.inShelf = false;
mViewState.headsUpIsVisible = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index df81c0e..8de0365 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1986,6 +1986,25 @@
public void setRemoteInputVisible(boolean remoteInputVisible) {
mRemoteInputVisible = remoteInputVisible;
setClipChildren(!remoteInputVisible);
+ setActionsImportanceForAccessibility(
+ remoteInputVisible ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
+
+ private void setActionsImportanceForAccessibility(int mode) {
+ if (mExpandedChild != null) {
+ setActionsImportanceForAccessibility(mode, mExpandedChild);
+ }
+ if (mHeadsUpChild != null) {
+ setActionsImportanceForAccessibility(mode, mHeadsUpChild);
+ }
+ }
+
+ private void setActionsImportanceForAccessibility(int mode, View child) {
+ View actionsCandidate = child.findViewById(com.android.internal.R.id.actions);
+ if (actionsCandidate != null) {
+ actionsCandidate.setImportantForAccessibility(mode);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index ce465bc..2719dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -44,7 +44,8 @@
import javax.inject.Inject;
/**
- * A global state to track all input states for the algorithm.
+ * Global state to track all input states for
+ * {@link com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm}.
*/
@SysUISingleton
public class AmbientState implements Dumpable {
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 d77e03f..7b23a56 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
@@ -583,24 +583,26 @@
ExpandableViewState childState = child.getViewState();
int intrinsicHeight = child.getIntrinsicHeight();
childState.height = intrinsicHeight;
- childState.yTranslation = yPosition + launchTransitionCompensation;
+ childState.setYTranslation(yPosition + launchTransitionCompensation);
childState.hidden = false;
// When the group is expanded, the children cast the shadows rather than the parent
// so use the parent's elevation here.
- childState.zTranslation =
- (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications)
- ? parentState.zTranslation
- : 0;
+ if (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications) {
+ childState.setZTranslation(parentState.getZTranslation());
+ } else {
+ childState.setZTranslation(0);
+ }
childState.dimmed = parentState.dimmed;
childState.hideSensitive = parentState.hideSensitive;
childState.belowSpeedBump = parentState.belowSpeedBump;
childState.clipTopAmount = 0;
- childState.alpha = 0;
+ childState.setAlpha(0);
if (i < firstOverflowIndex) {
- childState.alpha = showingAsLowPriority() ? expandFactor : 1.0f;
+ childState.setAlpha(showingAsLowPriority() ? expandFactor : 1.0f);
} else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
- childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
- childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
+ childState.setAlpha(
+ (mActualHeight - childState.getYTranslation()) / childState.height);
+ childState.setAlpha(Math.max(0.0f, Math.min(1.0f, childState.getAlpha())));
}
childState.location = parentState.location;
childState.inShelf = parentState.inShelf;
@@ -621,13 +623,16 @@
if (mirrorView.getVisibility() == GONE) {
mirrorView = alignView;
}
- mGroupOverFlowState.alpha = mirrorView.getAlpha();
- mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset(
+ mGroupOverFlowState.setAlpha(mirrorView.getAlpha());
+ float yTranslation = mGroupOverFlowState.getYTranslation()
+ + NotificationUtils.getRelativeYOffset(
mirrorView, overflowView);
+ mGroupOverFlowState.setYTranslation(yTranslation);
}
} else {
- mGroupOverFlowState.yTranslation += mNotificationHeaderMargin;
- mGroupOverFlowState.alpha = 0.0f;
+ mGroupOverFlowState.setYTranslation(
+ mGroupOverFlowState.getYTranslation() + mNotificationHeaderMargin);
+ mGroupOverFlowState.setAlpha(0.0f);
}
}
if (mNotificationHeader != null) {
@@ -635,11 +640,11 @@
mHeaderViewState = new ViewState();
}
mHeaderViewState.initFrom(mNotificationHeader);
- mHeaderViewState.zTranslation = childrenExpandedAndNotAnimating
- ? parentState.zTranslation
- : 0;
- mHeaderViewState.yTranslation = mCurrentHeaderTranslation;
- mHeaderViewState.alpha = mHeaderVisibleAmount;
+ mHeaderViewState.setZTranslation(childrenExpandedAndNotAnimating
+ ? parentState.getZTranslation()
+ : 0);
+ mHeaderViewState.setYTranslation(mCurrentHeaderTranslation);
+ mHeaderViewState.setAlpha(mHeaderVisibleAmount);
// The hiding is done automatically by the alpha, otherwise we'll pick it up again
// in the next frame with the initFrom call above and have an invisible header
mHeaderViewState.hidden = false;
@@ -711,14 +716,14 @@
// layout the divider
View divider = mDividers.get(i);
tmpState.initFrom(divider);
- tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
- float alpha = mChildrenExpanded && viewState.alpha != 0 ? mDividerAlpha : 0;
- if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
+ tmpState.setYTranslation(viewState.getYTranslation() - mDividerHeight);
+ float alpha = mChildrenExpanded && viewState.getAlpha() != 0 ? mDividerAlpha : 0;
+ if (mUserLocked && !showingAsLowPriority() && viewState.getAlpha() != 0) {
alpha = NotificationUtils.interpolate(0, mDividerAlpha,
- Math.min(viewState.alpha, expandFraction));
+ Math.min(viewState.getAlpha(), expandFraction));
}
tmpState.hidden = !dividersVisible;
- tmpState.alpha = alpha;
+ tmpState.setAlpha(alpha);
tmpState.applyToView(divider);
// There is no fake shadow to be drawn on the children
child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
@@ -790,24 +795,24 @@
// layout the divider
View divider = mDividers.get(i);
tmpState.initFrom(divider);
- tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
- float alpha = mChildrenExpanded && viewState.alpha != 0 ? mDividerAlpha : 0;
- if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
+ tmpState.setYTranslation(viewState.getYTranslation() - mDividerHeight);
+ float alpha = mChildrenExpanded && viewState.getAlpha() != 0 ? mDividerAlpha : 0;
+ if (mUserLocked && !showingAsLowPriority() && viewState.getAlpha() != 0) {
alpha = NotificationUtils.interpolate(0, mDividerAlpha,
- Math.min(viewState.alpha, expandFraction));
+ Math.min(viewState.getAlpha(), expandFraction));
}
tmpState.hidden = !dividersVisible;
- tmpState.alpha = alpha;
+ tmpState.setAlpha(alpha);
tmpState.animateTo(divider, properties);
// There is no fake shadow to be drawn on the children
child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
}
if (mOverflowNumber != null) {
if (mNeverAppliedGroupState) {
- float alpha = mGroupOverFlowState.alpha;
- mGroupOverFlowState.alpha = 0;
+ float alpha = mGroupOverFlowState.getAlpha();
+ mGroupOverFlowState.setAlpha(0);
mGroupOverFlowState.applyToView(mOverflowNumber);
- mGroupOverFlowState.alpha = alpha;
+ mGroupOverFlowState.setAlpha(alpha);
mNeverAppliedGroupState = false;
}
mGroupOverFlowState.animateTo(mOverflowNumber, properties);
@@ -949,7 +954,7 @@
child.setAlpha(start);
ViewState viewState = new ViewState();
viewState.initFrom(child);
- viewState.alpha = target;
+ viewState.setAlpha(target);
ALPHA_FADE_IN.setDelay(i * 50);
viewState.animateTo(child, ALPHA_FADE_IN);
}
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 6821b14..55c577f 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 @@
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;
}
@@ -3188,7 +3192,7 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
- return viewState.yTranslation + viewState.height
+ return viewState.getYTranslation() + viewState.height
>= mAmbientState.getMaxHeadsUpTranslation();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 843a9ff..5c09d61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -56,12 +56,13 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -178,6 +179,7 @@
@Nullable private Boolean mHistoryEnabled;
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
+ private final FeatureFlags mFeatureFlags;
private View mLongPressedView;
@@ -639,7 +641,8 @@
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
- NotificationStackSizeCalculator notificationStackSizeCalculator) {
+ NotificationStackSizeCalculator notificationStackSizeCalculator,
+ FeatureFlags featureFlags) {
mStackStateLogger = stackLogger;
mLogger = logger;
mAllowLongPress = allowLongPress;
@@ -675,6 +678,7 @@
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
mShadeController = shadeController;
+ mFeatureFlags = featureFlags;
updateResources();
}
@@ -739,9 +743,7 @@
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
- mFadeNotificationsOnDismiss = // TODO: this should probably be injected directly
- mResources.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
-
+ mFadeNotificationsOnDismiss = mFeatureFlags.isEnabled(Flags.NOTIFICATION_DISMISSAL_FADE);
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 2d2fbe5..ee57411 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -185,6 +185,13 @@
return false;
}
+ @Override
+ protected void updateSwipeProgressAlpha(View animView, float alpha) {
+ if (animView instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) animView).setContentAlpha(alpha);
+ }
+ }
+
@VisibleForTesting
protected void handleMenuRowSwipe(MotionEvent ev, View animView, float velocity,
NotificationMenuRowPlugin menuRow) {
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 eeed070..0502159 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
@@ -134,23 +134,23 @@
if (isHunGoingToShade) {
// Keep 100% opacity for heads up notification going to shade.
- viewState.alpha = 1f;
+ viewState.setAlpha(1f);
} else if (ambientState.isOnKeyguard()) {
// Adjust alpha for wakeup to lockscreen.
- viewState.alpha = 1f - ambientState.getHideAmount();
+ viewState.setAlpha(1f - ambientState.getHideAmount());
} else if (ambientState.isExpansionChanging()) {
// Adjust alpha for shade open & close.
float expansion = ambientState.getExpansionFraction();
- viewState.alpha = ambientState.isBouncerInTransit()
+ viewState.setAlpha(ambientState.isBouncerInTransit()
? BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion)
- : ShadeInterpolation.getContentAlpha(expansion);
+ : ShadeInterpolation.getContentAlpha(expansion));
}
// For EmptyShadeView if on keyguard, we need to control the alpha to create
// a nice transition when the user is dragging down the notification panel.
if (view instanceof EmptyShadeView && ambientState.isOnKeyguard()) {
final float fractionToShade = ambientState.getFractionToShade();
- viewState.alpha = ShadeInterpolation.getContentAlpha(fractionToShade);
+ viewState.setAlpha(ShadeInterpolation.getContentAlpha(fractionToShade));
}
NotificationShelf shelf = ambientState.getShelf();
@@ -166,10 +166,10 @@
continue;
}
- final float shelfTop = shelfState.yTranslation;
- final float viewTop = viewState.yTranslation;
+ final float shelfTop = shelfState.getYTranslation();
+ final float viewTop = viewState.getYTranslation();
if (viewTop >= shelfTop) {
- viewState.alpha = 0;
+ viewState.setAlpha(0);
}
}
}
@@ -277,7 +277,7 @@
if (!child.mustStayOnScreen() || state.headsUpIsVisible) {
clipStart = Math.max(drawStart, clipStart);
}
- float newYTranslation = state.yTranslation;
+ float newYTranslation = state.getYTranslation();
float newHeight = state.height;
float newNotificationEnd = newYTranslation + newHeight;
boolean isHeadsUp = (child instanceof ExpandableNotificationRow) && child.isPinned();
@@ -322,7 +322,8 @@
childViewState.hideSensitive = hideSensitive;
boolean isActivatedChild = activatedChild == child;
if (dimmed && isActivatedChild) {
- childViewState.zTranslation += 2.0f * ambientState.getZDistanceBetweenElements();
+ childViewState.setZTranslation(childViewState.getZTranslation()
+ + 2.0f * ambientState.getZDistanceBetweenElements());
}
}
}
@@ -416,12 +417,19 @@
}
/**
+ * 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())) {
@@ -447,7 +455,7 @@
* @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) {
@@ -527,12 +535,12 @@
// Must set viewState.yTranslation _before_ use.
// Incoming views have yTranslation=0 by default.
- viewState.yTranslation = algorithmState.mCurrentYPosition;
+ viewState.setYTranslation(algorithmState.mCurrentYPosition);
+ float viewEnd = viewState.getYTranslation() + viewState.height + ambientState.getStackY();
maybeUpdateHeadsUpIsVisible(viewState, ambientState.isShadeExpanded(),
- view.mustStayOnScreen(), /* topVisible */ viewState.yTranslation >= 0,
- /* viewEnd */ viewState.yTranslation + viewState.height + ambientState.getStackY(),
- /* hunMax */ ambientState.getMaxHeadsUpTranslation()
+ view.mustStayOnScreen(), /* topVisible */ viewState.getYTranslation() >= 0,
+ viewEnd, /* hunMax */ ambientState.getMaxHeadsUpTranslation()
);
if (view instanceof FooterView) {
final boolean shadeClosed = !ambientState.isShadeExpanded();
@@ -552,7 +560,7 @@
if (view instanceof EmptyShadeView) {
float fullHeight = ambientState.getLayoutMaxHeight() + mMarginBottom
- ambientState.getStackY();
- viewState.yTranslation = (fullHeight - getMaxAllowedChildHeight(view)) / 2f;
+ viewState.setYTranslation((fullHeight - getMaxAllowedChildHeight(view)) / 2f);
} else if (view != ambientState.getTrackedHeadsUpRow()) {
if (ambientState.isExpansionChanging()) {
// We later update shelf state, then hide views below the shelf.
@@ -591,13 +599,13 @@
+ mPaddingBetweenElements;
setLocation(view.getViewState(), algorithmState.mCurrentYPosition, i);
- viewState.yTranslation += ambientState.getStackY();
+ viewState.setYTranslation(viewState.getYTranslation() + ambientState.getStackY());
}
@VisibleForTesting
void updateViewWithShelf(ExpandableView view, ExpandableViewState viewState, float shelfStart) {
- viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
- if (viewState.yTranslation >= shelfStart) {
+ viewState.setYTranslation(Math.min(viewState.getYTranslation(), shelfStart));
+ if (viewState.getYTranslation() >= shelfStart) {
viewState.hidden = !view.isExpandAnimationRunning()
&& !view.hasExpandingChild();
viewState.inShelf = true;
@@ -690,9 +698,9 @@
if (trackedHeadsUpRow != null) {
ExpandableViewState childState = trackedHeadsUpRow.getViewState();
if (childState != null) {
- float endPosition = childState.yTranslation - ambientState.getStackTranslation();
- childState.yTranslation = MathUtils.lerp(
- headsUpTranslation, endPosition, ambientState.getAppearFraction());
+ float endPos = childState.getYTranslation() - ambientState.getStackTranslation();
+ childState.setYTranslation(MathUtils.lerp(
+ headsUpTranslation, endPos, ambientState.getAppearFraction()));
}
}
@@ -712,7 +720,7 @@
childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
}
boolean isTopEntry = topHeadsUpEntry == row;
- float unmodifiedEndLocation = childState.yTranslation + childState.height;
+ float unmodifiedEndLocation = childState.getYTranslation() + childState.height;
if (mIsExpanded) {
if (row.mustStayOnScreen() && !childState.headsUpIsVisible
&& !row.showingPulsing()) {
@@ -727,13 +735,14 @@
}
}
if (row.isPinned()) {
- childState.yTranslation = Math.max(childState.yTranslation, headsUpTranslation);
+ childState.setYTranslation(
+ Math.max(childState.getYTranslation(), headsUpTranslation));
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
childState.hidden = false;
ExpandableViewState topState =
topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState();
if (topState != null && !isTopEntry && (!mIsExpanded
- || unmodifiedEndLocation > topState.yTranslation + topState.height)) {
+ || unmodifiedEndLocation > topState.getYTranslation() + topState.height)) {
// Ensure that a headsUp doesn't vertically extend further than the heads-up at
// the top most z-position
childState.height = row.getIntrinsicHeight();
@@ -745,11 +754,12 @@
// heads up show full of row's content and any scroll y indicate that the
// translationY need to move up the HUN.
if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) {
- childState.yTranslation -= ambientState.getScrollY();
+ childState.setYTranslation(
+ childState.getYTranslation() - ambientState.getScrollY());
}
}
if (row.isHeadsUpAnimatingAway()) {
- childState.yTranslation = Math.max(childState.yTranslation, mHeadsUpInset);
+ childState.setYTranslation(Math.max(childState.getYTranslation(), mHeadsUpInset));
childState.hidden = false;
}
}
@@ -765,13 +775,13 @@
ExpandableViewState viewState) {
final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
- viewState.yTranslation);
+ viewState.getYTranslation());
// Transition from collapsed pinned state to fully expanded state
// when the pinned HUN approaches its actual location (when scrolling back to top).
- final float distToRealY = newTranslation - viewState.yTranslation;
+ final float distToRealY = newTranslation - viewState.getYTranslation();
viewState.height = (int) Math.max(viewState.height - distToRealY, collapsedHeight);
- viewState.yTranslation = newTranslation;
+ viewState.setYTranslation(newTranslation);
}
// Pin HUN to bottom of expanded QS
@@ -784,10 +794,10 @@
maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition);
final float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
- final float newTranslation = Math.min(childState.yTranslation, bottomPosition);
+ final float newTranslation = Math.min(childState.getYTranslation(), bottomPosition);
childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation
- newTranslation);
- childState.yTranslation = newTranslation;
+ childState.setYTranslation(newTranslation);
// Animate pinned HUN bottom corners to and from original roundness.
final float originalCornerRadius =
@@ -859,17 +869,17 @@
float baseZ = ambientState.getBaseZHeight();
if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
&& !ambientState.isDozingAndNotPulsing(child)
- && childViewState.yTranslation < ambientState.getTopPadding()
+ && childViewState.getYTranslation() < ambientState.getTopPadding()
+ ambientState.getStackTranslation()) {
if (childrenOnTop != 0.0f) {
childrenOnTop++;
} else {
float overlap = ambientState.getTopPadding()
- + ambientState.getStackTranslation() - childViewState.yTranslation;
+ + ambientState.getStackTranslation() - childViewState.getYTranslation();
childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
}
- childViewState.zTranslation = baseZ
- + childrenOnTop * zDistanceBetweenElements;
+ childViewState.setZTranslation(baseZ
+ + childrenOnTop * zDistanceBetweenElements);
} else if (shouldElevateHun) {
// In case this is a new view that has never been measured before, we don't want to
// elevate if we are currently expanded more then the notification
@@ -878,25 +888,28 @@
float shelfStart = ambientState.getInnerHeight()
- shelfHeight + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
- float notificationEnd = childViewState.yTranslation + child.getIntrinsicHeight()
+ float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
+ mPaddingBetweenElements;
if (shelfStart > notificationEnd) {
- childViewState.zTranslation = baseZ;
+ childViewState.setZTranslation(baseZ);
} else {
float factor = (notificationEnd - shelfStart) / shelfHeight;
+ if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
+ factor = 1.0f;
+ }
factor = Math.min(factor, 1.0f);
- childViewState.zTranslation = baseZ + factor * zDistanceBetweenElements;
+ childViewState.setZTranslation(baseZ + factor * zDistanceBetweenElements);
}
} else {
- childViewState.zTranslation = baseZ;
+ childViewState.setZTranslation(baseZ);
}
// We need to scrim the notification more from its surrounding content when we are pinned,
// and we therefore elevate it higher.
// We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when
// expanding after which we have a normal elevation again.
- childViewState.zTranslation += (1.0f - child.getHeaderVisibleAmount())
- * mPinnedZTranslationExtra;
+ childViewState.setZTranslation(childViewState.getZTranslation()
+ + (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
return childrenOnTop;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 174bf4c..ee72943 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -169,9 +169,9 @@
adaptDurationWhenGoingToFullShade(child, viewState, wasAdded, animationStaggerCount);
mAnimationProperties.delay = 0;
if (wasAdded || mAnimationFilter.hasDelays
- && (viewState.yTranslation != child.getTranslationY()
- || viewState.zTranslation != child.getTranslationZ()
- || viewState.alpha != child.getAlpha()
+ && (viewState.getYTranslation() != child.getTranslationY()
+ || viewState.getZTranslation() != child.getTranslationZ()
+ || viewState.getAlpha() != child.getAlpha()
|| viewState.height != child.getActualHeight()
|| viewState.clipTopAmount != child.getClipTopAmount())) {
mAnimationProperties.delay = mCurrentAdditionalDelay
@@ -191,7 +191,7 @@
mAnimationProperties.duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50
+ (long) (100 * longerDurationFactor);
}
- child.setTranslationY(viewState.yTranslation + startOffset);
+ child.setTranslationY(viewState.getYTranslation() + startOffset);
}
}
@@ -400,7 +400,7 @@
// travelled
ExpandableViewState viewState =
((ExpandableView) event.viewAfterChangingView).getViewState();
- translationDirection = ((viewState.yTranslation
+ translationDirection = ((viewState.getYTranslation()
- (ownPosition + actualHeight / 2.0f)) * 2 /
actualHeight);
translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
@@ -433,7 +433,7 @@
ExpandableViewState viewState = changingView.getViewState();
mTmpState.copyFrom(viewState);
if (event.headsUpFromBottom) {
- mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
+ mTmpState.setYTranslation(mHeadsUpAppearHeightBottom);
} else {
Runnable onAnimationEnd = null;
if (loggable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index 786de29..d07da38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -21,6 +21,7 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.util.Log;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
@@ -42,7 +43,7 @@
* A state of a view. This can be used to apply a set of view properties to a view with
* {@link com.android.systemui.statusbar.notification.stack.StackScrollState} or start
* animations with {@link com.android.systemui.statusbar.notification.stack.StackStateAnimator}.
-*/
+ */
public class ViewState implements Dumpable {
/**
@@ -51,6 +52,7 @@
*/
protected static final AnimationProperties NO_NEW_ANIMATIONS = new AnimationProperties() {
AnimationFilter mAnimationFilter = new AnimationFilter();
+
@Override
public AnimationFilter getAnimationFilter() {
return mAnimationFilter;
@@ -68,6 +70,7 @@
private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
+ private static final String LOG_TAG = "StackViewState";
private static final AnimatableProperty SCALE_X_PROPERTY
= new AnimatableProperty() {
@@ -117,35 +120,127 @@
}
};
- public float alpha;
- public float xTranslation;
- public float yTranslation;
- public float zTranslation;
public boolean gone;
public boolean hidden;
- public float scaleX = 1.0f;
- public float scaleY = 1.0f;
+
+ private float mAlpha;
+ private float mXTranslation;
+ private float mYTranslation;
+ private float mZTranslation;
+ private float mScaleX = 1.0f;
+ private float mScaleY = 1.0f;
+
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ /**
+ * @param alpha View transparency.
+ */
+ public void setAlpha(float alpha) {
+ if (isValidFloat(alpha, "alpha")) {
+ this.mAlpha = alpha;
+ }
+ }
+
+ public float getXTranslation() {
+ return mXTranslation;
+ }
+
+ /**
+ * @param xTranslation x-axis translation value for the animation.
+ */
+ public void setXTranslation(float xTranslation) {
+ if (isValidFloat(xTranslation, "xTranslation")) {
+ this.mXTranslation = xTranslation;
+ }
+ }
+
+ public float getYTranslation() {
+ return mYTranslation;
+ }
+
+ /**
+ * @param yTranslation y-axis translation value for the animation.
+ */
+ public void setYTranslation(float yTranslation) {
+ if (isValidFloat(yTranslation, "yTranslation")) {
+ this.mYTranslation = yTranslation;
+ }
+ }
+
+ public float getZTranslation() {
+ return mZTranslation;
+ }
+
+
+ /**
+ * @param zTranslation z-axis translation value for the animation.
+ */
+ public void setZTranslation(float zTranslation) {
+ if (isValidFloat(zTranslation, "zTranslation")) {
+ this.mZTranslation = zTranslation;
+ }
+ }
+
+ public float getScaleX() {
+ return mScaleX;
+ }
+
+ /**
+ * @param scaleX x-axis scale property for the animation.
+ */
+ public void setScaleX(float scaleX) {
+ if (isValidFloat(scaleX, "scaleX")) {
+ this.mScaleX = scaleX;
+ }
+ }
+
+ public float getScaleY() {
+ return mScaleY;
+ }
+
+ /**
+ * @param scaleY y-axis scale property for the animation.
+ */
+ public void setScaleY(float scaleY) {
+ if (isValidFloat(scaleY, "scaleY")) {
+ this.mScaleY = scaleY;
+ }
+ }
+
+ /**
+ * Checks if {@code value} is a valid float value. If it is not, logs it (using {@code name})
+ * and returns false.
+ */
+ private boolean isValidFloat(float value, String name) {
+ if (Float.isNaN(value)) {
+ Log.wtf(LOG_TAG, "Cannot set property " + name + " to NaN");
+ return false;
+ }
+ return true;
+ }
public void copyFrom(ViewState viewState) {
- alpha = viewState.alpha;
- xTranslation = viewState.xTranslation;
- yTranslation = viewState.yTranslation;
- zTranslation = viewState.zTranslation;
+ mAlpha = viewState.mAlpha;
+ mXTranslation = viewState.mXTranslation;
+ mYTranslation = viewState.mYTranslation;
+ mZTranslation = viewState.mZTranslation;
gone = viewState.gone;
hidden = viewState.hidden;
- scaleX = viewState.scaleX;
- scaleY = viewState.scaleY;
+ mScaleX = viewState.mScaleX;
+ mScaleY = viewState.mScaleY;
}
public void initFrom(View view) {
- alpha = view.getAlpha();
- xTranslation = view.getTranslationX();
- yTranslation = view.getTranslationY();
- zTranslation = view.getTranslationZ();
+ mAlpha = view.getAlpha();
+ mXTranslation = view.getTranslationX();
+ mYTranslation = view.getTranslationY();
+ mZTranslation = view.getTranslationZ();
gone = view.getVisibility() == View.GONE;
hidden = view.getVisibility() == View.INVISIBLE;
- scaleX = view.getScaleX();
- scaleY = view.getScaleY();
+ mScaleX = view.getScaleX();
+ mScaleY = view.getScaleY();
}
/**
@@ -161,51 +256,51 @@
boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X);
if (animatingX) {
updateAnimationX(view);
- } else if (view.getTranslationX() != this.xTranslation){
- view.setTranslationX(this.xTranslation);
+ } else if (view.getTranslationX() != this.mXTranslation) {
+ view.setTranslationX(this.mXTranslation);
}
// apply yTranslation
boolean animatingY = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y);
if (animatingY) {
updateAnimationY(view);
- } else if (view.getTranslationY() != this.yTranslation) {
- view.setTranslationY(this.yTranslation);
+ } else if (view.getTranslationY() != this.mYTranslation) {
+ view.setTranslationY(this.mYTranslation);
}
// apply zTranslation
boolean animatingZ = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z);
if (animatingZ) {
updateAnimationZ(view);
- } else if (view.getTranslationZ() != this.zTranslation) {
- view.setTranslationZ(this.zTranslation);
+ } else if (view.getTranslationZ() != this.mZTranslation) {
+ view.setTranslationZ(this.mZTranslation);
}
// apply scaleX
boolean animatingScaleX = isAnimating(view, SCALE_X_PROPERTY);
if (animatingScaleX) {
- updateAnimation(view, SCALE_X_PROPERTY, scaleX);
- } else if (view.getScaleX() != scaleX) {
- view.setScaleX(scaleX);
+ updateAnimation(view, SCALE_X_PROPERTY, mScaleX);
+ } else if (view.getScaleX() != mScaleX) {
+ view.setScaleX(mScaleX);
}
// apply scaleY
boolean animatingScaleY = isAnimating(view, SCALE_Y_PROPERTY);
if (animatingScaleY) {
- updateAnimation(view, SCALE_Y_PROPERTY, scaleY);
- } else if (view.getScaleY() != scaleY) {
- view.setScaleY(scaleY);
+ updateAnimation(view, SCALE_Y_PROPERTY, mScaleY);
+ } else if (view.getScaleY() != mScaleY) {
+ view.setScaleY(mScaleY);
}
int oldVisibility = view.getVisibility();
- boolean becomesInvisible = this.alpha == 0.0f
+ boolean becomesInvisible = this.mAlpha == 0.0f
|| (this.hidden && (!isAnimating(view) || oldVisibility != View.VISIBLE));
boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
if (animatingAlpha) {
updateAlphaAnimation(view);
- } else if (view.getAlpha() != this.alpha) {
+ } else if (view.getAlpha() != this.mAlpha) {
// apply layer type
- boolean becomesFullyVisible = this.alpha == 1.0f;
+ boolean becomesFullyVisible = this.mAlpha == 1.0f;
boolean becomesFaded = !becomesInvisible && !becomesFullyVisible;
if (FadeOptimizedNotification.FADE_LAYER_OPTIMIZATION_ENABLED
&& view instanceof FadeOptimizedNotification) {
@@ -229,7 +324,7 @@
}
// apply alpha
- view.setAlpha(this.alpha);
+ view.setAlpha(this.mAlpha);
}
// apply visibility
@@ -274,54 +369,55 @@
/**
* Start an animation to this viewstate
- * @param child the view to animate
+ *
+ * @param child the view to animate
* @param animationProperties the properties of the animation
*/
public void animateTo(View child, AnimationProperties animationProperties) {
boolean wasVisible = child.getVisibility() == View.VISIBLE;
- final float alpha = this.alpha;
+ final float alpha = this.mAlpha;
if (!wasVisible && (alpha != 0 || child.getAlpha() != 0)
&& !this.gone && !this.hidden) {
child.setVisibility(View.VISIBLE);
}
float childAlpha = child.getAlpha();
- boolean alphaChanging = this.alpha != childAlpha;
+ boolean alphaChanging = this.mAlpha != childAlpha;
if (child instanceof ExpandableView) {
// We don't want views to change visibility when they are animating to GONE
alphaChanging &= !((ExpandableView) child).willBeGone();
}
// start translationX animation
- if (child.getTranslationX() != this.xTranslation) {
+ if (child.getTranslationX() != this.mXTranslation) {
startXTranslationAnimation(child, animationProperties);
} else {
abortAnimation(child, TAG_ANIMATOR_TRANSLATION_X);
}
// start translationY animation
- if (child.getTranslationY() != this.yTranslation) {
+ if (child.getTranslationY() != this.mYTranslation) {
startYTranslationAnimation(child, animationProperties);
} else {
abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y);
}
// start translationZ animation
- if (child.getTranslationZ() != this.zTranslation) {
+ if (child.getTranslationZ() != this.mZTranslation) {
startZTranslationAnimation(child, animationProperties);
} else {
abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z);
}
// start scaleX animation
- if (child.getScaleX() != scaleX) {
- PropertyAnimator.startAnimation(child, SCALE_X_PROPERTY, scaleX, animationProperties);
+ if (child.getScaleX() != mScaleX) {
+ PropertyAnimator.startAnimation(child, SCALE_X_PROPERTY, mScaleX, animationProperties);
} else {
abortAnimation(child, SCALE_X_PROPERTY.getAnimatorTag());
}
// start scaleX animation
- if (child.getScaleY() != scaleY) {
- PropertyAnimator.startAnimation(child, SCALE_Y_PROPERTY, scaleY, animationProperties);
+ if (child.getScaleY() != mScaleY) {
+ PropertyAnimator.startAnimation(child, SCALE_Y_PROPERTY, mScaleY, animationProperties);
} else {
abortAnimation(child, SCALE_Y_PROPERTY.getAnimatorTag());
}
@@ -329,7 +425,7 @@
// start alpha animation
if (alphaChanging) {
startAlphaAnimation(child, animationProperties);
- } else {
+ } else {
abortAnimation(child, TAG_ANIMATOR_ALPHA);
}
}
@@ -339,9 +435,9 @@
}
private void startAlphaAnimation(final View child, AnimationProperties properties) {
- Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
- Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
- final float newEndValue = this.alpha;
+ Float previousStartValue = getChildTag(child, TAG_START_ALPHA);
+ Float previousEndValue = getChildTag(child, TAG_END_ALPHA);
+ final float newEndValue = this.mAlpha;
if (previousEndValue != null && previousEndValue == newEndValue) {
return;
}
@@ -426,9 +522,9 @@
}
private void startZTranslationAnimation(final View child, AnimationProperties properties) {
- Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
- Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
- float newEndValue = this.zTranslation;
+ Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_Z);
+ Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_Z);
+ float newEndValue = this.mZTranslation;
if (previousEndValue != null && previousEndValue == newEndValue) {
return;
}
@@ -487,9 +583,9 @@
}
private void startXTranslationAnimation(final View child, AnimationProperties properties) {
- Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_X);
- Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_X);
- float newEndValue = this.xTranslation;
+ Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_X);
+ Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_X);
+ float newEndValue = this.mXTranslation;
if (previousEndValue != null && previousEndValue == newEndValue) {
return;
}
@@ -519,7 +615,7 @@
child.getTranslationX(), newEndValue);
Interpolator customInterpolator = properties.getCustomInterpolator(child,
View.TRANSLATION_X);
- Interpolator interpolator = customInterpolator != null ? customInterpolator
+ Interpolator interpolator = customInterpolator != null ? customInterpolator
: Interpolators.FAST_OUT_SLOW_IN;
animator.setInterpolator(interpolator);
long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
@@ -553,9 +649,9 @@
}
private void startYTranslationAnimation(final View child, AnimationProperties properties) {
- Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
- Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
- float newEndValue = this.yTranslation;
+ Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_Y);
+ Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_Y);
+ float newEndValue = this.mYTranslation;
if (previousEndValue != null && previousEndValue == newEndValue) {
return;
}
@@ -585,7 +681,7 @@
child.getTranslationY(), newEndValue);
Interpolator customInterpolator = properties.getCustomInterpolator(child,
View.TRANSLATION_Y);
- Interpolator interpolator = customInterpolator != null ? customInterpolator
+ Interpolator interpolator = customInterpolator != null ? customInterpolator
: Interpolators.FAST_OUT_SLOW_IN;
animator.setInterpolator(interpolator);
long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
@@ -644,7 +740,7 @@
/**
* Cancel the previous animator and get the duration of the new animation.
*
- * @param duration the new duration
+ * @param duration the new duration
* @param previousAnimator the animator which was running before
* @return the new duration
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index fe43137..a2798f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -163,7 +163,6 @@
private PowerManager.WakeLock mWakeLock;
private final com.android.systemui.shade.ShadeController mShadeController;
private final KeyguardUpdateMonitor mUpdateMonitor;
- private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final SessionTracker mSessionTracker;
@@ -278,7 +277,7 @@
KeyguardStateController keyguardStateController, Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@Main Resources resources,
- KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters,
+ KeyguardBypassController keyguardBypassController,
MetricsLogger metricsLogger, DumpManager dumpManager,
PowerManager powerManager,
NotificationMediaManager notificationMediaManager,
@@ -294,7 +293,6 @@
mPowerManager = powerManager;
mShadeController = shadeController;
mUpdateMonitor = keyguardUpdateMonitor;
- mDozeParameters = dozeParameters;
mUpdateMonitor.registerCallback(this);
mMediaManager = notificationMediaManager;
mLatencyTracker = latencyTracker;
@@ -552,7 +550,7 @@
boolean deviceDreaming = mUpdateMonitor.isDreaming();
if (!mUpdateMonitor.isDeviceInteractive()) {
- if (!mKeyguardViewController.isShowing()
+ if (!mKeyguardStateController.isShowing()
&& !mScreenOffAnimationController.isKeyguardShowDelayed()) {
if (mKeyguardStateController.isUnlocked()) {
return MODE_WAKE_AND_UNLOCK;
@@ -569,7 +567,7 @@
if (unlockingAllowed && deviceDreaming) {
return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
}
- if (mKeyguardViewController.isShowing()) {
+ if (mKeyguardStateController.isShowing()) {
if (mKeyguardViewController.bouncerIsOrWillBeShowing() && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
} else if (unlockingAllowed) {
@@ -588,7 +586,7 @@
boolean bypass = mKeyguardBypassController.getBypassEnabled()
|| mAuthController.isUdfpsFingerDown();
if (!mUpdateMonitor.isDeviceInteractive()) {
- if (!mKeyguardViewController.isShowing()) {
+ if (!mKeyguardStateController.isShowing()) {
return bypass ? MODE_WAKE_AND_UNLOCK : MODE_ONLY_WAKE;
} else if (!unlockingAllowed) {
return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
@@ -612,7 +610,7 @@
if (unlockingAllowed && mKeyguardStateController.isOccluded()) {
return MODE_UNLOCK_COLLAPSING;
}
- if (mKeyguardViewController.isShowing()) {
+ if (mKeyguardStateController.isShowing()) {
if ((mKeyguardViewController.bouncerIsOrWillBeShowing()
|| mKeyguardBypassController.getAltBouncerShowing()) && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index f37243a..fa7bfae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -416,6 +416,9 @@
void endAffordanceLaunch();
+ /** Should the keyguard be hidden immediately in response to a back press/gesture. */
+ boolean shouldKeyguardHideImmediately();
+
boolean onBackPressed();
boolean onSpacePressed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 52a45d6..1e95dad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -363,7 +363,7 @@
mKeyguardUpdateMonitor.onCameraLaunched();
}
- if (!mStatusBarKeyguardViewManager.isShowing()) {
+ if (!mKeyguardStateController.isShowing()) {
final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
false /* onlyProvisioned */, true /* dismissShade */,
@@ -420,7 +420,7 @@
// TODO(b/169087248) Possibly add haptics here for emergency action. Currently disabled for
// app-side haptic experimentation.
- if (!mStatusBarKeyguardViewManager.isShowing()) {
+ if (!mKeyguardStateController.isShowing()) {
mCentralSurfaces.startActivityDismissingKeyguard(emergencyIntent,
false /* onlyProvisioned */, true /* dismissShade */,
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
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 f736047..604e146 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.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;
@@ -183,6 +182,8 @@
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView;
@@ -221,8 +222,6 @@
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -270,8 +269,7 @@
* </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 @@
ONLY_CORE_APPS = onlyCoreApps;
}
+ private final Context mContext;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
private float mTransitionToFullShadeProgress = 0f;
@@ -476,7 +475,6 @@
private final KeyguardStateController mKeyguardStateController;
private final HeadsUpManagerPhone mHeadsUpManager;
private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
- private final DynamicPrivacyController mDynamicPrivacyController;
private final FalsingCollector mFalsingCollector;
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -514,7 +512,7 @@
private final NotificationGutsManager mGutsManager;
private final NotificationLogger mNotificationLogger;
- private final PanelExpansionStateManager mPanelExpansionStateManager;
+ private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final KeyguardViewMediator mKeyguardViewMediator;
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final BrightnessSliderController.Factory mBrightnessSliderFactory;
@@ -700,7 +698,7 @@
NotificationGutsManager notificationGutsManager,
NotificationLogger notificationLogger,
NotificationInterruptStateProvider notificationInterruptStateProvider,
- PanelExpansionStateManager panelExpansionStateManager,
+ ShadeExpansionStateManager shadeExpansionStateManager,
KeyguardViewMediator keyguardViewMediator,
DisplayMetrics displayMetrics,
MetricsLogger metricsLogger,
@@ -765,7 +763,7 @@
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
IDreamManager dreamManager) {
- super(context);
+ mContext = context;
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
mLightBarController = lightBarController;
@@ -779,14 +777,13 @@
mHeadsUpManager = headsUpManagerPhone;
mKeyguardIndicationController = keyguardIndicationController;
mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
- mDynamicPrivacyController = dynamicPrivacyController;
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
mGutsManager = notificationGutsManager;
mNotificationLogger = notificationLogger;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
- mPanelExpansionStateManager = panelExpansionStateManager;
+ mShadeExpansionStateManager = shadeExpansionStateManager;
mKeyguardViewMediator = keyguardViewMediator;
mDisplayMetrics = displayMetrics;
mMetricsLogger = metricsLogger;
@@ -851,7 +848,7 @@
mScreenOffAnimationController = screenOffAnimationController;
- mPanelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
+ mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
mBubbleExpandListener = (isExpanding, key) ->
mContext.getMainExecutor().execute(this::updateScrimController);
@@ -1141,7 +1138,7 @@
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationLogger.setUpWithContainer(mNotifListContainer);
mNotificationIconAreaController.setupShelf(mNotificationShelfController);
- mPanelExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
+ mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
mUserSwitcherController.init(mNotificationShadeWindowView);
// Allow plugins to reference DarkIconDispatcher and StatusBarStateController
@@ -1377,7 +1374,7 @@
}
}
- private void onPanelExpansionChanged(PanelExpansionChangeEvent event) {
+ private void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
float fraction = event.getFraction();
boolean tracking = event.getTracking();
dispatchPanelExpansionForKeyguardDismiss(fraction, tracking);
@@ -1561,7 +1558,7 @@
mKeyguardViewMediator.registerCentralSurfaces(
/* statusBar= */ this,
mNotificationPanelViewController,
- mPanelExpansionStateManager,
+ mShadeExpansionStateManager,
mBiometricUnlockController,
mStackScroller,
mKeyguardBypassController);
@@ -1570,7 +1567,6 @@
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
- mDynamicPrivacyController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
@@ -2082,7 +2078,7 @@
// Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
// the bouncer appear animation.
- if (!mStatusBarKeyguardViewManager.isShowing()) {
+ if (!mKeyguardStateController.isShowing()) {
WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
}
}
@@ -2519,8 +2515,8 @@
};
// Do not deferKeyguard when occluded because, when keyguard is occluded,
// we do not launch the activity until keyguard is done.
- boolean occluded = mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isOccluded();
+ boolean occluded = mKeyguardStateController.isShowing()
+ && mKeyguardStateController.isOccluded();
boolean deferred = !occluded;
executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly,
willLaunchResolverActivity, deferred /* deferred */, animate);
@@ -2590,8 +2586,8 @@
@Override
public boolean onDismiss() {
if (runnable != null) {
- if (mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isOccluded()) {
+ if (mKeyguardStateController.isShowing()
+ && mKeyguardStateController.isOccluded()) {
mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
} else {
mMainExecutor.execute(runnable);
@@ -2685,7 +2681,7 @@
private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
boolean afterKeyguardGone) {
- if (mStatusBarKeyguardViewManager.isShowing() && requiresShadeOpen) {
+ if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
}
dismissKeyguardThenExecute(action, null /* cancelAction */,
@@ -2709,7 +2705,7 @@
mBiometricUnlockController.startWakeAndUnlock(
BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
}
- if (mStatusBarKeyguardViewManager.isShowing()) {
+ if (mKeyguardStateController.isShowing()) {
mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
afterKeyguardGone);
} else {
@@ -2845,8 +2841,8 @@
}
private void logStateToEventlog() {
- boolean isShowing = mStatusBarKeyguardViewManager.isShowing();
- boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded();
+ boolean isShowing = mKeyguardStateController.isShowing();
+ boolean isOccluded = mKeyguardStateController.isOccluded();
boolean isBouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
boolean isSecure = mKeyguardStateController.isMethodSecure();
boolean unlocked = mKeyguardStateController.canDismissLockScreen();
@@ -3242,18 +3238,17 @@
Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
Trace.beginSection("CentralSurfaces#updateDozingState");
- boolean visibleNotOccluded = mStatusBarKeyguardViewManager.isShowing()
- && !mStatusBarKeyguardViewManager.isOccluded();
+ boolean keyguardVisible = mKeyguardStateController.isVisible();
// If we're dozing and we'll be animating the screen off, the keyguard isn't currently
// visible but will be shortly for the animation, so we should proceed as if it's visible.
- boolean visibleNotOccludedOrWillBe =
- visibleNotOccluded || (mDozing && mDozeParameters.shouldDelayKeyguardShow());
+ boolean keyguardVisibleOrWillBe =
+ keyguardVisible || (mDozing && mDozeParameters.shouldDelayKeyguardShow());
boolean wakeAndUnlock = mBiometricUnlockController.getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
|| (mDozing && mDozeParameters.shouldControlScreenOff()
- && visibleNotOccludedOrWillBe);
+ && keyguardVisibleOrWillBe);
mNotificationPanelViewController.setDozing(mDozing, animate);
updateQsExpansionEnabled();
@@ -3312,19 +3307,23 @@
mNotificationPanelViewController.onAffordanceLaunchEnded();
}
+ /**
+ * Returns whether the keyguard should hide immediately (as opposed to via an animation).
+ * Non-scrimmed bouncers have a special animation tied to the notification panel expansion.
+ * @return whether the keyguard should be immediately hidden.
+ */
@Override
- public boolean onBackPressed() {
+ public boolean shouldKeyguardHideImmediately() {
final boolean isScrimmedBouncer =
mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
final boolean isBouncerOverDream = isBouncerShowingOverDream();
+ return (isScrimmedBouncer || isBouncerOverDream);
+ }
- if (mStatusBarKeyguardViewManager.onBackPressed(
- isScrimmedBouncer || isBouncerOverDream /* hideImmediately */)) {
- if (isScrimmedBouncer || isBouncerOverDream) {
- mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
- } else {
- mNotificationPanelViewController.expandWithoutQs();
- }
+ @Override
+ public boolean onBackPressed() {
+ if (mStatusBarKeyguardViewManager.canHandleBackPressed()) {
+ mStatusBarKeyguardViewManager.onBackPressed();
return true;
}
if (mNotificationPanelViewController.isQsCustomizing()) {
@@ -3339,7 +3338,7 @@
return true;
}
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
- && !isBouncerOverDream) {
+ && !isBouncerShowingOverDream()) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
mShadeController.animateCollapsePanels();
}
@@ -3550,7 +3549,7 @@
public void setBouncerShowingOverDream(boolean bouncerShowingOverDream) {
mBouncerShowingOverDream = bouncerShowingOverDream;
}
-
+
/**
* Propagate the bouncer state to status bar components.
*
@@ -3812,8 +3811,7 @@
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())
@@ -3930,11 +3928,7 @@
@Override
public boolean isKeyguardShowing() {
- if (mStatusBarKeyguardViewManager == null) {
- Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true");
- return true;
- }
- return mStatusBarKeyguardViewManager.isShowing();
+ return mKeyguardStateController.isShowing();
}
@Override
@@ -4225,14 +4219,6 @@
@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
@@ -4292,11 +4278,6 @@
.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/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 54d39fd..de7b152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -93,13 +93,13 @@
private boolean mControlScreenOffAnimation;
private boolean mIsQuickPickupEnabled;
- private boolean mKeyguardShowing;
+ private boolean mKeyguardVisible;
@VisibleForTesting
final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mKeyguardShowing = showing;
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ mKeyguardVisible = visible;
updateControlScreenOff();
}
@@ -293,7 +293,7 @@
public void updateControlScreenOff() {
if (!getDisplayNeedsBlanking()) {
final boolean controlScreenOff =
- getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff());
+ getAlwaysOn() && (mKeyguardVisible || shouldControlUnlockedScreenOff());
setControlScreenOffAnimation(controlScreenOff);
}
}
@@ -348,7 +348,7 @@
}
private boolean willAnimateFromLockScreenToAod() {
- return getAlwaysOn() && mKeyguardShowing;
+ return getAlwaysOn() && mKeyguardVisible;
}
private boolean getBoolean(String propName, int resId) {
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 d61c51e..9bb4132 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 @@
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 b58dbe2..5e26cf0 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 @@
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
@@ -88,7 +88,7 @@
updateListeningState()
}
- override fun onKeyguardVisibilityChanged(showing: Boolean) {
+ override fun onKeyguardVisibilityChanged(visible: Boolean) {
updateListeningState()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 054bd28..14cebf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -187,8 +187,8 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ if (visible) {
updateUserSwitcher();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 5a70d89..9767103 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -211,11 +211,11 @@
canvas.drawLine(end, 0, end, height, paint);
paint.setColor(Color.GREEN);
- int lastIcon = (int) mLastVisibleIconState.xTranslation;
+ int lastIcon = (int) mLastVisibleIconState.getXTranslation();
canvas.drawLine(lastIcon, 0, lastIcon, height, paint);
if (mFirstVisibleIconState != null) {
- int firstIcon = (int) mFirstVisibleIconState.xTranslation;
+ int firstIcon = (int) mFirstVisibleIconState.getXTranslation();
canvas.drawLine(firstIcon, 0, firstIcon, height, paint);
}
@@ -413,7 +413,7 @@
View view = getChildAt(i);
ViewState iconState = mIconStates.get(view);
iconState.initFrom(view);
- iconState.alpha = mIsolatedIcon == null || view == mIsolatedIcon ? 1.0f : 0.0f;
+ iconState.setAlpha(mIsolatedIcon == null || view == mIsolatedIcon ? 1.0f : 0.0f);
iconState.hidden = false;
}
}
@@ -467,7 +467,7 @@
// We only modify the xTranslation if it's fully inside of the container
// since during the transition to the shelf, the translations are controlled
// from the outside
- iconState.xTranslation = translationX;
+ iconState.setXTranslation(translationX);
}
if (mFirstVisibleIconState == null) {
mFirstVisibleIconState = iconState;
@@ -501,7 +501,7 @@
View view = getChildAt(i);
IconState iconState = mIconStates.get(view);
int dotWidth = mStaticDotDiameter + mDotPadding;
- iconState.xTranslation = translationX;
+ iconState.setXTranslation(translationX);
if (mNumDots < MAX_DOTS) {
if (mNumDots == 0 && iconState.iconAppearAmount < 0.8f) {
iconState.visibleState = StatusBarIconView.STATE_ICON;
@@ -525,7 +525,8 @@
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
IconState iconState = mIconStates.get(view);
- iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth();
+ iconState.setXTranslation(
+ getWidth() - iconState.getXTranslation() - view.getWidth());
}
}
if (mIsolatedIcon != null) {
@@ -533,8 +534,8 @@
if (iconState != null) {
// Most of the time the icon isn't yet added when this is called but only happening
// later
- iconState.xTranslation = mIsolatedIconLocation.left - mAbsolutePosition[0]
- - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f;
+ iconState.setXTranslation(mIsolatedIconLocation.left - mAbsolutePosition[0]
+ - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f);
iconState.visibleState = StatusBarIconView.STATE_ICON;
}
}
@@ -609,8 +610,10 @@
return 0;
}
- int translation = (int) (isLayoutRtl() ? getWidth() - mLastVisibleIconState.xTranslation
- : mLastVisibleIconState.xTranslation + mIconSize);
+ int translation = (int) (isLayoutRtl()
+ ? getWidth() - mLastVisibleIconState.getXTranslation()
+ : mLastVisibleIconState.getXTranslation() + mIconSize);
+
// There's a chance that last translation goes beyond the edge maybe
return Math.min(getWidth(), translation);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 4d1c361..8490768 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -249,6 +249,7 @@
private Callback mCallback;
private boolean mWallpaperSupportsAmbientMode;
private boolean mScreenOn;
+ private boolean mTransparentScrimBackground;
// Scrim blanking callbacks
private Runnable mPendingFrameCallback;
@@ -341,6 +342,8 @@
mScrimBehind.setDefaultFocusHighlightEnabled(false);
mNotificationsScrim.setDefaultFocusHighlightEnabled(false);
mScrimInFront.setDefaultFocusHighlightEnabled(false);
+ mTransparentScrimBackground = notificationsScrim.getResources()
+ .getBoolean(R.bool.notification_scrim_transparent);
updateScrims();
mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
}
@@ -777,13 +780,16 @@
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
if (mClipsQsScrim) {
- mBehindAlpha = 1;
- mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
+ mBehindAlpha = mTransparentScrimBackground ? 0 : 1;
+ mNotificationsAlpha =
+ mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
} else {
- mBehindAlpha = behindFraction * mDefaultScrimAlpha;
+ mBehindAlpha =
+ mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
// Delay fade-in of notification scrim a bit further, to coincide with the
// view fade in. Otherwise the empty panel can be quite jarring.
- mNotificationsAlpha = MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f,
+ mNotificationsAlpha = mTransparentScrimBackground
+ ? 0 : MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f,
mPanelExpansionFraction);
}
mBehindTint = mState.getBehindTint();
@@ -1534,7 +1540,7 @@
private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
mNeedsDrawableColorUpdate = true;
scheduleUpdate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index d6d021f..ece7ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -16,6 +16,7 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
import android.annotation.Nullable;
@@ -38,7 +39,7 @@
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.BaseStatusBarWifiView;
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -48,6 +49,10 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
import com.android.systemui.util.Assert;
@@ -84,6 +89,12 @@
void setMobileIcons(String slot, List<MobileIconState> states);
/**
+ * This method completely replaces {@link #setMobileIcons} with the information from the new
+ * mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
+ * to worry about funneling MobileIconState objects through anymore.
+ */
+ void setNewMobileIconSubIds(List<Integer> subIds);
+ /**
* Display the no calling & SMS icons.
*/
void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
@@ -141,12 +152,14 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
+ MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
@@ -207,6 +220,7 @@
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileUiAdapter mMobileUiAdapter;
private final DarkIconDispatcher mDarkIconDispatcher;
@Inject
@@ -214,10 +228,12 @@
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider,
+ MobileUiAdapter mobileUiAdapter,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
+ mMobileUiAdapter = mobileUiAdapter;
mDarkIconDispatcher = darkIconDispatcher;
}
@@ -227,6 +243,7 @@
location,
mStatusBarPipelineFlags,
mWifiViewModel,
+ mMobileUiAdapter,
mMobileContextProvider,
mDarkIconDispatcher);
}
@@ -244,11 +261,14 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
super(group,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
mobileContextProvider);
}
@@ -284,14 +304,18 @@
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileUiAdapter mMobileUiAdapter;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModel = wifiViewModel;
+ mMobileUiAdapter = mobileUiAdapter;
mMobileContextProvider = mobileContextProvider;
}
@@ -301,6 +325,7 @@
location,
mStatusBarPipelineFlags,
mWifiViewModel,
+ mMobileUiAdapter,
mMobileContextProvider);
}
}
@@ -315,6 +340,8 @@
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileIconsViewModel mMobileIconsViewModel;
+
protected final Context mContext;
protected final int mIconSize;
// Whether or not these icons show up in dumpsys
@@ -333,7 +360,9 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
mGroup = group;
mLocation = location;
mStatusBarPipelineFlags = statusBarPipelineFlags;
@@ -342,6 +371,14 @@
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
+
+ if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ // This starts the flow for the new pipeline, and will notify us of changes
+ mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
+ MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
+ } else {
+ mMobileIconsViewModel = null;
+ }
}
public boolean isDemoable() {
@@ -394,6 +431,9 @@
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
+
+ case TYPE_MOBILE_NEW:
+ return addNewMobileIcon(index, slot, holder.getTag());
}
return null;
@@ -410,7 +450,7 @@
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
- final BaseStatusBarWifiView view;
+ final BaseStatusBarFrameLayout view;
if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
view = onCreateModernStatusBarWifiView(slot);
// When [ModernStatusBarWifiView] is created, it will automatically apply the
@@ -429,17 +469,47 @@
}
@VisibleForTesting
- protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
+ protected StatusIconDisplayable addMobileIcon(
+ int index,
+ String slot,
+ MobileIconState state
+ ) {
+ if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ throw new IllegalStateException("Attempting to add a mobile icon while the new "
+ + "pipeline is enabled is not supported");
+ }
+
// Use the `subId` field as a key to query for the correct context
- StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
- view.applyMobileState(state);
- mGroup.addView(view, index, onCreateLayoutParams());
+ StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
+ mobileView.applyMobileState(state);
+ mGroup.addView(mobileView, index, onCreateLayoutParams());
if (mIsInDemoMode) {
Context mobileContext = mMobileContextProvider
.getMobileContextForSub(state.subId, mContext);
mDemoStatusIcons.addMobileView(state, mobileContext);
}
+ return mobileView;
+ }
+
+ protected StatusIconDisplayable addNewMobileIcon(
+ int index,
+ String slot,
+ int subId
+ ) {
+ if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ throw new IllegalStateException("Attempting to add a mobile icon using the new"
+ + "pipeline, but the enabled flag is false.");
+ }
+
+ BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
+ mGroup.addView(view, index, onCreateLayoutParams());
+
+ if (mIsInDemoMode) {
+ // TODO (b/249790009): demo mode should be handled at the data layer in the
+ // new pipeline
+ }
+
return view;
}
@@ -464,6 +534,15 @@
return view;
}
+ private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
+ String slot, int subId) {
+ return ModernStatusBarMobileView
+ .constructAndBind(
+ mContext,
+ slot,
+ mMobileIconsViewModel.viewModelForSub(subId));
+ }
+
protected LinearLayout.LayoutParams onCreateLayoutParams() {
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
}
@@ -519,6 +598,10 @@
return;
case TYPE_MOBILE:
onSetMobileIcon(viewIndex, holder.getMobileState());
+ return;
+ case TYPE_MOBILE_NEW:
+ // Nothing, the icon updates itself now
+ return;
default:
break;
}
@@ -542,9 +625,13 @@
}
public void onSetMobileIcon(int viewIndex, MobileIconState state) {
- StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
- if (view != null) {
- view.applyMobileState(state);
+ View view = mGroup.getChildAt(viewIndex);
+ if (view instanceof StatusBarMobileView) {
+ ((StatusBarMobileView) view).applyMobileState(state);
+ } else {
+ // ModernStatusBarMobileView automatically updates via the ViewModel
+ throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
+ + "the new pipeline");
}
if (mIsInDemoMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 7c31366..e106b9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.tuner.TunerService;
@@ -66,8 +67,8 @@
private final StatusBarIconList mStatusBarIconList;
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
private final ArraySet<String> mIconHideList = new ArraySet<>();
-
- private Context mContext;
+ private final StatusBarPipelineFlags mStatusBarPipelineFlags;
+ private final Context mContext;
/** */
@Inject
@@ -78,9 +79,12 @@
ConfigurationController configurationController,
TunerService tunerService,
DumpManager dumpManager,
- StatusBarIconList statusBarIconList) {
+ StatusBarIconList statusBarIconList,
+ StatusBarPipelineFlags statusBarPipelineFlags
+ ) {
mStatusBarIconList = statusBarIconList;
mContext = context;
+ mStatusBarPipelineFlags = statusBarPipelineFlags;
configurationController.addCallback(this);
commandQueue.addCallback(this);
@@ -220,6 +224,11 @@
*/
@Override
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
+ if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+ + "pipeline frontend is enabled");
+ return;
+ }
Slot mobileSlot = mStatusBarIconList.getSlot(slot);
// Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
@@ -227,7 +236,6 @@
Collections.reverse(iconStates);
for (MobileIconState state : iconStates) {
-
StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
if (holder == null) {
holder = StatusBarIconHolder.fromMobileIconState(state);
@@ -239,6 +247,28 @@
}
}
+ @Override
+ public void setNewMobileIconSubIds(List<Integer> subIds) {
+ if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ Log.d(TAG, "ignoring new pipeline callback, "
+ + "since the frontend is disabled");
+ return;
+ }
+ Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+
+ Collections.reverse(subIds);
+
+ for (Integer subId : subIds) {
+ StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
+ if (holder == null) {
+ holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
+ setIcon("mobile", holder);
+ } else {
+ // Don't have to do anything in the new world
+ }
+ }
+ }
+
/**
* Accept a list of CallIndicatorIconStates, and show the call strength icons.
* @param slot statusbar slot for the call strength icons
@@ -384,8 +414,6 @@
}
}
-
-
private void handleSet(String slotName, StatusBarIconHolder holder) {
int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index af342dd..68a203e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -25,6 +26,10 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
@@ -33,15 +38,35 @@
public static final int TYPE_ICON = 0;
public static final int TYPE_WIFI = 1;
public static final int TYPE_MOBILE = 2;
+ /**
+ * TODO (b/249790733): address this once the new pipeline is in place
+ * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
+ * to inform the old view system about changes to the data set (the list of mobile icons). The
+ * design of the new pipeline should allow for removal of this icon holder type, and obsolete
+ * the need for this entire class.
+ *
+ * @deprecated This field only exists so the new status bar pipeline can interface with the
+ * view holder system.
+ */
+ @Deprecated
+ public static final int TYPE_MOBILE_NEW = 3;
+
+ @IntDef({
+ TYPE_ICON,
+ TYPE_WIFI,
+ TYPE_MOBILE,
+ TYPE_MOBILE_NEW
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface IconType {}
private StatusBarIcon mIcon;
private WifiIconState mWifiState;
private MobileIconState mMobileState;
- private int mType = TYPE_ICON;
+ private @IconType int mType = TYPE_ICON;
private int mTag = 0;
private StatusBarIconHolder() {
-
}
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
@@ -80,6 +105,18 @@
}
/**
+ * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+ * determine icon ordering and building the correct view model
+ */
+ public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
+ StatusBarIconHolder holder = new StatusBarIconHolder();
+ holder.mType = TYPE_MOBILE_NEW;
+ holder.mTag = subId;
+
+ return holder;
+ }
+
+ /**
* Creates a new StatusBarIconHolder from a CallIndicatorIconState.
*/
public static StatusBarIconHolder fromCallIndicatorState(
@@ -95,7 +132,7 @@
return holder;
}
- public int getType() {
+ public @IconType int getType() {
return mType;
}
@@ -134,8 +171,12 @@
return mWifiState.visible;
case TYPE_MOBILE:
return mMobileState.visible;
+ case TYPE_MOBILE_NEW:
+ //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ return true;
- default: return true;
+ default:
+ return true;
}
}
@@ -156,6 +197,10 @@
case TYPE_MOBILE:
mMobileState.visible = visible;
break;
+
+ case TYPE_MOBILE_NEW:
+ //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ break;
}
}
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 a0578fa..3a1c03d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -31,12 +31,15 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -65,6 +68,9 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -74,9 +80,6 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.FoldAodAnimationController;
@@ -100,7 +103,7 @@
@SysUISingleton
public class StatusBarKeyguardViewManager implements RemoteInputController.Callback,
StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener,
- PanelExpansionListener, NavigationModeController.ModeChangedListener,
+ ShadeExpansionListener, NavigationModeController.ModeChangedListener,
KeyguardViewController, FoldAodAnimationController.FoldAodAnimationStatus {
// When hiding the Keyguard with timing supplied from WindowManager, better be early than late.
@@ -119,6 +122,7 @@
private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000;
private static String TAG = "StatusBarKeyguardViewManager";
+ private static final boolean DEBUG = false;
protected final Context mContext;
private final ConfigurationController mConfigurationController;
@@ -184,8 +188,25 @@
if (mAlternateAuthInterceptor != null) {
mAlternateAuthInterceptor.onBouncerVisibilityChanged();
}
+
+ /* Register predictive back callback when keyguard becomes visible, and unregister
+ when it's hidden. */
+ if (isVisible) {
+ registerBackCallback();
+ } else {
+ unregisterBackCallback();
+ }
}
};
+
+ private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+ if (DEBUG) {
+ Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()");
+ }
+ onBackPressed();
+ };
+ private boolean mIsBackCallbackRegistered = false;
+
private final DockManager.DockEventListener mDockEventListener =
new DockManager.DockEventListener() {
@Override
@@ -204,12 +225,11 @@
protected CentralSurfaces mCentralSurfaces;
private NotificationPanelViewController mNotificationPanelViewController;
private BiometricUnlockController mBiometricUnlockController;
+ private boolean mCentralSurfacesRegistered;
private View mNotificationContainer;
@Nullable protected KeyguardBouncer mBouncer;
- protected boolean mShowing;
- protected boolean mOccluded;
protected boolean mRemoteInputActive;
private boolean mGlobalActionsVisible = false;
private boolean mLastGlobalActionsVisible = false;
@@ -256,10 +276,9 @@
new KeyguardUpdateMonitorCallback() {
@Override
public void onEmergencyCallAction() {
-
// Since we won't get a setOccluded call we have to reset the view manually such that
// the bouncer goes away.
- if (mOccluded) {
+ if (mKeyguardStateController.isOccluded()) {
reset(true /* hideBouncerWhenShowing */);
}
}
@@ -317,7 +336,7 @@
@Override
public void registerCentralSurfaces(CentralSurfaces centralSurfaces,
NotificationPanelViewController notificationPanelViewController,
- PanelExpansionStateManager panelExpansionStateManager,
+ ShadeExpansionStateManager shadeExpansionStateManager,
BiometricUnlockController biometricUnlockController,
View notificationContainer,
KeyguardBypassController bypassController) {
@@ -331,13 +350,14 @@
mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
}
mNotificationPanelViewController = notificationPanelViewController;
- if (panelExpansionStateManager != null) {
- panelExpansionStateManager.addExpansionListener(this);
+ if (shadeExpansionStateManager != null) {
+ shadeExpansionStateManager.addExpansionListener(this);
}
mBypassController = bypassController;
mNotificationContainer = notificationContainer;
mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
centralSurfaces.getKeyguardMessageArea());
+ mCentralSurfacesRegistered = true;
registerListeners();
}
@@ -378,13 +398,53 @@
}
}
+ /** Register a callback, to be invoked by the Predictive Back system. */
+ private void registerBackCallback() {
+ if (!mIsBackCallbackRegistered) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_OVERLAY, mOnBackInvokedCallback);
+ mIsBackCallbackRegistered = true;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "view root was null, could not register back callback");
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "prevented registering back callback twice");
+ }
+ }
+ }
+
+ /** Unregister the callback formerly registered with the Predictive Back system. */
+ private void unregisterBackCallback() {
+ if (mIsBackCallbackRegistered) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(
+ mOnBackInvokedCallback);
+ mIsBackCallbackRegistered = false;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "view root was null, could not unregister back callback");
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "prevented unregistering back callback twice");
+ }
+ }
+ }
+
@Override
public void onDensityOrFontScaleChanged() {
hideBouncer(true /* destroyView */);
}
@Override
- public void onPanelExpansionChanged(PanelExpansionChangeEvent event) {
+ public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
float fraction = event.getFraction();
boolean tracking = event.getTracking();
// Avoid having the shade and the bouncer open at the same time over a dream.
@@ -405,8 +465,9 @@
} else if (mNotificationPanelViewController.isUnlockHintRunning()) {
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ } else {
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// Don't expand to the bouncer. Instead transition back to the lock screen (see
// CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
@@ -414,17 +475,19 @@
} else if (bouncerNeedsScrimming()) {
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ } else {
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
- } else if (mShowing && !hideBouncerOverDream) {
+ } else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
&& !mCentralSurfaces.isInLaunchTransition()
&& !isUnlockCollapsing()) {
if (mBouncer != null) {
mBouncer.setExpansion(fraction);
+ } else {
+ mBouncerInteractor.setExpansion(fraction);
}
- mBouncerInteractor.setExpansion(fraction);
}
if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
&& !mKeyguardStateController.canDismissLockScreen()
@@ -432,16 +495,18 @@
&& !bouncerIsAnimatingAway()) {
if (mBouncer != null) {
mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
+ } else {
+ mBouncerInteractor.show(/* isScrimmed= */false);
}
- mBouncerInteractor.show(/* isScrimmed= */false);
}
- } else if (!mShowing && isBouncerInTransit()) {
+ } else if (!mKeyguardStateController.isShowing() && isBouncerInTransit()) {
// Keyguard is not visible anymore, but expansion animation was still running.
// We need to hide the bouncer, otherwise it will be stuck in transit.
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ } else {
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
// unlocked.) Let's simply wake-up to dismiss the lock screen.
@@ -467,9 +532,8 @@
@Override
public void show(Bundle options) {
Trace.beginSection("StatusBarKeyguardViewManager#show");
- mShowing = true;
mNotificationShadeWindowController.setKeyguardShowing(true);
- mKeyguardStateController.notifyKeyguardState(mShowing,
+ mKeyguardStateController.notifyKeyguardState(true,
mKeyguardStateController.isOccluded());
reset(true /* hideBouncerWhenShowing */);
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
@@ -487,8 +551,9 @@
mCentralSurfaces.hideKeyguard();
if (mBouncer != null) {
mBouncer.show(true /* resetSecuritySelection */);
+ } else {
+ mBouncerInteractor.show(true);
}
- mBouncerInteractor.show(true);
} else {
mCentralSurfaces.showKeyguard();
if (hideBouncerWhenShowing) {
@@ -529,9 +594,10 @@
void hideBouncer(boolean destroyView) {
if (mBouncer != null) {
mBouncer.hide(destroyView);
+ } else {
+ mBouncerInteractor.hide();
}
- mBouncerInteractor.hide();
- if (mShowing) {
+ if (mKeyguardStateController.isShowing()) {
// If we were showing the bouncer and then aborting, we need to also clear out any
// potential actions unless we actually unlocked.
cancelPostAuthActions();
@@ -548,11 +614,12 @@
public void showBouncer(boolean scrimmed) {
resetAlternateAuth(false);
- if (mShowing && !isBouncerShowing()) {
+ if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
if (mBouncer != null) {
mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+ } else {
+ mBouncerInteractor.show(scrimmed);
}
- mBouncerInteractor.show(scrimmed);
}
updateStates();
}
@@ -564,7 +631,7 @@
public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
boolean afterKeyguardGone, String message) {
- if (mShowing) {
+ if (mKeyguardStateController.isShowing()) {
try {
Trace.beginSection("StatusBarKeyguardViewManager#dismissWithAction");
cancelPendingWakeupAction();
@@ -588,9 +655,10 @@
if (mBouncer != null) {
mBouncer.setDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
+ } else {
+ mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
+ mKeyguardGoneCancelAction);
}
- mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
- mKeyguardGoneCancelAction);
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
}
@@ -603,17 +671,21 @@
if (afterKeyguardGone) {
// we'll handle the dismiss action after keyguard is gone, so just show the
// bouncer
- mBouncerInteractor.show(/* isScrimmed= */true);
- if (mBouncer != null) mBouncer.show(false /* resetSecuritySelection */);
+ if (mBouncer != null) {
+ mBouncer.show(false /* resetSecuritySelection */);
+ } else {
+ mBouncerInteractor.show(/* isScrimmed= */true);
+ }
} else {
// after authentication success, run dismiss action with the option to defer
// hiding the keyguard based on the return value of the OnDismissAction
- mBouncerInteractor.setDismissAction(
- mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
- mBouncerInteractor.show(/* isScrimmed= */true);
if (mBouncer != null) {
mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
+ } else {
+ mBouncerInteractor.setDismissAction(
+ mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
+ mBouncerInteractor.show(/* isScrimmed= */true);
}
// bouncer will handle the dismiss action, so we no longer need to track it here
mAfterKeyguardGoneAction = null;
@@ -645,11 +717,12 @@
@Override
public void reset(boolean hideBouncerWhenShowing) {
- if (mShowing) {
+ if (mKeyguardStateController.isShowing()) {
+ final boolean isOccluded = mKeyguardStateController.isOccluded();
// Hide quick settings.
- mNotificationPanelViewController.resetViews(/* animate= */ !mOccluded);
+ mNotificationPanelViewController.resetViews(/* animate= */ !isOccluded);
// Hide bouncer and quick-quick settings.
- if (mOccluded && !mDozing) {
+ if (isOccluded && !mDozing) {
mCentralSurfaces.hideKeyguard();
if (hideBouncerWhenShowing || needsFullscreenBouncer()) {
hideBouncer(false /* destroyView */);
@@ -717,8 +790,9 @@
public void onFinishedGoingToSleep() {
if (mBouncer != null) {
mBouncer.onScreenTurnedOff();
+ } else {
+ mBouncerInteractor.onScreenTurnedOff();
}
- mBouncerInteractor.onScreenTurnedOff();
}
@Override
@@ -730,14 +804,10 @@
private void setDozing(boolean dozing) {
if (mDozing != dozing) {
mDozing = dozing;
- if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
+ if (dozing || mBouncer.needsFullscreenBouncer()
+ || mKeyguardStateController.isOccluded()) {
reset(dozing /* hideBouncerWhenShowing */);
}
-
- if (bouncerIsOrWillBeShowing()) {
- // Ensure bouncer is not shown when dozing state changes.
- hideBouncer(false);
- }
updateStates();
if (!dozing) {
@@ -768,18 +838,23 @@
@Override
public void setOccluded(boolean occluded, boolean animate) {
- final boolean isOccluding = !mOccluded && occluded;
- final boolean isUnOccluding = mOccluded && !occluded;
- setOccludedAndUpdateStates(occluded);
+ final boolean wasOccluded = mKeyguardStateController.isOccluded();
+ final boolean isOccluding = !wasOccluded && occluded;
+ final boolean isUnOccluding = wasOccluded && !occluded;
+ mKeyguardStateController.notifyKeyguardState(
+ mKeyguardStateController.isShowing(), occluded);
+ updateStates();
+ final boolean isShowing = mKeyguardStateController.isShowing();
+ final boolean isOccluded = mKeyguardStateController.isOccluded();
- if (mShowing && isOccluding) {
+ if (isShowing && isOccluding) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
if (mCentralSurfaces.isInLaunchTransition()) {
final Runnable endRunnable = new Runnable() {
@Override
public void run() {
- mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
+ mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
reset(true /* hideBouncerWhenShowing */);
}
};
@@ -794,19 +869,19 @@
// When isLaunchingActivityOverLockscreen() is true, we know for sure that the post
// collapse runnables will be run.
mShadeController.get().addPostCollapseAction(() -> {
- mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
+ mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
reset(true /* hideBouncerWhenShowing */);
});
return;
}
- } else if (mShowing && isUnOccluding) {
+ } else if (isShowing && isUnOccluding) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
}
- if (mShowing) {
- mMediaManager.updateMediaMetaData(false, animate && !mOccluded);
+ if (isShowing) {
+ mMediaManager.updateMediaMetaData(false, animate && !isOccluded);
}
- mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
+ mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
// setDozing(false) will call reset once we stop dozing. Also, if we're going away, there's
// no need to reset the keyguard views as we'll be gone shortly. Resetting now could cause
@@ -816,27 +891,19 @@
// by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
reset(isOccluding /* hideBouncerWhenShowing*/);
}
- if (animate && !mOccluded && mShowing && !bouncerIsShowing()) {
+ if (animate && !isOccluded && isShowing && !bouncerIsShowing()) {
mCentralSurfaces.animateKeyguardUnoccluding();
}
}
- private void setOccludedAndUpdateStates(boolean occluded) {
- mOccluded = occluded;
- updateStates();
- }
-
- public boolean isOccluded() {
- return mOccluded;
- }
-
@Override
public void startPreHideAnimation(Runnable finishRunnable) {
if (bouncerIsShowing()) {
if (mBouncer != null) {
mBouncer.startPreHideAnimation(finishRunnable);
+ } else {
+ mBouncerInteractor.startDisappearAnimation(finishRunnable);
}
- mBouncerInteractor.startDisappearAnimation(finishRunnable);
mCentralSurfaces.onBouncerPreHideAnimation();
// We update the state (which will show the keyguard) only if an animation will run on
@@ -859,8 +926,7 @@
@Override
public void hide(long startTime, long fadeoutDuration) {
Trace.beginSection("StatusBarKeyguardViewManager#hide");
- mShowing = false;
- mKeyguardStateController.notifyKeyguardState(mShowing,
+ mKeyguardStateController.notifyKeyguardState(false,
mKeyguardStateController.isOccluded());
launchPendingWakeupAction();
@@ -1009,33 +1075,43 @@
KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
}
- @Override
- public boolean isShowing() {
- return mShowing;
+ /**
+ * Returns whether a back invocation can be handled, which depends on whether the keyguard
+ * is currently showing (which itself is derived from multiple states).
+ *
+ * @return whether a back press can be handled right now.
+ */
+ public boolean canHandleBackPressed() {
+ return mBouncer.isShowing();
}
/**
* Notifies this manager that the back button has been pressed.
- *
- * @param hideImmediately Hide bouncer when {@code true}, keep it around otherwise.
- * Non-scrimmed bouncers have a special animation tied to the expansion
- * of the notification panel.
- * @return whether the back press has been handled
*/
- public boolean onBackPressed(boolean hideImmediately) {
- if (bouncerIsShowing()) {
- mCentralSurfaces.endAffordanceLaunch();
- // The second condition is for SIM card locked bouncer
- if (bouncerIsScrimmed()
- && !needsFullscreenBouncer()) {
- hideBouncer(false);
- updateStates();
- } else {
- reset(hideImmediately);
- }
- return true;
+ public void onBackPressed() {
+ if (!canHandleBackPressed()) {
+ return;
}
- return false;
+
+ mCentralSurfaces.endAffordanceLaunch();
+ // The second condition is for SIM card locked bouncer
+ if (bouncerIsScrimmed() && needsFullscreenBouncer()) {
+ hideBouncer(false);
+ updateStates();
+ } else {
+ /* Non-scrimmed bouncers have a special animation tied to the expansion
+ * of the notification panel. We decide whether to kick this animation off
+ * by computing the hideImmediately boolean.
+ */
+ boolean hideImmediately = mCentralSurfaces.shouldKeyguardHideImmediately();
+ reset(hideImmediately);
+ if (hideImmediately) {
+ mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
+ } else {
+ mNotificationPanelViewController.expandWithoutQs();
+ }
+ }
+ return;
}
@Override
@@ -1094,8 +1170,11 @@
};
protected void updateStates() {
- boolean showing = mShowing;
- boolean occluded = mOccluded;
+ if (!mCentralSurfacesRegistered) {
+ return;
+ }
+ boolean showing = mKeyguardStateController.isShowing();
+ boolean occluded = mKeyguardStateController.isOccluded();
boolean bouncerShowing = bouncerIsShowing();
boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing();
boolean bouncerDismissible = !isFullscreenBouncer();
@@ -1107,13 +1186,15 @@
if (bouncerDismissible || !showing || remoteInputActive) {
if (mBouncer != null) {
mBouncer.setBackButtonEnabled(true);
+ } else {
+ mBouncerInteractor.setBackButtonEnabled(true);
}
- mBouncerInteractor.setBackButtonEnabled(true);
} else {
if (mBouncer != null) {
mBouncer.setBackButtonEnabled(false);
+ } else {
+ mBouncerInteractor.setBackButtonEnabled(false);
}
- mBouncerInteractor.setBackButtonEnabled(false);
}
}
@@ -1127,13 +1208,6 @@
mNotificationShadeWindowController.setBouncerShowing(bouncerShowing);
mCentralSurfaces.setBouncerShowing(bouncerShowing);
}
-
- if (occluded != mLastOccluded || mFirstUpdate) {
- mKeyguardStateController.notifyKeyguardState(showing, occluded);
- }
- if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
- mKeyguardUpdateManager.onKeyguardVisibilityChanged(showing && !occluded);
- }
if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate
|| bouncerShowing != mLastBouncerShowing) {
mKeyguardUpdateManager.sendKeyguardBouncerChanged(bouncerIsOrWillBeShowing,
@@ -1184,12 +1258,12 @@
public boolean isNavBarVisible() {
boolean isWakeAndUnlockPulsing = mBiometricUnlockController != null
&& mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;
- boolean keyguardShowing = mShowing && !mOccluded;
+ boolean keyguardVisible = mKeyguardStateController.isVisible();
boolean hideWhileDozing = mDozing && !isWakeAndUnlockPulsing;
- boolean keyguardWithGestureNav = (keyguardShowing && !mDozing && !mScreenOffAnimationPlaying
+ boolean keyguardWithGestureNav = (keyguardVisible && !mDozing && !mScreenOffAnimationPlaying
|| mPulsing && !mIsDocked)
&& mGesturalNav;
- return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying
+ return (!keyguardVisible && !hideWhileDozing && !mScreenOffAnimationPlaying
|| bouncerIsShowing()
|| mRemoteInputActive
|| keyguardWithGestureNav
@@ -1279,8 +1353,9 @@
public void notifyKeyguardAuthenticated(boolean strongAuth) {
if (mBouncer != null) {
mBouncer.notifyKeyguardAuthenticated(strongAuth);
+ } else {
+ mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
}
- mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
resetAlternateAuth(false);
@@ -1297,21 +1372,30 @@
} else {
if (mBouncer != null) {
mBouncer.showMessage(message, colorState);
+ } else {
+ mBouncerInteractor.showMessage(message, colorState);
}
- mBouncerInteractor.showMessage(message, colorState);
}
}
@Override
public ViewRootImpl getViewRootImpl() {
- return mNotificationShadeWindowController.getNotificationShadeView().getViewRootImpl();
+ ViewGroup viewGroup = mNotificationShadeWindowController.getNotificationShadeView();
+ if (viewGroup != null) {
+ return viewGroup.getViewRootImpl();
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "ViewGroup was null, cannot get ViewRootImpl");
+ }
+ return null;
+ }
}
public void launchPendingWakeupAction() {
DismissWithActionRequest request = mPendingWakeupAction;
mPendingWakeupAction = null;
if (request != null) {
- if (mShowing) {
+ if (mKeyguardStateController.isShowing()) {
dismissWithAction(request.dismissAction, request.cancelAction,
request.afterKeyguardGone, request.message);
} else if (request.dismissAction != null) {
@@ -1330,10 +1414,10 @@
public boolean bouncerNeedsScrimming() {
// When a dream overlay is active, scrimming will cause any expansion to immediately expand.
- return (mOccluded && !mDreamOverlayStateController.isOverlayActive())
+ return (mKeyguardStateController.isOccluded()
+ && !mDreamOverlayStateController.isOverlayActive())
|| bouncerWillDismissWithAction()
- || (bouncerIsShowing()
- && bouncerIsScrimmed())
+ || (bouncerIsShowing() && bouncerIsScrimmed())
|| isFullscreenBouncer();
}
@@ -1345,14 +1429,13 @@
public void updateResources() {
if (mBouncer != null) {
mBouncer.updateResources();
+ } else {
+ mBouncerInteractor.updateResources();
}
- mBouncerInteractor.updateResources();
}
public void dump(PrintWriter pw) {
pw.println("StatusBarKeyguardViewManager:");
- pw.println(" mShowing: " + mShowing);
- pw.println(" mOccluded: " + mOccluded);
pw.println(" mRemoteInputActive: " + mRemoteInputActive);
pw.println(" mDozing: " + mDozing);
pw.println(" mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction);
@@ -1431,9 +1514,9 @@
public void updateKeyguardPosition(float x) {
if (mBouncer != null) {
mBouncer.updateKeyguardPosition(x);
+ } else {
+ mBouncerInteractor.setKeyguardPosition(x);
}
-
- mBouncerInteractor.setKeyguardPosition(x);
}
private static class DismissWithActionRequest {
@@ -1475,9 +1558,9 @@
public boolean isBouncerInTransit() {
if (mBouncer != null) {
return mBouncer.inTransit();
+ } else {
+ return mBouncerInteractor.isInTransit();
}
-
- return mBouncerInteractor.isInTransit();
}
/**
@@ -1486,9 +1569,9 @@
public boolean bouncerIsShowing() {
if (mBouncer != null) {
return mBouncer.isShowing();
+ } else {
+ return mBouncerInteractor.isFullyShowing();
}
-
- return mBouncerInteractor.isFullyShowing();
}
/**
@@ -1497,9 +1580,9 @@
public boolean bouncerIsScrimmed() {
if (mBouncer != null) {
return mBouncer.isScrimmed();
+ } else {
+ return mBouncerInteractor.isScrimmed();
}
-
- return mBouncerInteractor.isScrimmed();
}
/**
@@ -1508,9 +1591,10 @@
public boolean bouncerIsAnimatingAway() {
if (mBouncer != null) {
return mBouncer.isAnimatingAway();
+ } else {
+ return mBouncerInteractor.isAnimatingAway();
}
- return mBouncerInteractor.isAnimatingAway();
}
/**
@@ -1519,9 +1603,9 @@
public boolean bouncerWillDismissWithAction() {
if (mBouncer != null) {
return mBouncer.willDismissWithAction();
+ } else {
+ return mBouncerInteractor.willDismissWithAction();
}
-
- return mBouncerInteractor.willDismissWithAction();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index d464acb..26c1767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -337,7 +337,7 @@
// without cutting off the child view.
translationX -= getViewTotalWidth(child);
childState.visibleState = STATE_ICON;
- childState.xTranslation = translationX;
+ childState.setXTranslation(translationX);
mLayoutStates.add(0, childState);
// Shift translationX over by mIconSpacing for the next view.
@@ -354,13 +354,13 @@
for (int i = totalVisible - 1; i >= 0; i--) {
StatusIconState state = mLayoutStates.get(i);
// Allow room for underflow if we found we need it in onMeasure
- if (mNeedsUnderflow && (state.xTranslation < (contentStart + mUnderflowWidth))||
- (mShouldRestrictIcons && visible >= maxVisible)) {
+ if (mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth))
+ || (mShouldRestrictIcons && (visible >= maxVisible))) {
firstUnderflowIndex = i;
break;
}
mUnderflowStart = (int) Math.max(
- contentStart, state.xTranslation - mUnderflowWidth - mIconSpacing);
+ contentStart, state.getXTranslation() - mUnderflowWidth - mIconSpacing);
visible++;
}
@@ -371,7 +371,7 @@
for (int i = firstUnderflowIndex; i >= 0; i--) {
StatusIconState state = mLayoutStates.get(i);
if (totalDots < MAX_DOTS) {
- state.xTranslation = dotOffset;
+ state.setXTranslation(dotOffset);
state.visibleState = STATE_DOT;
dotOffset -= dotWidth;
totalDots++;
@@ -386,7 +386,7 @@
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
StatusIconState state = getViewStateFromChild(child);
- state.xTranslation = width - state.xTranslation - child.getWidth();
+ state.setXTranslation(width - state.getXTranslation() - child.getWidth());
}
}
}
@@ -410,7 +410,7 @@
}
vs.initFrom(child);
- vs.alpha = 1.0f;
+ vs.setAlpha(1.0f);
vs.hidden = false;
}
}
@@ -442,7 +442,7 @@
parentWidth = ((View) view.getParent()).getWidth();
}
- float currentDistanceToEnd = parentWidth - xTranslation;
+ float currentDistanceToEnd = parentWidth - getXTranslation();
if (!(view instanceof StatusIconDisplayable)) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 627cfb7..dc90266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -41,6 +41,7 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationsQuickSettingsContainer;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
@@ -64,7 +65,6 @@
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -284,7 +284,7 @@
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
NotificationIconAreaController notificationIconAreaController,
- PanelExpansionStateManager panelExpansionStateManager,
+ ShadeExpansionStateManager shadeExpansionStateManager,
FeatureFlags featureFlags,
StatusBarIconController statusBarIconController,
StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
@@ -306,7 +306,7 @@
animationScheduler,
locationPublisher,
notificationIconAreaController,
- panelExpansionStateManager,
+ shadeExpansionStateManager,
featureFlags,
statusBarIconController,
darkIconManagerFactory,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index e1215ee..9f3fd72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -53,6 +53,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.OperatorNameView;
@@ -74,7 +75,6 @@
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.EncryptionHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.CarrierConfigTracker;
@@ -127,7 +127,7 @@
private final StatusBarLocationPublisher mLocationPublisher;
private final FeatureFlags mFeatureFlags;
private final NotificationIconAreaController mNotificationIconAreaController;
- private final PanelExpansionStateManager mPanelExpansionStateManager;
+ private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
private final CarrierConfigTracker mCarrierConfigTracker;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@@ -184,7 +184,7 @@
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
NotificationIconAreaController notificationIconAreaController,
- PanelExpansionStateManager panelExpansionStateManager,
+ ShadeExpansionStateManager shadeExpansionStateManager,
FeatureFlags featureFlags,
StatusBarIconController statusBarIconController,
StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
@@ -206,7 +206,7 @@
mAnimationScheduler = animationScheduler;
mLocationPublisher = locationPublisher;
mNotificationIconAreaController = notificationIconAreaController;
- mPanelExpansionStateManager = panelExpansionStateManager;
+ mShadeExpansionStateManager = shadeExpansionStateManager;
mFeatureFlags = featureFlags;
mStatusBarIconController = statusBarIconController;
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
@@ -490,7 +490,7 @@
}
private boolean shouldHideNotificationIcons() {
- if (!mPanelExpansionStateManager.isClosed()
+ if (!mShadeExpansionStateManager.isClosed()
&& mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) {
return true;
}
@@ -536,7 +536,7 @@
* don't set the clock GONE otherwise it'll mess up the animation.
*/
private int clockHiddenMode() {
- if (!mPanelExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing()
+ if (!mShadeExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing()
&& !mStatusBarStateController.isDozing()) {
return View.INVISIBLE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 9a7c3fa..06d5542 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,10 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
@@ -30,4 +34,12 @@
@Binds
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+
+ @Binds
+ abstract fun mobileSubscriptionRepository(
+ impl: MobileSubscriptionRepositoryImpl
+ ): MobileSubscriptionRepository
+
+ @Binds
+ abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
new file mode 100644
index 0000000..46ccf32c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+import android.annotation.IntRange
+import android.telephony.Annotation.DataActivityType
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+
+/**
+ * Data class containing all of the relevant information for a particular line of service, known as
+ * a Subscription in the telephony world. These models are the result of a single telephony listener
+ * which has many callbacks which each modify some particular field on this object.
+ *
+ * The design goal here is to de-normalize fields from the system into our model fields below. So
+ * any new field that needs to be tracked should be copied into this data class rather than
+ * threading complex system objects through the pipeline.
+ */
+data class MobileSubscriptionModel(
+ /** From [ServiceStateListener.onServiceStateChanged] */
+ val isEmergencyOnly: Boolean = false,
+
+ /** From [SignalStrengthsListener.onSignalStrengthsChanged] */
+ val isGsm: Boolean = false,
+ @IntRange(from = 0, to = 4)
+ val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+ @IntRange(from = 0, to = 4)
+ val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+
+ /** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */
+ val dataConnectionState: Int? = null,
+
+ /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
+ @DataActivityType val dataActivityDirection: Int? = null,
+
+ /** From [CarrierNetworkListener.onCarrierNetworkChange] */
+ val carrierNetworkChangeActive: Boolean? = null,
+
+ /** From [DisplayInfoListener.onDisplayInfoChanged] */
+ val displayInfo: TelephonyDisplayInfo? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
new file mode 100644
index 0000000..36de2a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
+ * on various policy
+ */
+interface MobileSubscriptionRepository {
+ /** Observable list of current mobile subscriptions */
+ val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+
+ /** Observable for the subscriptionId of the current mobile data connection */
+ val activeMobileDataSubscriptionId: Flow<Int>
+
+ /** Get or create an observable for the given subscription ID */
+ fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileSubscriptionRepositoryImpl
+@Inject
+constructor(
+ private val subscriptionManager: SubscriptionManager,
+ private val telephonyManager: TelephonyManager,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+) : MobileSubscriptionRepository {
+ private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
+
+ /**
+ * State flow that emits the set of mobile data subscriptions, each represented by its own
+ * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+ * info object, but for now we keep track of the infos themselves.
+ */
+ override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
+ conflatedCallbackFlow {
+ val callback =
+ object : SubscriptionManager.OnSubscriptionsChangedListener() {
+ override fun onSubscriptionsChanged() {
+ trySend(Unit)
+ }
+ }
+
+ subscriptionManager.addOnSubscriptionsChangedListener(
+ bgDispatcher.asExecutor(),
+ callback,
+ )
+
+ awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+ }
+ .mapLatest { fetchSubscriptionsList() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+ /** StateFlow that keeps track of the current active mobile data subscription */
+ override val activeMobileDataSubscriptionId: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val callback =
+ object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+ override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+ trySend(subId)
+ }
+ }
+
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ )
+
+ /**
+ * Each mobile subscription needs its own flow, which comes from registering listeners on the
+ * system. Use this method to create those flows and cache them for reuse
+ */
+ override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
+ return subIdFlowCache[subId]
+ ?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
+ }
+
+ @VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
+
+ private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
+ var state = MobileSubscriptionModel()
+ conflatedCallbackFlow {
+ val phony = telephonyManager.createForSubscriptionId(subId)
+ // TODO (b/240569788): log all of these into the connectivity logger
+ val callback =
+ object :
+ TelephonyCallback(),
+ ServiceStateListener,
+ SignalStrengthsListener,
+ DataConnectionStateListener,
+ DataActivityListener,
+ CarrierNetworkListener,
+ DisplayInfoListener {
+ override fun onServiceStateChanged(serviceState: ServiceState) {
+ state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+ trySend(state)
+ }
+ override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+ val cdmaLevel =
+ signalStrength
+ .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
+ .let { strengths ->
+ if (!strengths.isEmpty()) {
+ strengths[0].level
+ } else {
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+ }
+
+ val primaryLevel = signalStrength.level
+
+ state =
+ state.copy(
+ cdmaLevel = cdmaLevel,
+ primaryLevel = primaryLevel,
+ isGsm = signalStrength.isGsm,
+ )
+ trySend(state)
+ }
+ override fun onDataConnectionStateChanged(
+ dataState: Int,
+ networkType: Int
+ ) {
+ state = state.copy(dataConnectionState = dataState)
+ trySend(state)
+ }
+ override fun onDataActivity(direction: Int) {
+ state = state.copy(dataActivityDirection = direction)
+ trySend(state)
+ }
+ override fun onCarrierNetworkChange(active: Boolean) {
+ state = state.copy(carrierNetworkChangeActive = active)
+ trySend(state)
+ }
+ override fun onDisplayInfoChanged(
+ telephonyDisplayInfo: TelephonyDisplayInfo
+ ) {
+ state = state.copy(displayInfo = telephonyDisplayInfo)
+ trySend(state)
+ }
+ }
+ phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose {
+ phony.unregisterTelephonyCallback(callback)
+ // Release the cached flow
+ subIdFlowCache.remove(subId)
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+ }
+
+ private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+ withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
new file mode 100644
index 0000000..77de849
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository to observe the state of [DeviceProvisionedController.isUserSetup]. This information
+ * can change some policy related to display
+ */
+interface UserSetupRepository {
+ /** Observable tracking [DeviceProvisionedController.isUserSetup] */
+ val isUserSetupFlow: Flow<Boolean>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class UserSetupRepositoryImpl
+@Inject
+constructor(
+ private val deviceProvisionedController: DeviceProvisionedController,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application scope: CoroutineScope,
+) : UserSetupRepository {
+ /** State flow that tracks [DeviceProvisionedController.isUserSetup] */
+ override val isUserSetupFlow: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onUserSetupChanged() {
+ trySend(Unit)
+ }
+ }
+
+ deviceProvisionedController.addCallback(callback)
+
+ awaitClose { deviceProvisionedController.removeCallback(callback) }
+ }
+ .onStart { emit(Unit) }
+ .mapLatest { fetchUserSetupState() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+ private suspend fun fetchUserSetupState(): Boolean =
+ withContext(bgDispatcher) { deviceProvisionedController.isCurrentUserSetup }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
new file mode 100644
index 0000000..40fe0f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.CarrierConfigTracker
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+interface MobileIconInteractor {
+ /** Identifier for RAT type indicator */
+ val iconGroup: Flow<SignalIcon.MobileIconGroup>
+ /** True if this line of service is emergency-only */
+ val isEmergencyOnly: Flow<Boolean>
+ /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
+ val level: Flow<Int>
+ /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
+ val numberOfLevels: Flow<Int>
+ /** True when we want to draw an icon that makes room for the exclamation mark */
+ val cutOut: Flow<Boolean>
+}
+
+/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+class MobileIconInteractorImpl(
+ mobileStatusInfo: Flow<MobileSubscriptionModel>,
+) : MobileIconInteractor {
+ override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
+ override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
+
+ override val level: Flow<Int> =
+ mobileStatusInfo.map { mobileModel ->
+ // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+ if (mobileModel.isGsm) {
+ mobileModel.primaryLevel
+ } else {
+ mobileModel.cdmaLevel
+ }
+ }
+
+ /**
+ * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
+ * once it's wired up inside of [CarrierConfigTracker]
+ */
+ override val numberOfLevels: Flow<Int> = flowOf(4)
+
+ /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
+ // TODO: find a better name for this?
+ override val cutOut: Flow<Boolean> = flowOf(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
new file mode 100644
index 0000000..8e67e19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Business layer logic for mobile subscription icons
+ *
+ * Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
+ * that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
+ */
+@SysUISingleton
+class MobileIconsInteractor
+@Inject
+constructor(
+ private val mobileSubscriptionRepo: MobileSubscriptionRepository,
+ private val carrierConfigTracker: CarrierConfigTracker,
+ userSetupRepo: UserSetupRepository,
+) {
+ private val activeMobileDataSubscriptionId =
+ mobileSubscriptionRepo.activeMobileDataSubscriptionId
+
+ private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ mobileSubscriptionRepo.subscriptionsFlow
+
+ /**
+ * Generally, SystemUI wants to show iconography for each subscription that is listed by
+ * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
+ * show a single representation of the pair of subscriptions. The docs define opportunistic as:
+ *
+ * "A subscription is opportunistic (if) the network it connects to has limited coverage"
+ * https://developer.android.com/reference/android/telephony/SubscriptionManager#setOpportunistic(boolean,%20int)
+ *
+ * In the case of opportunistic networks (typically CBRS), we will filter out one of the
+ * subscriptions based on
+ * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
+ * and by checking which subscription is opportunistic, or which one is active.
+ */
+ val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
+ ->
+ // Based on the old logic,
+ if (unfilteredSubs.size != 2) {
+ return@combine unfilteredSubs
+ }
+
+ val info1 = unfilteredSubs[0]
+ val info2 = unfilteredSubs[1]
+ // If both subscriptions are primary, show both
+ if (!info1.isOpportunistic && !info2.isOpportunistic) {
+ return@combine unfilteredSubs
+ }
+
+ // NOTE: at this point, we are now returning a single SubscriptionInfo
+
+ // If carrier required, always show the icon of the primary subscription.
+ // Otherwise, show whichever subscription is currently active for internet.
+ if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
+ // return the non-opportunistic info
+ return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
+ } else {
+ return@combine if (info1.subscriptionId == activeId) {
+ listOf(info1)
+ } else {
+ listOf(info2)
+ }
+ }
+ }
+
+ val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+
+ /** Vends out new [MobileIconInteractor] for a particular subId */
+ fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
+
+ /**
+ * Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
+ */
+ private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
+ mobileSubscriptionRepo.getFlowForSubId(subId)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
new file mode 100644
index 0000000..380017c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -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.statusbar.pipeline.mobile.ui
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * This class is intended to provide a context to collect on the
+ * [MobileIconsInteractor.filteredSubscriptions] data source and supply a state flow that can
+ * control [StatusBarIconController] to keep the old UI in sync with the new data source.
+ *
+ * It also provides a mechanism to create a top-level view model for each IconManager to know about
+ * the list of available mobile lines of service for which we want to show icons.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileUiAdapter
+@Inject
+constructor(
+ interactor: MobileIconsInteractor,
+ private val iconController: StatusBarIconController,
+ private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+ @Application scope: CoroutineScope,
+) {
+ private val mobileSubIds: Flow<List<Int>> =
+ interactor.filteredSubscriptions.mapLatest { infos ->
+ infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
+ }
+
+ /**
+ * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
+ * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
+ * house the mobile infos.
+ *
+ * NOTE: this should go away as the view presenter learns more about this data pipeline
+ */
+ private val mobileSubIdsState: StateFlow<List<Int>> =
+ mobileSubIds
+ .onEach {
+ // Notify the icon controller here so that it knows to add icons
+ iconController.setNewMobileIconSubIds(it)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+ /**
+ * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
+ * lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
+ * the old view system.
+ */
+ fun createMobileIconsViewModel(): MobileIconsViewModel =
+ iconsViewModelFactory.create(mobileSubIdsState)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
new file mode 100644
index 0000000..1405b05
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.R
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+
+object MobileIconBinder {
+ /** Binds the view to the view-model, continuing to update the former based on the latter */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: MobileIconViewModel,
+ ) {
+ val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
+ val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+
+ view.isVisible = true
+ iconView.isVisible = true
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // Set the icon for the triangle
+ launch {
+ viewModel.iconId.distinctUntilChanged().collect { iconId ->
+ mobileDrawable.level = iconId
+ }
+ }
+
+ // Set the tint
+ launch {
+ viewModel.tint.collect { tint ->
+ iconView.imageTintList = ColorStateList.valueOf(tint)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
new file mode 100644
index 0000000..e7d5ee2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.binder
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+object MobileIconsBinder {
+ /**
+ * Start this ViewModel collecting on the list of mobile subscriptions in the scope of [view]
+ * which is passed in and managed by [IconManager]. Once the subscription list flow starts
+ * collecting, [MobileUiAdapter] will send updates to the icon manager.
+ */
+ @JvmStatic
+ fun bind(view: View, viewModel: MobileIconsViewModel) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.subscriptionIdsFlow.collect {
+ // TODO(b/249790733): This is an empty collect, because [MobileUiAdapter]
+ // sets up a side-effect in this flow to trigger the methods on
+ // [StatusBarIconController] which allows for this pipeline to be a data
+ // source for the mobile icons.
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
new file mode 100644
index 0000000..ec4fa9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.view
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import com.android.systemui.R
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import java.util.ArrayList
+
+class ModernStatusBarMobileView(
+ context: Context,
+ attrs: AttributeSet?,
+) : BaseStatusBarFrameLayout(context, attrs) {
+
+ private lateinit var slot: String
+ override fun getSlot() = slot
+
+ override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+ // TODO
+ }
+
+ override fun setStaticDrawableColor(color: Int) {
+ // TODO
+ }
+
+ override fun setDecorColor(color: Int) {
+ // TODO
+ }
+
+ override fun setVisibleState(state: Int, animate: Boolean) {
+ // TODO
+ }
+
+ override fun getVisibleState(): Int {
+ return STATE_ICON
+ }
+
+ override fun isIconVisible(): Boolean {
+ return true
+ }
+
+ companion object {
+
+ /**
+ * Inflates a new instance of [ModernStatusBarMobileView], binds it to [viewModel], and
+ * returns it.
+ */
+ @JvmStatic
+ fun constructAndBind(
+ context: Context,
+ slot: String,
+ viewModel: MobileIconViewModel,
+ ): ModernStatusBarMobileView {
+ return (LayoutInflater.from(context)
+ .inflate(R.layout.status_bar_mobile_signal_group_new, null)
+ as ModernStatusBarMobileView)
+ .also {
+ it.slot = slot
+ MobileIconBinder.bind(it, viewModel)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
new file mode 100644
index 0000000..cfabeba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import android.graphics.Color
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
+ * a single line of service via [MobileIconInteractor] and update the UI based on that
+ * subscription's information.
+ *
+ * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
+ * [MobileIconsInteractor.filteredSubscriptions]
+ *
+ * TODO: figure out where carrier merged and VCN models go (probably here?)
+ */
+class MobileIconViewModel
+constructor(
+ val subscriptionId: Int,
+ iconInteractor: MobileIconInteractor,
+ logger: ConnectivityPipelineLogger,
+) {
+ /** An int consumable by [SignalDrawable] for display */
+ var iconId: Flow<Int> =
+ combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+ level,
+ numberOfLevels,
+ cutOut ->
+ SignalDrawable.getState(level, numberOfLevels, cutOut)
+ }
+ .distinctUntilChanged()
+ .logOutputChange(logger, "iconId($subscriptionId)")
+
+ var tint: Flow<Int> = flowOf(Color.CYAN)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
new file mode 100644
index 0000000..24c1db9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(InternalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * View model for describing the system's current mobile cellular connections. The result is a list
+ * of [MobileIconViewModel]s which describe the individual icons and can be bound to
+ * [ModernStatusBarMobileView]
+ */
+class MobileIconsViewModel
+@Inject
+constructor(
+ val subscriptionIdsFlow: Flow<List<Int>>,
+ private val interactor: MobileIconsInteractor,
+ private val logger: ConnectivityPipelineLogger,
+) {
+ /** TODO: do we need to cache these? */
+ fun viewModelForSub(subId: Int): MobileIconViewModel =
+ MobileIconViewModel(
+ subId,
+ interactor.createMobileConnectionInteractorForSubId(subId),
+ logger
+ )
+
+ class Factory
+ @Inject
+ constructor(
+ private val interactor: MobileIconsInteractor,
+ private val logger: ConnectivityPipelineLogger,
+ ) {
+ fun create(subscriptionIdsFlow: Flow<List<Int>>): MobileIconsViewModel {
+ return MobileIconsViewModel(
+ subscriptionIdsFlow,
+ interactor,
+ logger,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index c96faab..062c3d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -21,7 +21,9 @@
/** Provides information about the current wifi network. */
sealed class WifiNetworkModel {
/** A model representing that we have no active wifi network. */
- object Inactive : WifiNetworkModel()
+ object Inactive : WifiNetworkModel() {
+ override fun toString() = "WifiNetwork.Inactive"
+ }
/**
* A model representing that our wifi network is actually a carrier merged network, meaning it's
@@ -29,7 +31,9 @@
*
* See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
*/
- object CarrierMerged : WifiNetworkModel()
+ object CarrierMerged : WifiNetworkModel() {
+ override fun toString() = "WifiNetwork.CarrierMerged"
+ }
/** Provides information about an active wifi network. */
data class Active(
@@ -72,6 +76,24 @@
}
}
+ override fun toString(): String {
+ // Only include the passpoint-related values in the string if we have them. (Most
+ // networks won't have them so they'll be mostly clutter.)
+ val passpointString =
+ if (isPasspointAccessPoint ||
+ isOnlineSignUpForPasspointAccessPoint ||
+ passpointProviderFriendlyName != null) {
+ ", isPasspointAp=$isPasspointAccessPoint, " +
+ "isOnlineSignUpForPasspointAp=$isOnlineSignUpForPasspointAccessPoint, " +
+ "passpointName=$passpointProviderFriendlyName"
+ } else {
+ ""
+ }
+
+ return "WifiNetworkModel.Active(networkId=$networkId, isValidated=$isValidated, " +
+ "level=$level, ssid=$ssid$passpointString)"
+ }
+
companion object {
@VisibleForTesting
internal const val MIN_VALID_LEVEL = 0
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 6c616ac..0cd9bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -22,7 +22,7 @@
import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
-import com.android.systemui.statusbar.BaseStatusBarWifiView
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
@@ -37,7 +37,7 @@
class ModernStatusBarWifiView(
context: Context,
attrs: AttributeSet?
-) : BaseStatusBarWifiView(context, attrs) {
+) : BaseStatusBarFrameLayout(context, attrs) {
private lateinit var slot: String
private lateinit var binding: WifiViewBinder.Binding
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java
new file mode 100644
index 0000000..ba5fa1d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.PowerManager;
+
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.power.EnhancedEstimates;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * com.android.systemui.statusbar.policy related providers that others may want to override.
+ */
+@Module
+public class AospPolicyModule {
+ @Provides
+ @SysUISingleton
+ static BatteryController provideBatteryController(
+ Context context,
+ EnhancedEstimates enhancedEstimates,
+ PowerManager powerManager,
+ BroadcastDispatcher broadcastDispatcher,
+ DemoModeController demoModeController,
+ DumpManager dumpManager,
+ @Main Handler mainHandler,
+ @Background Handler bgHandler) {
+ BatteryController bC = new BatteryControllerImpl(
+ context,
+ enhancedEstimates,
+ powerManager,
+ broadcastDispatcher,
+ demoModeController,
+ dumpManager,
+ mainHandler,
+ bgHandler);
+ bC.init();
+ return bC;
+ }
+}
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 5b2d695..2f0ebf7 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 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 @@
item.isGuest,
item.isAddSupervisedUser,
isTablet,
+ item.isManageUsers,
)
return checkNotNull(context.getDrawable(iconRes))
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index f2ee858..21a8300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -107,7 +107,7 @@
boolean allSimsMissing = true;
CharSequence displayText = null;
- List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+ List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo();
final int N = subs.size();
for (int i = 0; i < N; i++) {
int subId = subs.get(i).getSubscriptionId();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 250d9d4..1ae1eae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -35,9 +35,16 @@
}
/**
- * If the lock screen is visible.
- * The keyguard is also visible when the device is asleep or in always on mode, except when
- * the screen timed out and the user can unlock by quickly pressing power.
+ * If the keyguard is visible. This is unrelated to being locked or not.
+ */
+ default boolean isVisible() {
+ return isShowing() && !isOccluded();
+ }
+
+ /**
+ * If the keyguard is showing. This includes when it's occluded by an activity, and when
+ * the device is asleep or in always on mode, except when the screen timed out and the user
+ * can unlock by quickly pressing power.
*
* This is unrelated to being locked or not.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index f4d08e0..cc6fdcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -181,6 +181,7 @@
if (mShowing == showing && mOccluded == occluded) return;
mShowing = showing;
mOccluded = occluded;
+ mKeyguardUpdateMonitor.setKeyguardShowing(showing, occluded);
Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events",
"Keyguard showing: " + showing + " occluded: " + occluded);
notifyKeyguardChanged();
@@ -387,6 +388,8 @@
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardStateController:");
+ pw.println(" mShowing: " + mShowing);
+ pw.println(" mOccluded: " + mOccluded);
pw.println(" mSecure: " + mSecure);
pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
pw.println(" mTrustManaged: " + mTrustManaged);
@@ -435,7 +438,7 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
update(false /* updateAlways */);
}
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 712953e..c150654 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.util.ViewController;
import java.util.ArrayList;
+import java.util.List;
import javax.inject.Inject;
@@ -91,11 +92,11 @@
private final KeyguardUpdateMonitorCallback mInfoCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", visible));
// Any time the keyguard is hidden, try to close the user switcher menu to
// restore keyguard to the default state
- if (!showing) {
+ if (!visible) {
closeSwitcherIfOpenAndNotSimple(false);
}
}
@@ -456,7 +457,7 @@
}
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 1692656..935fc7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
@@ -30,7 +30,6 @@
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import dagger.Lazy
import java.io.PrintWriter
@@ -118,7 +117,7 @@
dialogShower: UserSwitchDialogController.DialogShower?
) {
if (useInteractor) {
- userInteractor.selectUser(userId)
+ userInteractor.selectUser(userId, dialogShower)
} else {
_oldImpl.onUserSelected(userId, dialogShower)
}
@@ -203,11 +202,7 @@
dialogShower: UserSwitchDialogController.DialogShower?,
) {
if (useInteractor) {
- if (LegacyUserDataHelper.isUser(record)) {
- userInteractor.selectUser(record.resolveId())
- } else {
- userInteractor.executeAction(LegacyUserDataHelper.toUserActionModel(record))
- }
+ userInteractor.onRecordSelected(record, dialogShower)
} else {
_oldImpl.onUserListItemClicked(record, dialogShower)
}
@@ -254,7 +249,7 @@
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 46d2f3a..c294c37 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.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 @@
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 @@
&& 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 @@
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 @@
@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 09298b6..b1b8341 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 @@
* 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 c199744..b938c90 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 @@
*/
@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 @@
.apply {
vpnName?.let {
setContentText(
- mContext.getString(
+ context.getString(
R.string.notification_disclosure_vpn_text, it
)
)
@@ -111,23 +111,23 @@
.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 @@
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 8026ba5..b92725b 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 @@
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 @@
/**
* 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 @@
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 892fedc..dbbd0b8 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 @@
* 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 a52e2af..d5d904c 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -20,17 +20,19 @@
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.PowerManager
import android.os.SystemClock
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
-import androidx.annotation.CallSuper
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -60,7 +62,7 @@
@LayoutRes private val viewLayoutRes: Int,
private val windowTitle: String,
private val wakeReason: String,
-) {
+) : CoreStartable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
* all subclasses.
@@ -70,7 +72,8 @@
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
- flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = windowTitle
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
@@ -84,11 +87,8 @@
*/
internal abstract val windowLayoutParams: WindowManager.LayoutParams
- /** The view currently being displayed. Null if the view is not being displayed. */
- private var view: ViewGroup? = null
-
- /** The info currently being displayed. Null if the view is not being displayed. */
- internal var info: T? = null
+ /** A container for all the display-related objects. Null if the view is not being displayed. */
+ private var displayInfo: DisplayInfo? = null
/** A [Runnable] that, when run, will cancel the pending timeout of the view. */
private var cancelViewTimeout: Runnable? = null
@@ -100,10 +100,11 @@
* display the correct information in the view.
*/
fun displayView(newInfo: T) {
- val currentView = view
+ val currentDisplayInfo = displayInfo
- if (currentView != null) {
- updateView(newInfo, currentView)
+ if (currentDisplayInfo != null) {
+ currentDisplayInfo.info = newInfo
+ updateView(currentDisplayInfo.info, currentDisplayInfo.view)
} else {
// The view is new, so set up all our callbacks and inflate the view
configurationController.addCallback(displayScaleListener)
@@ -130,7 +131,7 @@
)
cancelViewTimeout?.run()
cancelViewTimeout = mainExecutor.executeDelayed(
- { removeView(TemporaryDisplayRemovalReason.REASON_TIMEOUT) },
+ { removeView(REMOVAL_REASON_TIMEOUT) },
timeout.toLong()
)
}
@@ -140,19 +141,24 @@
val newView = LayoutInflater
.from(context)
.inflate(viewLayoutRes, null) as ViewGroup
- view = newView
- updateView(newInfo, newView)
+ val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
+ newViewController.init()
+
+ // We don't need to hold on to the view controller since we never set anything additional
+ // on it -- it will be automatically cleaned up when the view is detached.
+ val newDisplayInfo = DisplayInfo(newView, newInfo)
+ displayInfo = newDisplayInfo
+ updateView(newDisplayInfo.info, newDisplayInfo.view)
windowManager.addView(newView, windowLayoutParams)
animateViewIn(newView)
}
/** Removes then re-inflates the view. */
private fun reinflateView() {
- val currentInfo = info
- if (view == null || currentInfo == null) { return }
+ val currentViewInfo = displayInfo ?: return
- windowManager.removeView(view)
- inflateAndUpdateView(currentInfo)
+ windowManager.removeView(currentViewInfo.view)
+ inflateAndUpdateView(currentViewInfo.info)
}
private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
@@ -167,13 +173,18 @@
* @param removalReason a short string describing why the view was removed (timeout, state
* change, etc.)
*/
- open fun removeView(removalReason: String) {
- if (view == null) { return }
+ fun removeView(removalReason: String) {
+ val currentDisplayInfo = displayInfo ?: return
+
+ val currentView = currentDisplayInfo.view
+ animateViewOut(currentView) { windowManager.removeView(currentView) }
+
logger.logChipRemoval(removalReason)
configurationController.removeCallback(displayScaleListener)
- windowManager.removeView(view)
- view = null
- info = null
+ // Re-set to null immediately (instead as part of the animation end runnable) so
+ // that if a new view event comes in while this view is animating out, we still display the
+ // new view appropriately.
+ displayInfo = null
// No need to time the view out since it's already gone
cancelViewTimeout?.run()
}
@@ -181,22 +192,41 @@
/**
* A method implemented by subclasses to update [currentView] based on [newInfo].
*/
- @CallSuper
- open fun updateView(newInfo: T, currentView: ViewGroup) {
- info = newInfo
- }
+ abstract fun updateView(newInfo: T, currentView: ViewGroup)
+
+ /**
+ * Fills [outRect] with the touchable region of this view. This will be used by WindowManager
+ * to decide which touch events go to the view.
+ */
+ abstract fun getTouchableRegion(view: View, outRect: Rect)
/**
* A method that can be implemented by subclasses to do custom animations for when the view
* appears.
*/
- open fun animateViewIn(view: ViewGroup) {}
+ internal open fun animateViewIn(view: ViewGroup) {}
+
+ /**
+ * A method that can be implemented by subclasses to do custom animations for when the view
+ * disappears.
+ *
+ * @param onAnimationEnd an action that *must* be run once the animation finishes successfully.
+ */
+ internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ onAnimationEnd.run()
+ }
+
+ /** A container for all the display-related state objects. */
+ private inner class DisplayInfo(
+ /** The view currently being displayed. */
+ val view: ViewGroup,
+
+ /** The info currently being displayed. */
+ var info: T,
+ )
}
-object TemporaryDisplayRemovalReason {
- const val REASON_TIMEOUT = "TIMEOUT"
- const val REASON_SCREEN_TAP = "SCREEN_TAP"
-}
+private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
private data class IconInfo(
val iconName: String,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TouchableRegionViewController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TouchableRegionViewController.kt
new file mode 100644
index 0000000..60241a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TouchableRegionViewController.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewTreeObserver
+import com.android.systemui.util.ViewController
+
+/**
+ * A view controller that will notify the [ViewTreeObserver] about the touchable region for this
+ * view. This will be used by WindowManager to decide which touch events go to the view and which
+ * pass through to the window below.
+ *
+ * @param touchableRegionSetter a function that, given the view and an out rect, fills the rect with
+ * the touchable region of this view.
+ */
+class TouchableRegionViewController(
+ view: View,
+ touchableRegionSetter: (View, Rect) -> Unit,
+) : ViewController<View>(view) {
+
+ private val tempRect = Rect()
+
+ private val internalInsetsListener =
+ ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo ->
+ inoutInfo.setTouchableInsets(
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
+ )
+
+ tempRect.setEmpty()
+ touchableRegionSetter.invoke(mView, tempRect)
+ inoutInfo.touchableRegion.set(tempRect)
+ }
+
+ public override fun onViewAttached() {
+ mView.viewTreeObserver.addOnComputeInternalInsetsListener(internalInsetsListener)
+ }
+
+ public override fun onViewDetached() {
+ mView.viewTreeObserver.removeOnComputeInternalInsetsListener(internalInsetsListener)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
similarity index 61%
rename from packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
rename to packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 007eb8f..1a25e4d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.media.taptotransfer.sender
+package com.android.systemui.temporarydisplay.chipbar
-import android.app.StatusBarManager
import android.content.Context
+import android.graphics.Rect
import android.media.MediaRoute2Info
import android.os.PowerManager
-import android.util.Log
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
@@ -29,6 +28,7 @@
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
@@ -38,23 +38,39 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.media.taptotransfer.sender.ChipStateSender
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderUiEventLogger
+import com.android.systemui.media.taptotransfer.sender.TransferStatus
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.concurrency.DelayableExecutor
-import dagger.Lazy
+import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
/**
- * A controller to display and hide the Media Tap-To-Transfer chip on the **sending** device. This
- * chip is shown when a user is transferring media to/from this device and a receiver device.
+ * A coordinator for showing/hiding the chipbar.
+ *
+ * The chipbar is a UI element that displays on top of all content. It appears at the top of the
+ * screen and consists of an icon, one line of text, and an optional end icon or action. It will
+ * auto-dismiss after some amount of seconds. The user is *not* able to manually dismiss the
+ * chipbar.
+ *
+ * It should be only be used for critical and temporary information that the user *must* be aware
+ * of. In general, prefer using heads-up notifications, since they are dismissable and will remain
+ * in the list of notifications until the user dismisses them.
+ *
+ * Only one chipbar may be shown at a time.
+ * TODO(b/245610654): Should we just display whichever chipbar was most recently requested, or do we
+ * need to maintain a priority ordering?
+ *
+ * TODO(b/245610654): Remove all media-related items from this class so it's just for generic
+ * chipbars.
*/
@SysUISingleton
-class MediaTttChipControllerSender @Inject constructor(
- commandQueue: CommandQueue,
+open class ChipbarCoordinator @Inject constructor(
context: Context,
@MediaTttSenderLogger logger: MediaTttLogger,
windowManager: WindowManager,
@@ -63,10 +79,9 @@
configurationController: ConfigurationController,
powerManager: PowerManager,
private val uiEventLogger: MediaTttSenderUiEventLogger,
- // Added Lazy<> to delay the time we create Falsing instances.
- // And overcome performance issue, check [b/247817628] for details.
- private val falsingManager: Lazy<FalsingManager>,
- private val falsingCollector: Lazy<FalsingCollector>,
+ private val falsingManager: FalsingManager,
+ private val falsingCollector: FalsingCollector,
+ private val viewUtil: ViewUtil,
) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
context,
logger,
@@ -75,60 +90,24 @@
accessibilityManager,
configurationController,
powerManager,
- R.layout.media_ttt_chip,
+ R.layout.chipbar,
MediaTttUtils.WINDOW_TITLE,
MediaTttUtils.WAKE_REASON,
) {
- private lateinit var parent: MediaTttChipRootView
+ private lateinit var parent: ChipbarRootView
override val windowLayoutParams = commonWindowLayoutParams.apply {
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
}
- private val commandQueueCallbacks = object : CommandQueue.Callbacks {
- override fun updateMediaTapToTransferSenderDisplay(
- @StatusBarManager.MediaTransferSenderState displayState: Int,
- routeInfo: MediaRoute2Info,
- undoCallback: IUndoMediaTransferCallback?
- ) {
- this@MediaTttChipControllerSender.updateMediaTapToTransferSenderDisplay(
- displayState, routeInfo, undoCallback
- )
- }
- }
-
- init {
- commandQueue.addCallback(commandQueueCallbacks)
- }
-
- private fun updateMediaTapToTransferSenderDisplay(
- @StatusBarManager.MediaTransferSenderState displayState: Int,
- routeInfo: MediaRoute2Info,
- undoCallback: IUndoMediaTransferCallback?
- ) {
- val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState)
- val stateName = chipState?.name ?: "Invalid"
- logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
-
- if (chipState == null) {
- Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
- return
- }
- uiEventLogger.logSenderStateChange(chipState)
-
- if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
- removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER.name)
- } else {
- displayView(ChipSenderInfo(chipState, routeInfo, undoCallback))
- }
- }
+ override fun start() {}
override fun updateView(
newInfo: ChipSenderInfo,
currentView: ViewGroup
) {
- super.updateView(newInfo, currentView)
+ // TODO(b/245610654): Adding logging here.
val chipState = newInfo.state
@@ -136,7 +115,7 @@
parent = currentView.requireViewById(R.id.media_ttt_sender_chip)
parent.touchHandler = object : Gefingerpoken {
override fun onTouchEvent(ev: MotionEvent?): Boolean {
- falsingCollector.get().onTouchEvent(ev)
+ falsingCollector.onTouchEvent(ev)
return false
}
}
@@ -145,11 +124,9 @@
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger
)
- MediaTttUtils.setIcon(
- currentView.requireViewById(R.id.app_icon),
- iconInfo.drawable,
- iconInfo.contentDescription
- )
+ val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
+ iconView.setImageDrawable(iconInfo.drawable)
+ iconView.contentDescription = iconInfo.contentDescription
// Text
val otherDeviceName = newInfo.routeInfo.name.toString()
@@ -167,7 +144,7 @@
newInfo.routeInfo,
newInfo.undoCallback,
uiEventLogger,
- falsingManager.get(),
+ falsingManager,
)
undoView.setOnClickListener(undoClickListener)
undoView.visibility = (undoClickListener != null).visibleIfTrue()
@@ -196,21 +173,19 @@
)
}
- override fun removeView(removalReason: String) {
- // Don't remove the chip if we're in progress or succeeded, since the user should still be
- // able to see the status of the transfer. (But do remove it if it's finally timed out.)
- val transferStatus = info?.state?.transferStatus
- if (
- (transferStatus == TransferStatus.IN_PROGRESS ||
- transferStatus == TransferStatus.SUCCEEDED) &&
- removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT
- ) {
- logger.logRemovalBypass(
- removalReason, bypassReason = "transferStatus=${transferStatus.name}"
- )
- return
- }
- super.removeView(removalReason)
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ ViewHierarchyAnimator.animateRemoval(
+ view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner),
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_ACCELERATE,
+ ANIMATION_DURATION,
+ includeMargins = true,
+ onAnimationEnd,
+ )
+ }
+
+ override fun getTouchableRegion(view: View, outRect: Rect) {
+ viewUtil.setRectToViewWindowLocation(view, outRect)
}
private fun Boolean.visibleIfTrue(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
rename to packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
index 3373159..edec420 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.taptotransfer.sender
+package com.android.systemui.temporarydisplay.chipbar
import android.content.Context
import android.util.AttributeSet
@@ -22,8 +22,8 @@
import android.widget.FrameLayout
import com.android.systemui.Gefingerpoken
-/** A simple subclass that allows for observing touch events on chip. */
-class MediaTttChipRootView(
+/** A simple subclass that allows for observing touch events on chipbar. */
+class ChipbarRootView(
context: Context,
attrs: AttributeSet?
) : FrameLayout(context, attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index adef182..3d56f23 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 @@
* 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 @@
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;
@@ -171,6 +172,10 @@
@Override
public void onColorsChanged(WallpaperColors wallpaperColors, int which, int userId) {
+ WallpaperColors currentColors = mCurrentColors.get(userId);
+ if (wallpaperColors != null && wallpaperColors.equals(currentColors)) {
+ return;
+ }
boolean currentUser = userId == mUserTracker.getUserId();
if (currentUser && !mAcceptColorEvents
&& mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP) {
@@ -357,8 +362,7 @@
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 9eb34a4..ed14c8a 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 @@
* 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 @@
@Nullable IAccessibilityManager accessibilityManager,
ToastFactory toastFactory, ToastLogger toastLogger
) {
- super(context);
+ mContext = context;
mCommandQueue = commandQueue;
mNotificationManager = notificationManager;
mIAccessibilityManager = accessibilityManager;
@@ -179,7 +180,7 @@
}
@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 00ed3d6..10a09dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -22,25 +22,20 @@
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.os.Handler;
-import android.os.PowerManager;
import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.ReferenceSystemUIModule;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.privacy.MediaProjectionPrivacyItemMonitor;
import com.android.systemui.privacy.PrivacyItemMonitor;
@@ -64,8 +59,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BatteryControllerImpl;
+import com.android.systemui.statusbar.policy.AospPolicyModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
@@ -86,18 +80,21 @@
import dagger.multibindings.IntoSet;
/**
- * A dagger module for injecting default implementations of components of System UI that may be
- * overridden by the System UI implementation.
+ * A TV specific version of {@link ReferenceSystemUIModule}.
+ *
+ * Code here should be specific to the TV variant of SystemUI and will not be included in other
+ * variants of SystemUI.
*/
-@Module(includes = {
- GestureModule.class,
- PowerModule.class,
- QSModule.class,
- ReferenceScreenshotModule.class,
- VolumeModule.class,
- },
- subcomponents = {
- })
+@Module(
+ includes = {
+ AospPolicyModule.class,
+ GestureModule.class,
+ PowerModule.class,
+ QSModule.class,
+ ReferenceScreenshotModule.class,
+ VolumeModule.class,
+ }
+)
public abstract class TvSystemUIModule {
@SysUISingleton
@@ -114,21 +111,6 @@
@Provides
@SysUISingleton
- static BatteryController provideBatteryController(Context context,
- EnhancedEstimates enhancedEstimates, PowerManager powerManager,
- BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
- DumpManager dumpManager,
- @Main Handler mainHandler, @Background Handler bgHandler) {
- BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
- broadcastDispatcher, demoModeController,
- dumpManager,
- mainHandler, bgHandler);
- bC.init();
- return bC;
- }
-
- @Provides
- @SysUISingleton
static SensorPrivacyController provideSensorPrivacyController(
SensorPrivacyManager sensorPrivacyManager) {
SensorPrivacyController spC = new SensorPrivacyControllerImpl(sensorPrivacyManager);
@@ -221,9 +203,9 @@
@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 fc20ac2..6ed3a09 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.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.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 @@
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 @@
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 @@
.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 @@
}
}
- 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 @@
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 eea6ac0..59ad24a 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 @@
@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 4dc78f9..bf70673 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.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 @@
/** */
@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 @@
@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 108ab43..7f1195b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -16,426 +16,41 @@
package com.android.systemui.user
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.LayerDrawable
import android.os.Bundle
-import android.os.UserManager
-import android.provider.Settings
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.MotionEvent
import android.view.View
-import android.view.ViewGroup
-import android.widget.AdapterView
-import android.widget.ArrayAdapter
-import android.widget.ImageView
-import android.widget.TextView
-import android.window.OnBackInvokedCallback
-import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
-import androidx.constraintlayout.helper.widget.Flow
import androidx.lifecycle.ViewModelProvider
-import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.util.UserIcons
-import com.android.settingslib.Utils
-import com.android.systemui.Gefingerpoken
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import dagger.Lazy
import javax.inject.Inject
-import kotlin.math.ceil
-
-private const val USER_VIEW = "user_view"
/** Support a fullscreen user switcher */
open class UserSwitcherActivity
@Inject
constructor(
- private val userSwitcherController: UserSwitcherController,
- private val broadcastDispatcher: BroadcastDispatcher,
private val falsingCollector: FalsingCollector,
- private val falsingManager: FalsingManager,
- private val userManager: UserManager,
- private val userTracker: UserTracker,
- private val flags: FeatureFlags,
private val viewModelFactory: Lazy<UserSwitcherViewModel.Factory>,
) : ComponentActivity() {
- private lateinit var parent: UserSwitcherRootView
- private lateinit var broadcastReceiver: BroadcastReceiver
- private var popupMenu: UserSwitcherPopupMenu? = null
- private lateinit var addButton: View
- private var addUserRecords = mutableListOf<UserRecord>()
- private val onBackCallback = OnBackInvokedCallback { finish() }
- private val userSwitchedCallback: UserTracker.Callback =
- object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- finish()
- }
- }
- // When the add users options become available, insert another option to manage users
- private val manageUserRecord =
- UserRecord(
- null /* info */,
- null /* picture */,
- false /* isGuest */,
- false /* isCurrent */,
- false /* isAddUser */,
- false /* isRestricted */,
- false /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
-
- private val adapter: UserAdapter by lazy { UserAdapter() }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- createActivity()
- }
-
- @VisibleForTesting
- fun createActivity() {
setContentView(R.layout.user_switcher_fullscreen)
window.decorView.systemUiVisibility =
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
- if (isUsingModernArchitecture()) {
- Log.d(TAG, "Using modern architecture.")
- val viewModel =
- ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
- UserSwitcherViewBinder.bind(
- view = requireViewById(R.id.user_switcher_root),
- viewModel = viewModel,
- lifecycleOwner = this,
- layoutInflater = layoutInflater,
- falsingCollector = falsingCollector,
- onFinish = this::finish,
- )
- return
- } else {
- Log.d(TAG, "Not using modern architecture.")
- }
-
- parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root)
-
- parent.touchHandler =
- object : Gefingerpoken {
- override fun onTouchEvent(ev: MotionEvent?): Boolean {
- falsingCollector.onTouchEvent(ev)
- return false
- }
- }
-
- requireViewById<View>(R.id.cancel).apply { setOnClickListener { _ -> finish() } }
-
- addButton =
- requireViewById<View>(R.id.add).apply { setOnClickListener { _ -> showPopupMenu() } }
-
- onBackInvokedDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- onBackCallback
+ val viewModel =
+ ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
+ UserSwitcherViewBinder.bind(
+ view = requireViewById(R.id.user_switcher_root),
+ viewModel = viewModel,
+ lifecycleOwner = this,
+ layoutInflater = layoutInflater,
+ falsingCollector = falsingCollector,
+ onFinish = this::finish,
)
-
- userSwitcherController.init(parent)
- initBroadcastReceiver()
-
- parent.post { buildUserViews() }
- userTracker.addCallback(userSwitchedCallback, mainExecutor)
- }
-
- private fun showPopupMenu() {
- val items = mutableListOf<UserRecord>()
- addUserRecords.forEach { items.add(it) }
-
- var popupMenuAdapter =
- ItemAdapter(
- this,
- R.layout.user_switcher_fullscreen_popup_item,
- layoutInflater,
- { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) },
- { item: UserRecord ->
- adapter.findUserIcon(item, true).mutate().apply {
- setTint(
- resources.getColor(
- R.color.user_switcher_fullscreen_popup_item_tint,
- getTheme()
- )
- )
- }
- }
- )
- popupMenuAdapter.addAll(items)
-
- popupMenu =
- UserSwitcherPopupMenu(this).apply {
- setAnchorView(addButton)
- setAdapter(popupMenuAdapter)
- setOnItemClickListener { parent: AdapterView<*>, view: View, pos: Int, id: Long ->
- if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) {
- return@setOnItemClickListener
- }
- // -1 for the header
- val item = popupMenuAdapter.getItem(pos - 1)
- if (item == manageUserRecord) {
- val i = Intent().setAction(Settings.ACTION_USER_SETTINGS)
- this@UserSwitcherActivity.startActivity(i)
- } else {
- adapter.onUserListItemClicked(item)
- }
-
- dismiss()
- popupMenu = null
-
- if (!item.isAddUser) {
- this@UserSwitcherActivity.finish()
- }
- }
-
- show()
- }
- }
-
- private fun buildUserViews() {
- var count = 0
- var start = 0
- for (i in 0 until parent.getChildCount()) {
- if (parent.getChildAt(i).getTag() == USER_VIEW) {
- if (count == 0) start = i
- count++
- }
- }
- parent.removeViews(start, count)
- addUserRecords.clear()
- val flow = requireViewById<Flow>(R.id.flow)
- val totalWidth = parent.width
- val userViewCount = adapter.getTotalUserViews()
- val maxColumns = getMaxColumns(userViewCount)
- val horizontalGap =
- resources.getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap)
- val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap
- val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns
-
- flow.setMaxElementsWrap(maxColumns)
-
- for (i in 0 until adapter.getCount()) {
- val item = adapter.getItem(i)
- if (adapter.doNotRenderUserView(item)) {
- addUserRecords.add(item)
- } else {
- val userView = adapter.getView(i, null, parent)
- userView.requireViewById<ImageView>(R.id.user_switcher_icon).apply {
- val lp = layoutParams
- if (maxWidgetDiameter < lp.width) {
- lp.width = maxWidgetDiameter
- lp.height = maxWidgetDiameter
- layoutParams = lp
- }
- }
-
- userView.setId(View.generateViewId())
- parent.addView(userView)
-
- // Views must have an id and a parent in order for Flow to lay them out
- flow.addView(userView)
-
- userView.setOnClickListener { v ->
- if (falsingManager.isFalseTap(LOW_PENALTY) || !v.isEnabled()) {
- return@setOnClickListener
- }
-
- if (!item.isCurrent || item.isGuest) {
- adapter.onUserListItemClicked(item)
- }
- }
- }
- }
-
- if (!addUserRecords.isEmpty()) {
- addUserRecords.add(manageUserRecord)
- addButton.visibility = View.VISIBLE
- } else {
- addButton.visibility = View.GONE
- }
- }
-
- override fun onBackPressed() {
- if (isUsingModernArchitecture()) {
- return super.onBackPressed()
- }
-
- finish()
- }
-
- override fun onDestroy() {
- super.onDestroy()
- if (isUsingModernArchitecture()) {
- return
- }
- destroyActivity()
- }
-
- @VisibleForTesting
- fun destroyActivity() {
- onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackCallback)
- broadcastDispatcher.unregisterReceiver(broadcastReceiver)
- userTracker.removeCallback(userSwitchedCallback)
- }
-
- private fun initBroadcastReceiver() {
- broadcastReceiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val action = intent.getAction()
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- finish()
- }
- }
- }
-
- val filter = IntentFilter()
- filter.addAction(Intent.ACTION_SCREEN_OFF)
- broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
- }
-
- @VisibleForTesting
- fun getMaxColumns(userCount: Int): Int {
- return if (userCount < 5) 4 else ceil(userCount / 2.0).toInt()
- }
-
- private fun isUsingModernArchitecture(): Boolean {
- return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY)
- }
-
- /** Provides views to populate the option menu. */
- private class ItemAdapter(
- val parentContext: Context,
- val resource: Int,
- val layoutInflater: LayoutInflater,
- val textGetter: (UserRecord) -> String,
- val iconGetter: (UserRecord) -> Drawable
- ) : ArrayAdapter<UserRecord>(parentContext, resource) {
-
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- val item = getItem(position)
- val view = convertView ?: layoutInflater.inflate(resource, parent, false)
-
- view.requireViewById<ImageView>(R.id.icon).apply { setImageDrawable(iconGetter(item)) }
- view.requireViewById<TextView>(R.id.text).apply { setText(textGetter(item)) }
-
- return view
- }
- }
-
- private inner class UserAdapter : BaseUserSwitcherAdapter(userSwitcherController) {
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- val item = getItem(position)
- var view = convertView as ViewGroup?
- if (view == null) {
- view =
- layoutInflater.inflate(R.layout.user_switcher_fullscreen_item, parent, false)
- as ViewGroup
- }
- (view.getChildAt(0) as ImageView).apply { setImageDrawable(getDrawable(item)) }
- (view.getChildAt(1) as TextView).apply { setText(getName(getContext(), item)) }
-
- view.setEnabled(item.isSwitchToEnabled)
- UserSwitcherController.setSelectableAlpha(view)
- view.setTag(USER_VIEW)
- return view
- }
-
- override fun getName(context: Context, item: UserRecord, isTablet: Boolean): String {
- return if (item == manageUserRecord) {
- getString(R.string.manage_users)
- } else {
- super.getName(context, item, isTablet)
- }
- }
-
- fun findUserIcon(item: UserRecord, isTablet: Boolean = false): Drawable {
- if (item == manageUserRecord) {
- return getDrawable(R.drawable.ic_manage_users)
- }
- if (item.info == null) {
- return getIconDrawable(this@UserSwitcherActivity, item, isTablet)
- }
- val userIcon = userManager.getUserIcon(item.info.id)
- if (userIcon != null) {
- return BitmapDrawable(userIcon)
- }
- return UserIcons.getDefaultUserIcon(resources, item.info.id, false)
- }
-
- fun getTotalUserViews(): Int {
- return users.count { item -> !doNotRenderUserView(item) }
- }
-
- fun doNotRenderUserView(item: UserRecord): Boolean {
- return item.isAddUser || item.isAddSupervisedUser || item.isGuest && item.info == null
- }
-
- private fun getDrawable(item: UserRecord): Drawable {
- var drawable =
- if (item.isGuest) {
- getDrawable(R.drawable.ic_account_circle)
- } else {
- findUserIcon(item)
- }
- drawable.mutate()
-
- if (!item.isCurrent && !item.isSwitchToEnabled) {
- drawable.setTint(
- resources.getColor(
- R.color.kg_user_switcher_restricted_avatar_icon_color,
- getTheme()
- )
- )
- }
-
- val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() as LayerDrawable
- if (item == userSwitcherController.currentUserRecord) {
- (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply {
- val stroke =
- resources.getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
- val color =
- Utils.getColorAttrDefaultColor(
- this@UserSwitcherActivity,
- com.android.internal.R.attr.colorAccentPrimary
- )
-
- setStroke(stroke, color)
- }
- }
-
- ld.setDrawableByLayerId(R.id.user_avatar, drawable)
- return ld
- }
-
- override fun notifyDataSetChanged() {
- super.notifyDataSetChanged()
- buildUserViews()
- }
- }
-
- companion object {
- private const val TAG = "UserSwitcherActivity"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 3014f39..919e699 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -220,7 +220,7 @@
val result = withContext(backgroundDispatcher) { manager.aliveUsers }
if (result != null) {
- _userInfos.value = result
+ _userInfos.value = result.sortedBy { it.creationTime }
}
}
}
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 9370286..d4fb563 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 @@
* 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 1b4746a..dc004f3 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 @@
)
}
+ 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 a84238c..ba5a82a 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
@@ -43,6 +43,7 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.data.repository.UserRepository
@@ -101,6 +102,7 @@
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()
}
@@ -163,10 +165,11 @@
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 (
@@ -182,35 +185,45 @@
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
+
+ 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.canCreateUser(
- manager,
+ UserActionsUtil.canManageUsers(
repository,
settings.isUserSwitcherEnabled,
settings.isAddUsersFromLockscreen,
)
) {
- add(UserActionModel.ADD_USER)
- }
-
- if (
- UserActionsUtil.canCreateSupervisedUser(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- supervisedUserPackageName,
- )
- ) {
- add(UserActionModel.ADD_SUPERVISED_USER)
+ add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
}
}
}
@@ -263,7 +276,10 @@
toRecord(
action = it,
selectedUserId = selectedUserInfo.id,
- isAddFromLockscreenEnabled = settings.isAddUsersFromLockscreen,
+ isRestricted =
+ it != UserActionModel.ENTER_GUEST_MODE &&
+ it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
+ !settings.isAddUsersFromLockscreen,
)
}
)
@@ -390,9 +406,24 @@
guestUserInteractor.onDeviceBootCompleted()
}
+ /** Switches to the user or executes the action represented by the given record. */
+ fun onRecordSelected(
+ record: UserRecord,
+ dialogShower: UserSwitchDialogController.DialogShower? = null,
+ ) {
+ if (LegacyUserDataHelper.isUser(record)) {
+ // It's safe to use checkNotNull around record.info because isUser only returns true
+ // if record.info is not null.
+ selectUser(checkNotNull(record.info).id, dialogShower)
+ } else {
+ executeAction(LegacyUserDataHelper.toUserActionModel(record), dialogShower)
+ }
+ }
+
/** Switches to the user with the given user ID. */
fun selectUser(
newlySelectedUserId: Int,
+ dialogShower: UserSwitchDialogController.DialogShower? = null,
) {
if (isNewImpl) {
val currentlySelectedUserInfo = repository.getSelectedUserInfo()
@@ -428,22 +459,28 @@
return
}
+ dialogShower?.dismiss()
+
switchUser(newlySelectedUserId)
} else {
- controller.onUserSelected(newlySelectedUserId, /* dialogShower= */ null)
+ controller.onUserSelected(newlySelectedUserId, dialogShower)
}
}
/** Executes the given action. */
- fun executeAction(action: UserActionModel) {
+ fun executeAction(
+ action: UserActionModel,
+ dialogShower: UserSwitchDialogController.DialogShower? = null,
+ ) {
if (isNewImpl) {
when (action) {
UserActionModel.ENTER_GUEST_MODE ->
guestUserInteractor.createAndSwitchTo(
this::showDialog,
this::dismissDialog,
- this::selectUser,
- )
+ ) { userId ->
+ selectUser(userId, dialogShower)
+ }
UserActionModel.ADD_USER -> {
val currentUser = repository.getSelectedUserInfo()
showDialog(
@@ -460,12 +497,12 @@
.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 {
@@ -553,20 +590,13 @@
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.
@@ -575,7 +605,7 @@
}
private fun switchUser(userId: Int) {
- // TODO(b/246631653): track jank and lantecy like in the old impl.
+ // TODO(b/246631653): track jank and latency like in the old impl.
refreshUsersScheduler.pause()
try {
activityManager.switchUser(userId)
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 137de15..03a7470 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 @@
context = context,
selectedUserId = selectedUserId,
),
+ isManageUsers = actionType == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
}
@@ -90,6 +91,7 @@
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 15fdc35..e74232d 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.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 @@
*/
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 @@
isGuest: Boolean,
isAddSupervisedUser: Boolean,
isTablet: Boolean = false,
+ isManageUsers: Boolean,
): Int {
return if (isAddUser && isTablet) {
R.drawable.ic_account_circle_filled
@@ -59,6 +49,8 @@
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 @@
isAddUser = record.isAddUser,
isAddSupervisedUser = record.isAddSupervisedUser,
isTablet = isTablet,
+ isManageUsers = record.isManageUsers,
)
)
}
@@ -114,8 +107,9 @@
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 @@
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 6e7b523..91c5921 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 @@
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 5b83df7..219dae2 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 @@
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.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 @@
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 @@
_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 @@
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/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index 5f7d745..a925e38 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -67,6 +67,8 @@
private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
private boolean mDefaultShowOperatorNameConfigLoaded;
private boolean mDefaultShowOperatorNameConfig;
+ private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded;
+ private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
@Inject
public CarrierConfigTracker(
@@ -207,6 +209,22 @@
}
/**
+ * Returns KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN value for
+ * the default carrier config.
+ */
+ public boolean getAlwaysShowPrimarySignalBarInOpportunisticNetworkDefault() {
+ if (!mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded) {
+ mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig = CarrierConfigManager
+ .getDefaultConfig().getBoolean(CarrierConfigManager
+ .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN
+ );
+ mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded = true;
+ }
+
+ return mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
+ }
+
+ /**
* Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the given subId, or the
* default value if no override exists
*
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 53da213..2efeda9 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 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 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/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index 8c736dc..81ae6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.BroadcastRunning;
import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.dagger.qualifiers.Main;
@@ -51,6 +52,17 @@
return thread.getLooper();
}
+ /** BroadcastRunning Looper (for sending and receiving broadcasts) */
+ @Provides
+ @SysUISingleton
+ @BroadcastRunning
+ public static Looper provideBroadcastRunningLooper() {
+ HandlerThread thread = new HandlerThread("BroadcastRunning",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ return thread.getLooper();
+ }
+
/** Long running tasks Looper */
@Provides
@SysUISingleton
@@ -83,7 +95,17 @@
}
/**
- * Provide a Long running Executor by default.
+ * Provide a BroadcastRunning Executor (for sending and receiving broadcasts).
+ */
+ @Provides
+ @SysUISingleton
+ @BroadcastRunning
+ public static Executor provideBroadcastRunningExecutor(@BroadcastRunning Looper looper) {
+ return new ExecutorImpl(looper);
+ }
+
+ /**
+ * Provide a Long running Executor.
*/
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index db35437e..ecb365f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -34,9 +34,26 @@
private final String mTag = getClass().getSimpleName();
private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
- private boolean mIsConditionMet = false;
+ private final boolean mOverriding;
+ private Boolean mIsConditionMet;
private boolean mStarted = false;
- private boolean mOverriding = false;
+
+ /**
+ * By default, conditions have an initial value of false and are not overriding.
+ */
+ public Condition() {
+ this(false, false);
+ }
+
+ /**
+ * Constructor for specifying initial state and overriding condition attribute.
+ * @param initialConditionMet Initial state of the condition.
+ * @param overriding Whether this condition overrides others.
+ */
+ protected Condition(Boolean initialConditionMet, boolean overriding) {
+ mIsConditionMet = initialConditionMet;
+ mOverriding = overriding;
+ }
/**
* Starts monitoring the condition.
@@ -49,14 +66,6 @@
protected abstract void stop();
/**
- * Sets whether this condition's value overrides others in determining the overall state.
- */
- public void setOverriding(boolean overriding) {
- mOverriding = overriding;
- updateCondition(mIsConditionMet);
- }
-
- /**
* Returns whether the current condition overrides
*/
public boolean isOverridingCondition() {
@@ -110,13 +119,31 @@
* @param isConditionMet True if the condition has been fulfilled. False otherwise.
*/
protected void updateCondition(boolean isConditionMet) {
- if (mIsConditionMet == isConditionMet) {
+ if (mIsConditionMet != null && mIsConditionMet == isConditionMet) {
return;
}
if (shouldLog()) Log.d(mTag, "updating condition to " + isConditionMet);
mIsConditionMet = isConditionMet;
+ sendUpdate();
+ }
+ /**
+ * Clears the set condition value. This is purposefully separate from
+ * {@link #updateCondition(boolean)} to avoid confusion around {@code null} values.
+ */
+ protected void clearCondition() {
+ if (mIsConditionMet == null) {
+ return;
+ }
+
+ if (shouldLog()) Log.d(mTag, "clearing condition");
+
+ mIsConditionMet = null;
+ sendUpdate();
+ }
+
+ private void sendUpdate() {
final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
final Callback cb = iterator.next().get();
@@ -128,8 +155,21 @@
}
}
+ /**
+ * Returns whether the condition is set. This method should be consulted to understand the
+ * value of {@link #isConditionMet()}.
+ * @return {@code true} if value is present, {@code false} otherwise.
+ */
+ public boolean isConditionSet() {
+ return mIsConditionMet != null;
+ }
+
+ /**
+ * Returns whether the condition has been met. Note that this method will return {@code false}
+ * if the condition is not set as well.
+ */
public boolean isConditionMet() {
- return mIsConditionMet;
+ return Boolean.TRUE.equals(mIsConditionMet);
}
private boolean shouldLog() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index dac8a0b..4824f67 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -57,12 +57,16 @@
}
public void update() {
+ // Only consider set conditions.
+ final Collection<Condition> setConditions = mSubscription.mConditions.stream()
+ .filter(Condition::isConditionSet).collect(Collectors.toSet());
+
// Overriding conditions do not override each other
- final Collection<Condition> overridingConditions = mSubscription.mConditions.stream()
+ final Collection<Condition> overridingConditions = setConditions.stream()
.filter(Condition::isOverridingCondition).collect(Collectors.toSet());
final Collection<Condition> targetCollection = overridingConditions.isEmpty()
- ? mSubscription.mConditions : overridingConditions;
+ ? setConditions : overridingConditions;
final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
.stream()
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 619e50b..a0a0372 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 @@
/** */
@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/util/view/ViewUtil.kt b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
index 613a797..6160b00 100644
--- a/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
@@ -1,5 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.util.view
+import android.graphics.Rect
import android.view.View
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -23,4 +40,22 @@
top <= y &&
y <= top + view.height
}
+
+ /**
+ * Sets [outRect] to be the view's location within its window.
+ */
+ fun setRectToViewWindowLocation(view: View, outRect: Rect) {
+ val locInWindow = IntArray(2)
+ view.getLocationInWindow(locInWindow)
+
+ val x = locInWindow[0]
+ val y = locInWindow[1]
+
+ outRect.set(
+ x,
+ y,
+ x + view.width,
+ y + view.height,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 87fb2a6..0b3521b 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 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 @@
}
@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/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 0f7e143..42d7d52 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -16,21 +16,17 @@
package com.android.systemui.wallpapers;
-import static android.view.Display.DEFAULT_DISPLAY;
-
import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
-import android.content.ComponentCallbacks2;
-import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -40,8 +36,6 @@
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
-import android.view.Display;
-import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@@ -49,8 +43,11 @@
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.wallpapers.canvas.ImageCanvasWallpaperRenderer;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.wallpapers.canvas.WallpaperColorExtractor;
import com.android.systemui.wallpapers.gl.EglHelper;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
@@ -59,6 +56,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -78,15 +76,28 @@
private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
private final ArraySet<RectF> mColorAreas = new ArraySet<>();
private volatile int mPages = 1;
+ private boolean mPagesComputed = false;
private HandlerThread mWorker;
// scaled down version
private Bitmap mMiniBitmap;
private final FeatureFlags mFeatureFlags;
+ // used in canvasEngine to load/unload the bitmap and extract the colors
+ @Background
+ private final DelayableExecutor mBackgroundExecutor;
+ private static final int DELAY_UNLOAD_BITMAP = 2000;
+
+ @Main
+ private final Executor mMainExecutor;
+
@Inject
- public ImageWallpaper(FeatureFlags featureFlags) {
+ public ImageWallpaper(FeatureFlags featureFlags,
+ @Background DelayableExecutor backgroundExecutor,
+ @Main Executor mainExecutor) {
super();
mFeatureFlags = featureFlags;
+ mBackgroundExecutor = backgroundExecutor;
+ mMainExecutor = mainExecutor;
}
@Override
@@ -339,7 +350,6 @@
imgArea.left = 0;
imgArea.right = 1;
}
-
return imgArea;
}
@@ -510,69 +520,85 @@
class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
-
- // time [ms] before unloading the wallpaper after it is loaded
- private static final int DELAY_FORGET_WALLPAPER = 5000;
-
- private final Runnable mUnloadWallpaperCallback = this::unloadWallpaper;
-
private WallpaperManager mWallpaperManager;
- private ImageCanvasWallpaperRenderer mImageCanvasWallpaperRenderer;
+ private final WallpaperColorExtractor mWallpaperColorExtractor;
+ private SurfaceHolder mSurfaceHolder;
+ @VisibleForTesting
+ static final int MIN_SURFACE_WIDTH = 128;
+ @VisibleForTesting
+ static final int MIN_SURFACE_HEIGHT = 128;
private Bitmap mBitmap;
+ private boolean mWideColorGamut = false;
- private Display mDisplay;
- private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
-
- private AsyncTask<Void, Void, Bitmap> mLoader;
- private boolean mNeedsDrawAfterLoadingWallpaper = false;
+ /*
+ * Counter to unload the bitmap as soon as possible.
+ * Before any bitmap operation, this is incremented.
+ * After an operation completion, this is decremented (synchronously),
+ * and if the count is 0, unload the bitmap
+ */
+ private int mBitmapUsages = 0;
+ private final Object mLock = new Object();
CanvasEngine() {
super();
setFixedSizeAllowed(true);
setShowForAllUsers(true);
- }
+ mWallpaperColorExtractor = new WallpaperColorExtractor(
+ mBackgroundExecutor,
+ new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ @Override
+ public void onColorsProcessed(List<RectF> regions,
+ List<WallpaperColors> colors) {
+ CanvasEngine.this.onColorsProcessed(regions, colors);
+ }
- void trimMemory(int level) {
- if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
- && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
- && isBitmapLoaded()) {
- if (DEBUG) {
- Log.d(TAG, "trimMemory");
- }
- unloadWallpaper();
+ @Override
+ public void onMiniBitmapUpdated() {
+ CanvasEngine.this.onMiniBitmapUpdated();
+ }
+
+ @Override
+ public void onActivated() {
+ setOffsetNotificationsEnabled(true);
+ }
+
+ @Override
+ public void onDeactivated() {
+ setOffsetNotificationsEnabled(false);
+ }
+ });
+
+ // if the number of pages is already computed, transmit it to the color extractor
+ if (mPagesComputed) {
+ mWallpaperColorExtractor.onPageChanged(mPages);
}
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#onCreate");
if (DEBUG) {
Log.d(TAG, "onCreate");
}
+ mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
+ mSurfaceHolder = surfaceHolder;
+ Rect dimensions = mWallpaperManager.peekBitmapDimensions();
+ int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
+ int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
+ mSurfaceHolder.setFixedSize(width, height);
- mWallpaperManager = getSystemService(WallpaperManager.class);
- super.onCreate(surfaceHolder);
-
- final Context displayContext = getDisplayContext();
- final int displayId = displayContext == null ? DEFAULT_DISPLAY :
- displayContext.getDisplayId();
- DisplayManager dm = getSystemService(DisplayManager.class);
- if (dm != null) {
- mDisplay = dm.getDisplay(displayId);
- if (mDisplay == null) {
- Log.e(TAG, "Cannot find display! Fallback to default.");
- mDisplay = dm.getDisplay(DEFAULT_DISPLAY);
- }
- }
- setOffsetNotificationsEnabled(false);
-
- mImageCanvasWallpaperRenderer = new ImageCanvasWallpaperRenderer(surfaceHolder);
- loadWallpaper(false);
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .registerDisplayListener(this, null);
+ getDisplaySizeAndUpdateColorExtractor();
+ Trace.endSection();
}
@Override
public void onDestroy() {
- super.onDestroy();
- unloadWallpaper();
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .unregisterDisplayListener(this);
+ mWallpaperColorExtractor.cleanUp();
+ unloadBitmap();
}
@Override
@@ -581,31 +607,30 @@
}
@Override
+ public boolean shouldWaitForEngineShown() {
+ return true;
+ }
+
+ @Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (DEBUG) {
Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
}
- super.onSurfaceChanged(holder, format, width, height);
- mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
- drawFrame(false);
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
- super.onSurfaceDestroyed(holder);
if (DEBUG) {
Log.i(TAG, "onSurfaceDestroyed");
}
- mImageCanvasWallpaperRenderer.setSurfaceHolder(null);
+ mSurfaceHolder = null;
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
- super.onSurfaceCreated(holder);
if (DEBUG) {
Log.i(TAG, "onSurfaceCreated");
}
- mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
}
@Override
@@ -613,135 +638,90 @@
if (DEBUG) {
Log.d(TAG, "onSurfaceRedrawNeeded");
}
- super.onSurfaceRedrawNeeded(holder);
- // At the end of this method we should have drawn into the surface.
- // This means that the bitmap should be loaded synchronously if
- // it was already unloaded.
- if (!isBitmapLoaded()) {
- setBitmap(mWallpaperManager.getBitmap(true /* hardware */));
+ drawFrame();
+ }
+
+ private void drawFrame() {
+ mBackgroundExecutor.execute(this::drawFrameSynchronized);
+ }
+
+ private void drawFrameSynchronized() {
+ synchronized (mLock) {
+ drawFrameInternal();
}
- drawFrame(true);
}
- private DisplayInfo getDisplayInfo() {
- mDisplay.getDisplayInfo(mTmpDisplayInfo);
- return mTmpDisplayInfo;
- }
-
- private void drawFrame(boolean forceRedraw) {
- if (!mImageCanvasWallpaperRenderer.isSurfaceHolderLoaded()) {
+ private void drawFrameInternal() {
+ if (mSurfaceHolder == null) {
Log.e(TAG, "attempt to draw a frame without a valid surface");
return;
}
+ // load the wallpaper if not already done
if (!isBitmapLoaded()) {
- // ensure that we load the wallpaper.
- // if the wallpaper is currently loading, this call will have no effect.
- loadWallpaper(true);
- return;
- }
- mImageCanvasWallpaperRenderer.drawFrame(mBitmap, forceRedraw);
- }
-
- private void setBitmap(Bitmap bitmap) {
- if (bitmap == null) {
- Log.e(TAG, "Attempt to set a null bitmap");
- } else if (mBitmap == bitmap) {
- Log.e(TAG, "The value of bitmap is the same");
- } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
- Log.e(TAG, "Attempt to set an invalid wallpaper of length "
- + bitmap.getWidth() + "x" + bitmap.getHeight());
+ loadWallpaperAndDrawFrameInternal();
} else {
- if (mBitmap != null) {
- mBitmap.recycle();
- }
- mBitmap = bitmap;
+ mBitmapUsages++;
+
+ // drawing is done on the main thread
+ mMainExecutor.execute(() -> {
+ drawFrameOnCanvas(mBitmap);
+ reportEngineShown(false);
+ unloadBitmapIfNotUsed();
+ });
}
}
- private boolean isBitmapLoaded() {
+ @VisibleForTesting
+ void drawFrameOnCanvas(Bitmap bitmap) {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame");
+ Surface surface = mSurfaceHolder.getSurface();
+ Canvas canvas = mWideColorGamut
+ ? surface.lockHardwareWideColorGamutCanvas()
+ : surface.lockHardwareCanvas();
+ if (canvas != null) {
+ Rect dest = mSurfaceHolder.getSurfaceFrame();
+ try {
+ canvas.drawBitmap(bitmap, null, dest, null);
+ } finally {
+ surface.unlockCanvasAndPost(canvas);
+ }
+ }
+ Trace.endSection();
+ }
+
+ @VisibleForTesting
+ boolean isBitmapLoaded() {
return mBitmap != null && !mBitmap.isRecycled();
}
- /**
- * Loads the wallpaper on background thread and schedules updating the surface frame,
- * and if {@code needsDraw} is set also draws a frame.
- *
- * If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to
- * the active request).
- *
- */
- private void loadWallpaper(boolean needsDraw) {
- mNeedsDrawAfterLoadingWallpaper |= needsDraw;
- if (mLoader != null) {
- if (DEBUG) {
- Log.d(TAG, "Skipping loadWallpaper, already in flight ");
- }
- return;
- }
- mLoader = new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- Throwable exception;
- try {
- Bitmap wallpaper = mWallpaperManager.getBitmap(true /* hardware */);
- if (wallpaper != null
- && wallpaper.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
- throw new RuntimeException("Wallpaper is too large to draw!");
- }
- return wallpaper;
- } catch (RuntimeException | OutOfMemoryError e) {
- exception = e;
- }
-
- if (isCancelled()) {
- return null;
- }
-
- // Note that if we do fail at this, and the default wallpaper can't
- // be loaded, we will go into a cycle. Don't do a build where the
- // default wallpaper can't be loaded.
- Log.w(TAG, "Unable to load wallpaper!", exception);
- try {
- mWallpaperManager.clear();
- } catch (IOException ex) {
- // now we're really screwed.
- Log.w(TAG, "Unable reset to default wallpaper!", ex);
- }
-
- if (isCancelled()) {
- return null;
- }
-
- try {
- return mWallpaperManager.getBitmap(true /* hardware */);
- } catch (RuntimeException | OutOfMemoryError e) {
- Log.w(TAG, "Unable to load default wallpaper!", e);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- setBitmap(bitmap);
-
- if (mNeedsDrawAfterLoadingWallpaper) {
- drawFrame(true);
- }
-
- mLoader = null;
- mNeedsDrawAfterLoadingWallpaper = false;
- scheduleUnloadWallpaper();
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ private void unloadBitmapIfNotUsed() {
+ mBackgroundExecutor.execute(this::unloadBitmapIfNotUsedSynchronized);
}
- private void unloadWallpaper() {
- if (mLoader != null) {
- mLoader.cancel(false);
- mLoader = null;
+ private void unloadBitmapIfNotUsedSynchronized() {
+ synchronized (mLock) {
+ mBitmapUsages -= 1;
+ if (mBitmapUsages <= 0) {
+ mBitmapUsages = 0;
+ unloadBitmapInternal();
+ }
}
+ }
+ private void unloadBitmap() {
+ mBackgroundExecutor.execute(this::unloadBitmapSynchronized);
+ }
+
+ private void unloadBitmapSynchronized() {
+ synchronized (mLock) {
+ mBitmapUsages = 0;
+ unloadBitmapInternal();
+ }
+ }
+
+ private void unloadBitmapInternal() {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap");
if (mBitmap != null) {
mBitmap.recycle();
}
@@ -750,12 +730,131 @@
final Surface surface = getSurfaceHolder().getSurface();
surface.hwuiDestroy();
mWallpaperManager.forgetLoadedWallpaper();
+ Trace.endSection();
}
- private void scheduleUnloadWallpaper() {
- Handler handler = getMainThreadHandler();
- handler.removeCallbacks(mUnloadWallpaperCallback);
- handler.postDelayed(mUnloadWallpaperCallback, DELAY_FORGET_WALLPAPER);
+ private void loadWallpaperAndDrawFrameInternal() {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#loadWallpaper");
+ boolean loadSuccess = false;
+ Bitmap bitmap;
+ try {
+ bitmap = mWallpaperManager.getBitmap(false);
+ if (bitmap != null
+ && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
+ throw new RuntimeException("Wallpaper is too large to draw!");
+ }
+ } catch (RuntimeException | OutOfMemoryError exception) {
+
+ // Note that if we do fail at this, and the default wallpaper can't
+ // be loaded, we will go into a cycle. Don't do a build where the
+ // default wallpaper can't be loaded.
+ Log.w(TAG, "Unable to load wallpaper!", exception);
+ try {
+ mWallpaperManager.clear(WallpaperManager.FLAG_SYSTEM);
+ } catch (IOException ex) {
+ // now we're really screwed.
+ Log.w(TAG, "Unable reset to default wallpaper!", ex);
+ }
+
+ try {
+ bitmap = mWallpaperManager.getBitmap(false);
+ } catch (RuntimeException | OutOfMemoryError e) {
+ Log.w(TAG, "Unable to load default wallpaper!", e);
+ bitmap = null;
+ }
+ }
+
+ if (bitmap == null) {
+ Log.w(TAG, "Could not load bitmap");
+ } else if (bitmap.isRecycled()) {
+ Log.e(TAG, "Attempt to load a recycled bitmap");
+ } else if (mBitmap == bitmap) {
+ Log.e(TAG, "Loaded a bitmap that was already loaded");
+ } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
+ Log.e(TAG, "Attempt to load an invalid wallpaper of length "
+ + bitmap.getWidth() + "x" + bitmap.getHeight());
+ } else {
+ // at this point, loading is done correctly.
+ loadSuccess = true;
+ // recycle the previously loaded bitmap
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ }
+ mBitmap = bitmap;
+ mWideColorGamut = mWallpaperManager.wallpaperSupportsWcg(
+ WallpaperManager.FLAG_SYSTEM);
+
+ // +2 usages for the color extraction and the delayed unload.
+ mBitmapUsages += 2;
+ recomputeColorExtractorMiniBitmap();
+ drawFrameInternal();
+
+ /*
+ * after loading, the bitmap will be unloaded after all these conditions:
+ * - the frame is redrawn
+ * - the mini bitmap from color extractor is recomputed
+ * - the DELAY_UNLOAD_BITMAP has passed
+ */
+ mBackgroundExecutor.executeDelayed(
+ this::unloadBitmapIfNotUsedSynchronized, DELAY_UNLOAD_BITMAP);
+ }
+ // even if the bitmap cannot be loaded, call reportEngineShown
+ if (!loadSuccess) reportEngineShown(false);
+ Trace.endSection();
+ }
+
+ private void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) {
+ try {
+ notifyLocalColorsChanged(regions, colors);
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ }
+
+ @VisibleForTesting
+ void recomputeColorExtractorMiniBitmap() {
+ mWallpaperColorExtractor.onBitmapChanged(mBitmap);
+ }
+
+ @VisibleForTesting
+ void onMiniBitmapUpdated() {
+ unloadBitmapIfNotUsed();
+ }
+
+ @Override
+ public boolean supportsLocalColorExtraction() {
+ return true;
+ }
+
+ @Override
+ public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+ // this call will activate the offset notifications
+ // if no colors were being processed before
+ mWallpaperColorExtractor.addLocalColorsAreas(regions);
+ }
+
+ @Override
+ public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+ // this call will deactivate the offset notifications
+ // if we are no longer processing colors
+ mWallpaperColorExtractor.removeLocalColorAreas(regions);
+ }
+
+ @Override
+ public void onOffsetsChanged(float xOffset, float yOffset,
+ float xOffsetStep, float yOffsetStep,
+ int xPixelOffset, int yPixelOffset) {
+ final int pages;
+ if (xOffsetStep > 0 && xOffsetStep <= 1) {
+ pages = Math.round(1 / xOffsetStep) + 1;
+ } else {
+ pages = 1;
+ }
+ if (pages != mPages || !mPagesComputed) {
+ mPages = pages;
+ mPagesComputed = true;
+ mWallpaperColorExtractor.onPageChanged(mPages);
+ }
}
@Override
@@ -764,13 +863,46 @@
}
@Override
- public void onDisplayChanged(int displayId) {
+ public void onDisplayRemoved(int displayId) {
}
@Override
- public void onDisplayRemoved(int displayId) {
+ public void onDisplayChanged(int displayId) {
+ // changes the display in the color extractor
+ // the new display dimensions will be used in the next color computation
+ if (displayId == getDisplayContext().getDisplayId()) {
+ getDisplaySizeAndUpdateColorExtractor();
+ }
+ }
+ private void getDisplaySizeAndUpdateColorExtractor() {
+ Rect window = getDisplayContext()
+ .getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics()
+ .getBounds();
+ mWallpaperColorExtractor.setDisplayDimensions(window.width(), window.height());
+ }
+
+
+ @Override
+ protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+ super.dump(prefix, fd, out, args);
+ out.print(prefix); out.print("Engine="); out.println(this);
+ out.print(prefix); out.print("valid surface=");
+ out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null
+ ? getSurfaceHolder().getSurface().isValid()
+ : "null");
+
+ out.print(prefix); out.print("surface frame=");
+ out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null");
+
+ out.print(prefix); out.print("bitmap=");
+ out.println(mBitmap == null ? "null"
+ : mBitmap.isRecycled() ? "recycled"
+ : mBitmap.getWidth() + "x" + mBitmap.getHeight());
+
+ mWallpaperColorExtractor.dump(prefix, fd, out, args);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java
deleted file mode 100644
index fdba16e..0000000
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java
+++ /dev/null
@@ -1,145 +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.wallpapers.canvas;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.SurfaceHolder;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Helper to draw a wallpaper on a surface.
- * It handles the geometry regarding the dimensions of the display and the wallpaper,
- * and rescales the surface and the wallpaper accordingly.
- */
-public class ImageCanvasWallpaperRenderer {
-
- private static final String TAG = ImageCanvasWallpaperRenderer.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private SurfaceHolder mSurfaceHolder;
- //private Bitmap mBitmap = null;
-
- @VisibleForTesting
- static final int MIN_SURFACE_WIDTH = 128;
- @VisibleForTesting
- static final int MIN_SURFACE_HEIGHT = 128;
-
- private boolean mSurfaceRedrawNeeded;
-
- private int mLastSurfaceWidth = -1;
- private int mLastSurfaceHeight = -1;
-
- public ImageCanvasWallpaperRenderer(SurfaceHolder surfaceHolder) {
- mSurfaceHolder = surfaceHolder;
- }
-
- /**
- * Set the surface holder on which to draw.
- * Should be called when the surface holder is created or changed
- * @param surfaceHolder the surface on which to draw the wallpaper
- */
- public void setSurfaceHolder(SurfaceHolder surfaceHolder) {
- mSurfaceHolder = surfaceHolder;
- }
-
- /**
- * Check if a surface holder is loaded
- * @return true if a valid surfaceHolder has been set.
- */
- public boolean isSurfaceHolderLoaded() {
- return mSurfaceHolder != null;
- }
-
- /**
- * Computes and set the surface dimensions, by using the play and the bitmap dimensions.
- * The Bitmap must be loaded before any call to this function
- */
- private boolean updateSurfaceSize(Bitmap bitmap) {
- int surfaceWidth = Math.max(MIN_SURFACE_WIDTH, bitmap.getWidth());
- int surfaceHeight = Math.max(MIN_SURFACE_HEIGHT, bitmap.getHeight());
- boolean surfaceChanged =
- surfaceWidth != mLastSurfaceWidth || surfaceHeight != mLastSurfaceHeight;
- if (surfaceChanged) {
- /*
- Used a fixed size surface, because we are special. We can do
- this because we know the current design of window animations doesn't
- cause this to break.
- */
- mSurfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
- mLastSurfaceWidth = surfaceWidth;
- mLastSurfaceHeight = surfaceHeight;
- }
- return surfaceChanged;
- }
-
- /**
- * Draw a the wallpaper on the surface.
- * The bitmap and the surface must be loaded before calling
- * this function.
- * @param forceRedraw redraw the wallpaper even if no changes are detected
- */
- public void drawFrame(Bitmap bitmap, boolean forceRedraw) {
-
- if (bitmap == null || bitmap.isRecycled()) {
- Log.e(TAG, "Attempt to draw frame before background is loaded:");
- return;
- }
-
- if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
- Log.e(TAG, "Attempt to set an invalid wallpaper of length "
- + bitmap.getWidth() + "x" + bitmap.getHeight());
- return;
- }
-
- mSurfaceRedrawNeeded |= forceRedraw;
- boolean surfaceChanged = updateSurfaceSize(bitmap);
-
- boolean redrawNeeded = surfaceChanged || mSurfaceRedrawNeeded;
- mSurfaceRedrawNeeded = false;
-
- if (!redrawNeeded) {
- if (DEBUG) {
- Log.d(TAG, "Suppressed drawFrame since redraw is not needed ");
- }
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "Redrawing wallpaper");
- }
- drawWallpaperWithCanvas(bitmap);
- }
-
- @VisibleForTesting
- void drawWallpaperWithCanvas(Bitmap bitmap) {
- Canvas c = mSurfaceHolder.lockHardwareCanvas();
- if (c != null) {
- Rect dest = mSurfaceHolder.getSurfaceFrame();
- Log.i(TAG, "Redrawing in rect: " + dest + " with surface size: "
- + mLastSurfaceWidth + "x" + mLastSurfaceHeight);
- try {
- c.drawBitmap(bitmap, null, dest, null);
- } finally {
- mSurfaceHolder.unlockCanvasAndPost(c);
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
new file mode 100644
index 0000000..e2e4555
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
@@ -0,0 +1,400 @@
+/*
+ * 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.wallpapers.canvas;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.MathUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.util.Assert;
+import com.android.systemui.wallpapers.ImageWallpaper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used by the {@link ImageWallpaper} to extract colors from areas of a wallpaper.
+ * It uses a background executor, and uses callbacks to inform that the work is done.
+ * It uses a downscaled version of the wallpaper to extract the colors.
+ */
+public class WallpaperColorExtractor {
+
+ private Bitmap mMiniBitmap;
+
+ @VisibleForTesting
+ static final int SMALL_SIDE = 128;
+
+ private static final String TAG = WallpaperColorExtractor.class.getSimpleName();
+ private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+ new RectF(0, 0, 1, 1);
+
+ private int mDisplayWidth = -1;
+ private int mDisplayHeight = -1;
+ private int mPages = -1;
+ private int mBitmapWidth = -1;
+ private int mBitmapHeight = -1;
+
+ private final Object mLock = new Object();
+
+ private final List<RectF> mPendingRegions = new ArrayList<>();
+ private final Set<RectF> mProcessedRegions = new ArraySet<>();
+
+ @Background
+ private final Executor mBackgroundExecutor;
+
+ private final WallpaperColorExtractorCallback mWallpaperColorExtractorCallback;
+
+ /**
+ * Interface to handle the callbacks after the different steps of the color extraction
+ */
+ public interface WallpaperColorExtractorCallback {
+ /**
+ * Callback after the colors of new regions have been extracted
+ * @param regions the list of new regions that have been processed
+ * @param colors the resulting colors for these regions, in the same order as the regions
+ */
+ void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors);
+
+ /**
+ * Callback after the mini bitmap is computed, to indicate that the wallpaper bitmap is
+ * no longer used by the color extractor and can be safely recycled
+ */
+ void onMiniBitmapUpdated();
+
+ /**
+ * Callback to inform that the extractor has started processing colors
+ */
+ void onActivated();
+
+ /**
+ * Callback to inform that no more colors are being processed
+ */
+ void onDeactivated();
+ }
+
+ /**
+ * Creates a new color extractor.
+ * @param backgroundExecutor the executor on which the color extraction will be performed
+ * @param wallpaperColorExtractorCallback an interface to handle the callbacks from
+ * the color extractor.
+ */
+ public WallpaperColorExtractor(@Background Executor backgroundExecutor,
+ WallpaperColorExtractorCallback wallpaperColorExtractorCallback) {
+ mBackgroundExecutor = backgroundExecutor;
+ mWallpaperColorExtractorCallback = wallpaperColorExtractorCallback;
+ }
+
+ /**
+ * Used by the outside to inform that the display size has changed.
+ * The new display size will be used in the next computations, but the current colors are
+ * not recomputed.
+ */
+ public void setDisplayDimensions(int displayWidth, int displayHeight) {
+ mBackgroundExecutor.execute(() ->
+ setDisplayDimensionsSynchronized(displayWidth, displayHeight));
+ }
+
+ private void setDisplayDimensionsSynchronized(int displayWidth, int displayHeight) {
+ synchronized (mLock) {
+ if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return;
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ processColorsInternal();
+ }
+ }
+
+ /**
+ * @return whether color extraction is currently in use
+ */
+ private boolean isActive() {
+ return mPendingRegions.size() + mProcessedRegions.size() > 0;
+ }
+
+ /**
+ * Should be called when the wallpaper is changed.
+ * This will recompute the mini bitmap
+ * and restart the extraction of all areas
+ * @param bitmap the new wallpaper
+ */
+ public void onBitmapChanged(@NonNull Bitmap bitmap) {
+ mBackgroundExecutor.execute(() -> onBitmapChangedSynchronized(bitmap));
+ }
+
+ private void onBitmapChangedSynchronized(@NonNull Bitmap bitmap) {
+ synchronized (mLock) {
+ if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
+ Log.e(TAG, "Attempt to extract colors from an invalid bitmap");
+ return;
+ }
+ mBitmapWidth = bitmap.getWidth();
+ mBitmapHeight = bitmap.getHeight();
+ mMiniBitmap = createMiniBitmap(bitmap);
+ mWallpaperColorExtractorCallback.onMiniBitmapUpdated();
+ recomputeColors();
+ }
+ }
+
+ /**
+ * Should be called when the number of pages is changed
+ * This will restart the extraction of all areas
+ * @param pages the total number of pages of the launcher
+ */
+ public void onPageChanged(int pages) {
+ mBackgroundExecutor.execute(() -> onPageChangedSynchronized(pages));
+ }
+
+ private void onPageChangedSynchronized(int pages) {
+ synchronized (mLock) {
+ if (mPages == pages) return;
+ mPages = pages;
+ if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) {
+ recomputeColors();
+ }
+ }
+ }
+
+ // helper to recompute colors, to be called in synchronized methods
+ private void recomputeColors() {
+ mPendingRegions.addAll(mProcessedRegions);
+ mProcessedRegions.clear();
+ processColorsInternal();
+ }
+
+ /**
+ * Add new regions to extract
+ * This will trigger the color extraction and call the callback only for these new regions
+ * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+ */
+ public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+ if (regions.size() > 0) {
+ mBackgroundExecutor.execute(() -> addLocalColorsAreasSynchronized(regions));
+ } else {
+ Log.w(TAG, "Attempt to add colors with an empty list");
+ }
+ }
+
+ private void addLocalColorsAreasSynchronized(@NonNull List<RectF> regions) {
+ synchronized (mLock) {
+ boolean wasActive = isActive();
+ mPendingRegions.addAll(regions);
+ if (!wasActive && isActive()) {
+ mWallpaperColorExtractorCallback.onActivated();
+ }
+ processColorsInternal();
+ }
+ }
+
+ /**
+ * Remove regions to extract. If a color extraction is ongoing does not stop it.
+ * But if there are subsequent changes that restart the extraction, the removed regions
+ * will not be recomputed.
+ * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+ */
+ public void removeLocalColorAreas(@NonNull List<RectF> regions) {
+ mBackgroundExecutor.execute(() -> removeLocalColorAreasSynchronized(regions));
+ }
+
+ private void removeLocalColorAreasSynchronized(@NonNull List<RectF> regions) {
+ synchronized (mLock) {
+ boolean wasActive = isActive();
+ mPendingRegions.removeAll(regions);
+ regions.forEach(mProcessedRegions::remove);
+ if (wasActive && !isActive()) {
+ mWallpaperColorExtractorCallback.onDeactivated();
+ }
+ }
+ }
+
+ /**
+ * Clean up the memory (in particular, the mini bitmap) used by this class.
+ */
+ public void cleanUp() {
+ mBackgroundExecutor.execute(this::cleanUpSynchronized);
+ }
+
+ private void cleanUpSynchronized() {
+ synchronized (mLock) {
+ if (mMiniBitmap != null) {
+ mMiniBitmap.recycle();
+ mMiniBitmap = null;
+ }
+ mProcessedRegions.clear();
+ mPendingRegions.clear();
+ }
+ }
+
+ private Bitmap createMiniBitmap(@NonNull Bitmap bitmap) {
+ Trace.beginSection("WallpaperColorExtractor#createMiniBitmap");
+ // if both sides of the image are larger than SMALL_SIDE, downscale the bitmap.
+ int smallestSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
+ float scale = Math.min(1.0f, (float) SMALL_SIDE / smallestSide);
+ Bitmap result = createMiniBitmap(bitmap,
+ (int) (scale * bitmap.getWidth()),
+ (int) (scale * bitmap.getHeight()));
+ Trace.endSection();
+ return result;
+ }
+
+ @VisibleForTesting
+ Bitmap createMiniBitmap(@NonNull Bitmap bitmap, int width, int height) {
+ return Bitmap.createScaledBitmap(bitmap, width, height, false);
+ }
+
+ private WallpaperColors getLocalWallpaperColors(@NonNull RectF area) {
+ RectF imageArea = pageToImgRect(area);
+ if (imageArea == null || !LOCAL_COLOR_BOUNDS.contains(imageArea)) {
+ return null;
+ }
+ Rect subImage = new Rect(
+ (int) Math.floor(imageArea.left * mMiniBitmap.getWidth()),
+ (int) Math.floor(imageArea.top * mMiniBitmap.getHeight()),
+ (int) Math.ceil(imageArea.right * mMiniBitmap.getWidth()),
+ (int) Math.ceil(imageArea.bottom * mMiniBitmap.getHeight()));
+ if (subImage.isEmpty()) {
+ // Do not notify client. treat it as too small to sample
+ return null;
+ }
+ return getLocalWallpaperColors(subImage);
+ }
+
+ @VisibleForTesting
+ WallpaperColors getLocalWallpaperColors(@NonNull Rect subImage) {
+ Assert.isNotMainThread();
+ Bitmap colorImg = Bitmap.createBitmap(mMiniBitmap,
+ subImage.left, subImage.top, subImage.width(), subImage.height());
+ return WallpaperColors.fromBitmap(colorImg);
+ }
+
+ /**
+ * Transform the logical coordinates into wallpaper coordinates.
+ *
+ * Logical coordinates are organised such that the various pages are non-overlapping. So,
+ * if there are n pages, the first page will have its X coordinate on the range [0-1/n].
+ *
+ * The real pages are overlapping. If the Wallpaper are a width Ww and the screen a width
+ * Ws, the relative width of a page Wr is Ws/Ww. This does not change if the number of
+ * pages increase.
+ * If there are n pages, the page k starts at the offset k * (1 - Wr) / (n - 1), as the
+ * last page is at position (1-Wr) and the others are regularly spread on the range [0-
+ * (1-Wr)].
+ */
+ private RectF pageToImgRect(RectF area) {
+ // Width of a page for the caller of this API.
+ float virtualPageWidth = 1f / (float) mPages;
+ float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
+ float rightPosOnPage = (area.right % virtualPageWidth) / virtualPageWidth;
+ int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
+
+ if (mDisplayWidth <= 0 || mDisplayHeight <= 0) {
+ Log.e(TAG, "Trying to extract colors with invalid display dimensions");
+ return null;
+ }
+
+ RectF imgArea = new RectF();
+ imgArea.bottom = area.bottom;
+ imgArea.top = area.top;
+
+ float imageScale = Math.min(((float) mBitmapHeight) / mDisplayHeight, 1);
+ float mappedScreenWidth = mDisplayWidth * imageScale;
+ float pageWidth = Math.min(1.0f,
+ mBitmapWidth > 0 ? mappedScreenWidth / (float) mBitmapWidth : 1.f);
+ float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
+
+ imgArea.left = MathUtils.constrain(
+ leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+ imgArea.right = MathUtils.constrain(
+ rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+ if (imgArea.left > imgArea.right) {
+ // take full page
+ imgArea.left = 0;
+ imgArea.right = 1;
+ }
+ return imgArea;
+ }
+
+ /**
+ * Extract the colors from the pending regions,
+ * then notify the callback with the resulting colors for these regions
+ * This method should only be called synchronously
+ */
+ private void processColorsInternal() {
+ /*
+ * if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been
+ * called, and thus the wallpaper is not yet loaded. In that case, exit, the function
+ * will be called again when the bitmap is loaded and the miniBitmap is computed.
+ */
+ if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
+
+ /*
+ * if the screen size or number of pages is not yet known, exit
+ * the function will be called again once the screen size and page are known
+ */
+ if (mDisplayWidth < 0 || mDisplayHeight < 0 || mPages < 0) return;
+
+ Trace.beginSection("WallpaperColorExtractor#processColorsInternal");
+ List<WallpaperColors> processedColors = new ArrayList<>();
+ for (int i = 0; i < mPendingRegions.size(); i++) {
+ RectF nextArea = mPendingRegions.get(i);
+ WallpaperColors colors = getLocalWallpaperColors(nextArea);
+
+ mProcessedRegions.add(nextArea);
+ processedColors.add(colors);
+ }
+ List<RectF> processedRegions = new ArrayList<>(mPendingRegions);
+ mPendingRegions.clear();
+ Trace.endSection();
+
+ mWallpaperColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
+ }
+
+ /**
+ * Called to dump current state.
+ * @param prefix prefix.
+ * @param fd fd.
+ * @param out out.
+ * @param args args.
+ */
+ public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+ out.print(prefix); out.print("display="); out.println(mDisplayWidth + "x" + mDisplayHeight);
+ out.print(prefix); out.print("mPages="); out.println(mPages);
+
+ out.print(prefix); out.print("bitmap dimensions=");
+ out.println(mBitmapWidth + "x" + mBitmapHeight);
+
+ out.print(prefix); out.print("bitmap=");
+ out.println(mMiniBitmap == null ? "null"
+ : mMiniBitmap.isRecycled() ? "recycled"
+ : mMiniBitmap.getWidth() + "x" + mMiniBitmap.getHeight());
+
+ out.print(prefix); out.print("PendingRegions size="); out.print(mPendingRegions.size());
+ out.print(prefix); out.print("ProcessedRegions size="); out.print(mProcessedRegions.size());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 3472cb1..fbc6a58 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 @@
* -> 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 @@
| 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 @@
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 @@
WakefulnessLifecycle wakefulnessLifecycle,
UserTracker userTracker,
@Main Executor sysUiMainExecutor) {
- super(context);
+ mContext = context;
mShell = shell;
mCommandQueue = commandQueue;
mConfigurationController = configurationController;
diff --git a/packages/SystemUI/tests/Android.bp b/packages/SystemUI/tests/Android.bp
new file mode 100644
index 0000000..3c418ed
--- /dev/null
+++ b/packages/SystemUI/tests/Android.bp
@@ -0,0 +1,50 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+ name: "SystemUITests",
+
+ dxflags: ["--multi-dex"],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ static_libs: ["SystemUI-tests"],
+ compile_multilib: "both",
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
+ libs: [
+ "android.test.runner",
+ "telephony-common",
+ "android.test.base",
+ ],
+ aaptflags: [
+ "--extra-packages com.android.systemui",
+ ],
+
+ // sign this with platform cert, so this test is allowed to inject key events into
+ // UI it doesn't own. This is necessary to allow screenshots to be taken
+ certificate: "platform",
+
+ additional_manifests: ["AndroidManifest.xml"],
+ manifest: "AndroidManifest-base.xml",
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
deleted file mode 100644
index ff5165d..0000000
--- a/packages/SystemUI/tests/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2011 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_JACK_FLAGS := --multi-dex native
-LOCAL_DX_FLAGS := --multi-dex
-
-LOCAL_PACKAGE_NAME := SystemUITests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- SystemUI-tests
-
-LOCAL_MULTILIB := both
-
-LOCAL_JNI_SHARED_LIBRARIES := \
- libdexmakerjvmtiagent \
- libmultiplejvmtiagentsinterferenceagent \
- libstaticjvmtiagent
-
-LOCAL_JAVA_LIBRARIES := \
- android.test.runner \
- telephony-common \
- android.test.base \
-
-LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui
-
-# sign this with platform cert, so this test is allowed to inject key events into
-# UI it doesn't own. This is necessary to allow screenshots to be taken
-LOCAL_CERTIFICATE := platform
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest.xml
-LOCAL_MANIFEST_FILE := AndroidManifest-base.xml
-
-# Provide jack a list of classes to exclude from code coverage.
-# This is needed because the SystemUITests compile SystemUI source directly, rather than using
-# LOCAL_INSTRUMENTATION_FOR := SystemUI.
-#
-# We want to exclude the test classes from code coverage measurements, but they share the same
-# package as the rest of SystemUI so they can't be easily filtered by package name.
-#
-# Generate a comma separated list of patterns based on the test source files under src/
-# SystemUI classes are in ../src/ so they won't be excluded.
-# Example:
-# Input files: src/com/android/systemui/Test.java src/com/android/systemui/AnotherTest.java
-# Generated exclude list: com.android.systemui.Test*,com.android.systemui.AnotherTest*
-
-# Filter all src files under src/ to just java files
-local_java_files := $(filter %.java,$(call all-java-files-under, src))
-# Transform java file names into full class names.
-# This only works if the class name matches the file name and the directory structure
-# matches the package.
-local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
-local_comma := ,
-local_empty :=
-local_space := $(local_empty) $(local_empty)
-# Convert class name list to jacoco exclude list
-# This appends a * to all classes and replace the space separators with commas.
-jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.*
-LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
-
-ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
- include $(BUILD_PACKAGE)
-endif
-
-# Reset variables
-local_java_files :=
-local_classes :=
-local_comma :=
-local_space :=
-jacoco_exclude :=
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index 80385e6..f5cd0ca 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -41,6 +41,7 @@
import android.testing.ViewUtils;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceView;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -84,6 +85,7 @@
MockitoAnnotations.initMocks(this);
mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
+ mKeyguardSecurityContainer.setId(View.generateViewId());
ViewUtils.attachView(mKeyguardSecurityContainer);
mTestableLooper = TestableLooper.get(this);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index c2c7dde..ecf7e0d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -29,7 +29,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -171,7 +170,7 @@
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(TelephonyManager.SIM_STATE_READY);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -191,7 +190,7 @@
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(TelephonyManager.SIM_STATE_READY);
when(mKeyguardUpdateMonitor.getSimState(1)).thenReturn(
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
@@ -224,7 +223,7 @@
@Test
public void testWrongSlots() {
reset(mCarrierTextCallback);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
new ArrayList<>());
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
@@ -238,7 +237,7 @@
@Test
public void testMoreSlotsThanSubs() {
reset(mCarrierTextCallback);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
new ArrayList<>());
// STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
@@ -289,7 +288,7 @@
list.add(TEST_SUBSCRIPTION);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_READY);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -314,7 +313,7 @@
list.add(TEST_SUBSCRIPTION_ROAMING);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_READY);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -339,7 +338,7 @@
list.add(TEST_SUBSCRIPTION_NULL);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_READY);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -364,7 +363,7 @@
list.add(TEST_SUBSCRIPTION_NULL);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_READY);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
mockWifi();
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -396,7 +395,7 @@
@Test
public void testCreateInfo_noSubscriptions() {
reset(mCarrierTextCallback);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
new ArrayList<>());
ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
@@ -421,7 +420,7 @@
list.add(TEST_SUBSCRIPTION);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_READY);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -446,7 +445,7 @@
when(mKeyguardUpdateMonitor.getSimState(anyInt()))
.thenReturn(TelephonyManager.SIM_STATE_READY)
.thenReturn(TelephonyManager.SIM_STATE_NOT_READY);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -471,7 +470,7 @@
when(mKeyguardUpdateMonitor.getSimState(anyInt()))
.thenReturn(TelephonyManager.SIM_STATE_NOT_READY)
.thenReturn(TelephonyManager.SIM_STATE_READY);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -498,7 +497,7 @@
.thenReturn(TelephonyManager.SIM_STATE_READY)
.thenReturn(TelephonyManager.SIM_STATE_NOT_READY)
.thenReturn(TelephonyManager.SIM_STATE_READY);
- when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 25e7dbb..03efd06 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -17,15 +17,21 @@
import android.content.BroadcastReceiver
import android.testing.AndroidTestingRunner
+import android.view.View
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.plugins.Clock
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.mockito.any
@@ -35,11 +41,15 @@
import com.android.systemui.util.mockito.mock
import java.util.TimeZone
import java.util.concurrent.Executor
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -54,29 +64,43 @@
class ClockEventControllerTest : SysuiTestCase() {
@JvmField @Rule val mockito = MockitoJUnit.rule()
- @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var keyguardInteractor: KeyguardInteractor
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var batteryController: BatteryController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var animations: ClockAnimations
@Mock private lateinit var events: ClockEvents
- @Mock private lateinit var clock: Clock
+ @Mock private lateinit var clock: ClockController
@Mock private lateinit var mainExecutor: Executor
@Mock private lateinit var bgExecutor: Executor
@Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var smallClockController: ClockFaceController
+ @Mock private lateinit var largeClockController: ClockFaceController
+ @Mock private lateinit var smallClockEvents: ClockFaceEvents
+ @Mock private lateinit var largeClockEvents: ClockFaceEvents
+ @Mock private lateinit var parentView: View
+ @Mock private lateinit var transitionRepository: KeyguardTransitionRepository
+ private lateinit var repository: FakeKeyguardRepository
- private lateinit var clockEventController: ClockEventController
+ private lateinit var underTest: ClockEventController
@Before
fun setUp() {
- whenever(clock.smallClock).thenReturn(TextView(context))
- whenever(clock.largeClock).thenReturn(TextView(context))
+ whenever(clock.smallClock).thenReturn(smallClockController)
+ whenever(clock.largeClock).thenReturn(largeClockController)
+ whenever(smallClockController.view).thenReturn(TextView(context))
+ whenever(largeClockController.view).thenReturn(TextView(context))
+ whenever(smallClockController.events).thenReturn(smallClockEvents)
+ whenever(largeClockController.events).thenReturn(largeClockEvents)
whenever(clock.events).thenReturn(events)
whenever(clock.animations).thenReturn(animations)
- clockEventController = ClockEventController(
- statusBarStateController,
+ repository = FakeKeyguardRepository()
+
+ underTest = ClockEventController(
+ KeyguardInteractor(repository = repository),
+ KeyguardTransitionInteractor(repository = transitionRepository),
broadcastDispatcher,
batteryController,
keyguardUpdateMonitor,
@@ -87,43 +111,44 @@
bgExecutor,
featureFlags
)
+ underTest.clock = clock
+
+ runBlocking(IMMEDIATE) {
+ underTest.registerListeners(parentView)
+
+ repository.setDozing(true)
+ repository.setDozeAmount(1f)
+ }
}
@Test
fun clockSet_validateInitialization() {
- clockEventController.clock = clock
-
verify(clock).initialize(any(), anyFloat(), anyFloat())
}
@Test
fun clockUnset_validateState() {
- clockEventController.clock = clock
- clockEventController.clock = null
+ underTest.clock = null
- assertEquals(clockEventController.clock, null)
+ assertEquals(underTest.clock, null)
}
@Test
- fun themeChanged_verifyClockPaletteUpdated() {
- clockEventController.clock = clock
- verify(events).onColorPaletteChanged(any(), any(), any())
-
- clockEventController.registerListeners()
+ fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
+ verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
+ verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
verify(configurationController).addCallback(capture(captor))
captor.value.onThemeChanged()
- verify(events, times(2)).onColorPaletteChanged(any(), any(), any())
+ verify(events).onColorPaletteChanged(any())
}
@Test
- fun fontChanged_verifyFontSizeUpdated() {
- clockEventController.clock = clock
- verify(events).onColorPaletteChanged(any(), any(), any())
-
- clockEventController.registerListeners()
+ fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) {
+ verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
+ verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
verify(configurationController).addCallback(capture(captor))
@@ -133,10 +158,7 @@
}
@Test
- fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
verify(batteryController).addCallback(capture(batteryCaptor))
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -148,26 +170,21 @@
}
@Test
- fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
+ fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() =
+ runBlocking(IMMEDIATE) {
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
- val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
- verify(batteryController).addCallback(capture(batteryCaptor))
- val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
- keyguardCaptor.value.onKeyguardVisibilityChanged(true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, true)
-
- verify(animations, times(1)).charge()
- }
+ verify(animations, times(1)).charge()
+ }
@Test
- fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
verify(batteryController).addCallback(capture(batteryCaptor))
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -179,25 +196,20 @@
}
@Test
- fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
+ fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() =
+ runBlocking(IMMEDIATE) {
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, false)
- val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
- verify(batteryController).addCallback(capture(batteryCaptor))
- val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
- keyguardCaptor.value.onKeyguardVisibilityChanged(true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, false)
-
- verify(animations, never()).charge()
- }
+ verify(animations, never()).charge()
+ }
@Test
- fun localeCallback_verifyClockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun localeCallback_verifyClockNotified() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<BroadcastReceiver>()
verify(broadcastDispatcher).registerReceiver(
capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
@@ -208,10 +220,7 @@
}
@Test
- fun keyguardCallback_visibilityChanged_clockDozeCalled() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
@@ -223,10 +232,7 @@
}
@Test
- fun keyguardCallback_timeFormat_clockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun keyguardCallback_timeFormat_clockNotified() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
captor.value.onTimeFormatChanged("12h")
@@ -235,11 +241,8 @@
}
@Test
- fun keyguardCallback_timezoneChanged_clockNotified() {
+ fun keyguardCallback_timezoneChanged_clockNotified() = runBlocking(IMMEDIATE) {
val mockTimeZone = mock<TimeZone>()
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
captor.value.onTimeZoneChanged(mockTimeZone)
@@ -248,10 +251,7 @@
}
@Test
- fun keyguardCallback_userSwitched_clockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun keyguardCallback_userSwitched_clockNotified() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
captor.value.onUserSwitchComplete(10)
@@ -260,25 +260,27 @@
}
@Test
- fun keyguardCallback_verifyKeyguardChanged() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
+ fun keyguardCallback_verifyKeyguardChanged() = runBlocking(IMMEDIATE) {
+ val job = underTest.listenForDozeAmount(this)
+ repository.setDozeAmount(0.4f)
- val captor = argumentCaptor<StatusBarStateController.StateListener>()
- verify(statusBarStateController).addCallback(capture(captor))
- captor.value.onDozeAmountChanged(0.4f, 0.6f)
+ yield()
verify(animations).doze(0.4f)
+
+ job.cancel()
}
@Test
- fun unregisterListeners_validate() {
- clockEventController.clock = clock
- clockEventController.unregisterListeners()
+ fun unregisterListeners_validate() = runBlocking(IMMEDIATE) {
+ underTest.unregisterListeners()
verify(broadcastDispatcher).unregisterReceiver(any())
verify(configurationController).removeCallback(any())
verify(batteryController).removeCallback(any())
verify(keyguardUpdateMonitor).removeCallback(any())
- verify(statusBarStateController).removeCallback(any())
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
index aa671d1..91b544b 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.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 @@
whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker)
whenever(sessionTracker.getSessionId(anyInt())).thenReturn(sessionId)
keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
- mContext,
uiEventLogger,
keyguardUpdateMonitor,
sessionTracker)
@@ -195,4 +194,4 @@
verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallbackCaptor.capture())
updateMonitorCallback = updateMonitorCallbackCaptor.value
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 635ee9e..bb03a47 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -45,7 +45,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.shared.clocks.ClockRegistry;
@@ -87,7 +87,7 @@
@Mock
KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
- private Clock mClock;
+ private ClockController mClock;
@Mock
DumpManager mDumpManager;
@Mock
@@ -265,6 +265,6 @@
private void verifyAttachment(VerificationMode times) {
verify(mClockRegistry, times).registerClockChangeListener(
any(ClockRegistry.ClockChangeListener.class));
- verify(mClockEventController, times).registerListeners();
+ verify(mClockEventController, times).registerListeners(mView);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index a0295d0..254f953 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -41,7 +41,8 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.ClockFaceController;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Before;
@@ -61,7 +62,13 @@
ViewGroup mMockKeyguardSliceView;
@Mock
- Clock mClock;
+ ClockController mClock;
+
+ @Mock
+ ClockFaceController mSmallClock;
+
+ @Mock
+ ClockFaceController mLargeClock;
private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
@@ -75,8 +82,11 @@
when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
.thenReturn(mMockKeyguardSliceView);
- when(mClock.getSmallClock()).thenReturn(new TextView(getContext()));
- when(mClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ when(mClock.getSmallClock()).thenReturn(mSmallClock);
+ when(mClock.getLargeClock()).thenReturn(mLargeClock);
+
+ when(mSmallClock.getView()).thenReturn(new TextView(getContext()));
+ when(mLargeClock.getView()).thenReturn(new TextView(getContext()));
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@@ -124,41 +134,49 @@
public void onPluginConnected_showClock() {
mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
- assertEquals(mClock.getSmallClock().getParent(), mSmallClockFrame);
- assertEquals(mClock.getLargeClock().getParent(), mLargeClockFrame);
+ assertEquals(mClock.getSmallClock().getView().getParent(), mSmallClockFrame);
+ assertEquals(mClock.getLargeClock().getView().getParent(), mLargeClockFrame);
}
@Test
public void onPluginConnected_showSecondPluginClock() {
// GIVEN a plugin has already connected
- Clock otherClock = mock(Clock.class);
- when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
- when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ ClockController otherClock = mock(ClockController.class);
+ ClockFaceController smallClock = mock(ClockFaceController.class);
+ ClockFaceController largeClock = mock(ClockFaceController.class);
+ when(otherClock.getSmallClock()).thenReturn(smallClock);
+ when(otherClock.getLargeClock()).thenReturn(largeClock);
+ when(smallClock.getView()).thenReturn(new TextView(getContext()));
+ when(largeClock.getView()).thenReturn(new TextView(getContext()));
mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
// THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
- assertThat(otherClock.getSmallClock().getParent()).isEqualTo(mSmallClockFrame);
- assertThat(otherClock.getLargeClock().getParent()).isEqualTo(mLargeClockFrame);
- assertThat(mClock.getSmallClock().getParent()).isNull();
- assertThat(mClock.getLargeClock().getParent()).isNull();
+ assertThat(otherClock.getSmallClock().getView().getParent()).isEqualTo(mSmallClockFrame);
+ assertThat(otherClock.getLargeClock().getView().getParent()).isEqualTo(mLargeClockFrame);
+ assertThat(mClock.getSmallClock().getView().getParent()).isNull();
+ assertThat(mClock.getLargeClock().getView().getParent()).isNull();
}
@Test
public void onPluginDisconnected_secondOfTwoDisconnected() {
// GIVEN two plugins are connected
- Clock otherClock = mock(Clock.class);
- when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
- when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ ClockController otherClock = mock(ClockController.class);
+ ClockFaceController smallClock = mock(ClockFaceController.class);
+ ClockFaceController largeClock = mock(ClockFaceController.class);
+ when(otherClock.getSmallClock()).thenReturn(smallClock);
+ when(otherClock.getLargeClock()).thenReturn(largeClock);
+ when(smallClock.getView()).thenReturn(new TextView(getContext()));
+ when(largeClock.getView()).thenReturn(new TextView(getContext()));
mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
// WHEN the second plugin is disconnected
mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
// THEN nothing should be shown
- assertThat(otherClock.getSmallClock().getParent()).isNull();
- assertThat(otherClock.getLargeClock().getParent()).isNull();
- assertThat(mClock.getSmallClock().getParent()).isNull();
- assertThat(mClock.getLargeClock().getParent()).isNull();
+ assertThat(otherClock.getSmallClock().getView().getParent()).isNull();
+ assertThat(otherClock.getLargeClock().getView().getParent()).isNull();
+ assertThat(mClock.getSmallClock().getView().getParent()).isNull();
+ assertThat(mClock.getLargeClock().getView().getParent()).isNull();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index c6ebaa8..48e8239 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 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 @@
.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 @@
.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 @@
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 c1036e3..82d3ca7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -21,10 +21,14 @@
import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE;
import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
+import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD;
+import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
+
import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
@@ -32,9 +36,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -43,19 +44,17 @@
import android.graphics.Insets;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.widget.FrameLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.settings.GlobalSettings;
@@ -64,8 +63,6 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -78,14 +75,11 @@
public class KeyguardSecurityContainerTest extends SysuiTestCase {
private static final int VIEW_WIDTH = 1600;
-
- private int mScreenWidth;
- private int mFakeMeasureSpec;
+ private static final int VIEW_HEIGHT = 900;
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
- @Mock
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
@Mock
private GlobalSettings mGlobalSettings;
@@ -93,66 +87,39 @@
private FalsingManager mFalsingManager;
@Mock
private UserSwitcherController mUserSwitcherController;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Captor
- private ArgumentCaptor<FrameLayout.LayoutParams> mLayoutCaptor;
private KeyguardSecurityContainer mKeyguardSecurityContainer;
- private FrameLayout.LayoutParams mSecurityViewFlipperLayoutParams;
@Before
public void setup() {
// Needed here, otherwise when mKeyguardSecurityContainer is created below, it'll cache
// the real references (rather than the TestableResources that this call creates).
mContext.ensureTestableResources();
- mSecurityViewFlipperLayoutParams = new FrameLayout.LayoutParams(
- MATCH_PARENT, MATCH_PARENT);
- when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
+ mSecurityViewFlipper = new KeyguardSecurityViewFlipper(getContext());
+ mSecurityViewFlipper.setId(View.generateViewId());
mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
+ mKeyguardSecurityContainer.setRight(VIEW_WIDTH);
+ mKeyguardSecurityContainer.setLeft(0);
+ mKeyguardSecurityContainer.setTop(0);
+ mKeyguardSecurityContainer.setBottom(VIEW_HEIGHT);
+ mKeyguardSecurityContainer.setId(View.generateViewId());
mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
mKeyguardSecurityContainer.addView(mSecurityViewFlipper, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User");
when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true);
-
- mScreenWidth = getUiDevice().getDisplayWidth();
- mFakeMeasureSpec = View
- .MeasureSpec.makeMeasureSpec(mScreenWidth, View.MeasureSpec.EXACTLY);
}
-
@Test
- public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() {
- mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
-
- int halfWidthMeasureSpec =
- View.MeasureSpec.makeMeasureSpec(mScreenWidth / 2, View.MeasureSpec.EXACTLY);
- mKeyguardSecurityContainer.onMeasure(mFakeMeasureSpec, mFakeMeasureSpec);
-
- verify(mSecurityViewFlipper).measure(halfWidthMeasureSpec, mFakeMeasureSpec);
- }
-
- @Test
- public void onMeasure_usesFullWidthWithOneHandedModeDisabled() {
- mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
-
- mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
- verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, mFakeMeasureSpec);
- }
-
- @Test
- public void onMeasure_respectsViewInsets() {
+ public void testOnApplyWindowInsets() {
int paddingBottom = getContext().getResources()
.getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
int imeInsetAmount = paddingBottom + 1;
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);
@@ -162,24 +129,19 @@
.setInsetsIgnoringVisibility(systemBars(), systemBarInset)
.build();
- // It's reduced by the max of the systembar and IME, so just subtract IME inset.
- int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
- mScreenWidth - imeInsetAmount, View.MeasureSpec.EXACTLY);
-
mKeyguardSecurityContainer.onApplyWindowInsets(insets);
- mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
- verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, expectedHeightMeasureSpec);
+ assertThat(mKeyguardSecurityContainer.getPaddingBottom()).isEqualTo(imeInsetAmount);
}
@Test
- public void onMeasure_respectsViewInsets_largerSystembar() {
+ public void testOnApplyWindowInsets_largerSystembar() {
int imeInsetAmount = 0;
int paddingBottom = getContext().getResources()
.getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
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);
@@ -189,25 +151,23 @@
.setInsetsIgnoringVisibility(systemBars(), systemBarInset)
.build();
- int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
- mScreenWidth - systemBarInsetAmount, View.MeasureSpec.EXACTLY);
-
mKeyguardSecurityContainer.onApplyWindowInsets(insets);
- mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
- verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, expectedHeightMeasureSpec);
+ assertThat(mKeyguardSecurityContainer.getPaddingBottom()).isEqualTo(systemBarInsetAmount);
}
- private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
- int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
- mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
-
- mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
- mKeyguardSecurityContainer.layout(0, 0, mScreenWidth, mScreenWidth);
-
- // Clear any interactions with the mock so we know the interactions definitely come from the
- // below testing.
- reset(mSecurityViewFlipper);
+ @Test
+ public void testDefaultViewMode() {
+ mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
+ mUserSwitcherController, () -> {
+ });
+ mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
+ mUserSwitcherController, () -> {});
+ ConstraintSet.Constraint viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.startToStart).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.endToEnd).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
}
@Test
@@ -217,13 +177,22 @@
mKeyguardSecurityContainer.getWidth() - 1f);
verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_RIGHT);
- assertSecurityTranslationX(
- mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
+ ConstraintSet.Constraint viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.widthPercent).isEqualTo(0.5f);
+ assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
mKeyguardSecurityContainer.updatePositionByTouchX(1f);
verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_LEFT);
- verify(mSecurityViewFlipper).setTranslationX(0.0f);
+ viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.widthPercent).isEqualTo(0.5f);
+ assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
}
@Test
@@ -232,10 +201,16 @@
mKeyguardSecurityContainer.updatePositionByTouchX(
mKeyguardSecurityContainer.getWidth() - 1f);
- verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
+ ConstraintSet.Constraint viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
mKeyguardSecurityContainer.updatePositionByTouchX(1f);
- verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
+ viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
}
@Test
@@ -249,17 +224,31 @@
setupUserSwitcher();
mKeyguardSecurityContainer.onConfigurationChanged(landscapeConfig);
- // THEN views are oriented side by side
- assertSecurityGravity(Gravity.LEFT | Gravity.BOTTOM);
- assertUserSwitcherGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
- assertSecurityTranslationX(
- mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
- assertUserSwitcherTranslationX(0f);
-
+ ConstraintSet.Constraint viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ ConstraintSet.Constraint userSwitcherConstraint =
+ getViewConstraint(R.id.keyguard_bouncer_user_switcher);
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.leftToRight).isEqualTo(
+ R.id.keyguard_bouncer_user_switcher);
+ assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.rightToLeft).isEqualTo(
+ mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.bottomMargin).isEqualTo(
+ getContext().getResources().getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_y_trans));
+ assertThat(viewFlipperConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
+ assertThat(userSwitcherConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
+ assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
+ assertThat(userSwitcherConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
}
@Test
- public void testUserSwitcherModeViewGravityPortrait() {
+ public void testUserSwitcherModeViewPositionPortrait() {
// GIVEN one user has been setup and in landscape
when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
Configuration portraitConfig = configuration(ORIENTATION_PORTRAIT);
@@ -267,15 +256,28 @@
// WHEN UserSwitcherViewMode is initialized and config has changed
setupUserSwitcher();
- reset(mSecurityViewFlipper);
- when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
mKeyguardSecurityContainer.onConfigurationChanged(portraitConfig);
- // THEN views are both centered horizontally
- assertSecurityGravity(Gravity.CENTER_HORIZONTAL);
- assertUserSwitcherGravity(Gravity.CENTER_HORIZONTAL);
- assertSecurityTranslationX(0);
- assertUserSwitcherTranslationX(0);
+ ConstraintSet.Constraint viewFlipperConstraint =
+ getViewConstraint(mSecurityViewFlipper.getId());
+ ConstraintSet.Constraint userSwitcherConstraint =
+ getViewConstraint(R.id.keyguard_bouncer_user_switcher);
+
+ assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.topMargin).isEqualTo(
+ getContext().getResources().getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_y_trans));
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.verticalChainStyle).isEqualTo(CHAIN_SPREAD);
+ assertThat(userSwitcherConstraint.layout.verticalChainStyle).isEqualTo(CHAIN_SPREAD);
+ assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
+ assertThat(userSwitcherConstraint.layout.mHeight).isEqualTo(WRAP_CONTENT);
+ assertThat(userSwitcherConstraint.layout.mWidth).isEqualTo(WRAP_CONTENT);
}
@Test
@@ -337,9 +339,9 @@
when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT);
mKeyguardSecurityContainer.onConfigurationChanged(new Configuration());
- assertSecurityTranslationX(0);
- assertUserSwitcherTranslationX(
- mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
+ ConstraintSet.Constraint viewFlipperConstraint = getViewConstraint(
+ mSecurityViewFlipper.getId());
+ assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
}
private Configuration configuration(@Configuration.Orientation int orientation) {
@@ -348,28 +350,6 @@
return config;
}
- private void assertSecurityTranslationX(float translation) {
- verify(mSecurityViewFlipper).setTranslationX(translation);
- }
-
- private void assertUserSwitcherTranslationX(float translation) {
- ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
- R.id.keyguard_bouncer_user_switcher);
- assertThat(userSwitcher.getTranslationX()).isEqualTo(translation);
- }
-
- private void assertUserSwitcherGravity(@Gravity.GravityFlags int gravity) {
- ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
- R.id.keyguard_bouncer_user_switcher);
- assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
- .isEqualTo(gravity);
- }
-
- private void assertSecurityGravity(@Gravity.GravityFlags int gravity) {
- verify(mSecurityViewFlipper, atLeastOnce()).setLayoutParams(mLayoutCaptor.capture());
- assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(gravity);
- }
-
private void setViewWidth(int width) {
mKeyguardSecurityContainer.setRight(width);
mKeyguardSecurityContainer.setLeft(0);
@@ -398,10 +378,7 @@
private void setupUserSwitcher() {
when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
- mGlobalSettings, mFalsingManager, mUserSwitcherController);
- // reset mSecurityViewFlipper so setup doesn't influence test verifications
- reset(mSecurityViewFlipper);
- when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
+ mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> {});
}
private ArrayList<UserRecord> buildUserRecords(int count) {
@@ -411,8 +388,22 @@
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;
}
+
+ private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
+ int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
+ mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
+ mUserSwitcherController, () -> {});
+ }
+
+ /** Get the ConstraintLayout constraint of the view. */
+ private ConstraintSet.Constraint getViewConstraint(int viewId) {
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mKeyguardSecurityContainer);
+ return constraintSet.getConstraint(viewId);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 9aa71e9..784e7dd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -607,7 +607,7 @@
public void testTriesToAuthenticate_whenKeyguard() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
@@ -617,7 +617,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -630,7 +630,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -654,7 +654,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
// Stop scanning when bouncer becomes visible
@@ -668,7 +668,7 @@
@Test
public void testTriesToAuthenticate_whenAssistant() {
- mKeyguardUpdateMonitor.setKeyguardOccluded(true);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
mKeyguardUpdateMonitor.setAssistantVisible(true);
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -683,7 +683,7 @@
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
new ArrayList<>());
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
@@ -693,7 +693,7 @@
mTestableLooper.processAllMessages();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -705,7 +705,7 @@
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -717,7 +717,7 @@
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
@@ -738,7 +738,7 @@
public void testFaceAndFingerprintLockout_onlyFace() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
faceAuthLockedOut();
@@ -749,7 +749,7 @@
public void testFaceAndFingerprintLockout_onlyFingerprint() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
@@ -761,7 +761,7 @@
public void testFaceAndFingerprintLockout() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
faceAuthLockedOut();
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
@@ -860,7 +860,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
@@ -905,7 +905,7 @@
mTestableLooper.processAllMessages();
List<SubscriptionInfo> listToVerify = mKeyguardUpdateMonitor
- .getFilteredSubscriptionInfo(false);
+ .getFilteredSubscriptionInfo();
assertThat(listToVerify.size()).isEqualTo(1);
assertThat(listToVerify.get(0)).isEqualTo(TEST_SUBSCRIPTION_2);
}
@@ -1033,8 +1033,7 @@
public void testOccludingAppFingerprintListeningState() {
// GIVEN keyguard isn't visible (app occluding)
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
- mKeyguardUpdateMonitor.setKeyguardOccluded(true);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
// THEN we shouldn't listen for fingerprints
@@ -1049,8 +1048,7 @@
public void testOccludingAppRequestsFingerprint() {
// GIVEN keyguard isn't visible (app occluding)
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
- mKeyguardUpdateMonitor.setKeyguardOccluded(true);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
// WHEN an occluding app requests fp
mKeyguardUpdateMonitor.requestFingerprintAuthOnOccludingApp(true);
@@ -1142,7 +1140,7 @@
setKeyguardBouncerVisibility(false /* isVisible */);
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
when(mKeyguardBypassController.canBypass()).thenReturn(true);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
// WHEN status bar state reports a change to the keyguard that would normally indicate to
// start running face auth
@@ -1153,8 +1151,9 @@
// listening state to update
assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(false);
- // WHEN biometric listening state is updated
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ // WHEN biometric listening state is updated when showing state changes from false => true
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
// THEN face unlock is running
assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(true);
@@ -1520,7 +1519,7 @@
public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
@@ -1529,7 +1528,7 @@
mKeyguardUpdateMonitor.onFaceAuthenticated(0, false);
// Make sure keyguard is going away after face auth attempt, and that it calls
// updateBiometricStateListeningState.
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
mTestableLooper.processAllMessages();
verify(mHandler).postDelayed(mKeyguardUpdateMonitor.mFpCancelNotReceived,
@@ -1589,7 +1588,7 @@
}
private void keyguardIsVisible() {
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
}
private void triggerAuthInterrupt() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index e363f17..181839a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -232,7 +232,7 @@
}
@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/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 8fc0489..6ab54a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -718,7 +718,7 @@
}
@Test
- fun animatesViewRemovalFromStartToEnd() {
+ fun animatesViewRemovalFromStartToEnd_viewHasSiblings() {
setUpRootWithChildren()
val child = rootView.getChildAt(0)
@@ -742,6 +742,35 @@
}
@Test
+ fun animatesViewRemovalFromStartToEnd_viewHasNoSiblings() {
+ rootView = LinearLayout(mContext)
+ (rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL
+ (rootView as LinearLayout).weightSum = 1f
+
+ val onlyChild = View(mContext)
+ rootView.addView(onlyChild)
+ forceLayout()
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ onlyChild,
+ destination = ViewHierarchyAnimator.Hotspot.LEFT,
+ interpolator = Interpolators.LINEAR
+ )
+
+ assertTrue(success)
+ assertNotNull(onlyChild.getTag(R.id.tag_animator))
+ checkBounds(onlyChild, l = 0, t = 0, r = 200, b = 100)
+ advanceAnimation(onlyChild, 0.5f)
+ checkBounds(onlyChild, l = 0, t = 0, r = 100, b = 100)
+ advanceAnimation(onlyChild, 1.0f)
+ checkBounds(onlyChild, l = 0, t = 0, r = 0, b = 100)
+ endAnimation(rootView)
+ endAnimation(onlyChild)
+ assertEquals(0, rootView.childCount)
+ assertFalse(onlyChild in rootView.children)
+ }
+
+ @Test
fun animatesViewRemovalRespectingDestination() {
// CENTER
setUpRootWithChildren()
@@ -906,6 +935,251 @@
checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100)
}
+ /* ******** start of animatesViewRemoval_includeMarginsTrue tests ******** */
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_center() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalTop = removedChild.top
+ val originalRight = removedChild.right
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ val expectedX = ((originalLeft - M_LEFT) + (originalRight + M_RIGHT)) / 2
+ val expectedY = ((originalTop - M_TOP) + (originalBottom + M_BOTTOM)) / 2
+
+ checkBounds(
+ removedChild,
+ l = expectedX,
+ t = expectedY,
+ r = expectedX,
+ b = expectedY
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_left() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalTop = removedChild.top
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.LEFT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalLeft - M_LEFT,
+ t = originalTop,
+ r = originalLeft - M_LEFT,
+ b = originalBottom
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_topLeft() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalTop = removedChild.top
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalLeft - M_LEFT,
+ t = originalTop - M_TOP,
+ r = originalLeft - M_LEFT,
+ b = originalTop - M_TOP
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_top() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalTop = removedChild.top
+ val originalRight = removedChild.right
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalLeft,
+ t = originalTop - M_TOP,
+ r = originalRight,
+ b = originalTop - M_TOP
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_topRight() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalTop = removedChild.top
+ val originalRight = removedChild.right
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalRight + M_RIGHT,
+ t = originalTop - M_TOP,
+ r = originalRight + M_RIGHT,
+ b = originalTop - M_TOP
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_right() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalTop = removedChild.top
+ val originalRight = removedChild.right
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.RIGHT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalRight + M_RIGHT,
+ t = originalTop,
+ r = originalRight + M_RIGHT,
+ b = originalBottom
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_bottomRight() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalRight = removedChild.right
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalRight + M_RIGHT,
+ t = originalBottom + M_BOTTOM,
+ r = originalRight + M_RIGHT,
+ b = originalBottom + M_BOTTOM
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_bottom() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalRight = removedChild.right
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalLeft,
+ t = originalBottom + M_BOTTOM,
+ r = originalRight,
+ b = originalBottom + M_BOTTOM
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_bottomLeft() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalLeft - M_LEFT,
+ t = originalBottom + M_BOTTOM,
+ r = originalLeft - M_LEFT,
+ b = originalBottom + M_BOTTOM
+ )
+ }
+ /* ******** end of animatesViewRemoval_includeMarginsTrue tests ******** */
+
@Test
fun animatesChildrenDuringViewRemoval() {
setUpRootWithChildren()
@@ -964,6 +1238,60 @@
}
@Test
+ fun animateRemoval_runnableRunsWhenAnimationEnds() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ setUpRootWithChildren()
+ forceLayout()
+ val removedView = rootView.getChildAt(0)
+
+ ViewHierarchyAnimator.animateRemoval(
+ removedView,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ endAnimation(removedView)
+
+ assertEquals(true, runnableRun)
+ }
+
+ @Test
+ fun animateRemoval_runnableDoesNotRunWhenAnimationCancelled() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ setUpRootWithChildren()
+ forceLayout()
+ val removedView = rootView.getChildAt(0)
+
+ ViewHierarchyAnimator.animateRemoval(
+ removedView,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ cancelAnimation(removedView)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
+ fun animationRemoval_runnableDoesNotRunWhenOnlyPartwayThroughAnimation() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ setUpRootWithChildren()
+ forceLayout()
+ val removedView = rootView.getChildAt(0)
+
+ ViewHierarchyAnimator.animateRemoval(
+ removedView,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ advanceAnimation(removedView, 0.5f)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
fun cleansUpListenersCorrectly() {
val firstChild = View(mContext)
firstChild.layoutParams = LinearLayout.LayoutParams(50 /* width */, 100 /* height */)
@@ -1132,7 +1460,7 @@
checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
}
- private fun setUpRootWithChildren() {
+ private fun setUpRootWithChildren(includeMarginsOnFirstChild: Boolean = false) {
rootView = LinearLayout(mContext)
(rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL
(rootView as LinearLayout).weightSum = 1f
@@ -1146,13 +1474,26 @@
val secondChild = View(mContext)
rootView.addView(secondChild)
- val childParams = LinearLayout.LayoutParams(
+ val firstChildParams = LinearLayout.LayoutParams(
0 /* width */,
LinearLayout.LayoutParams.MATCH_PARENT
)
- childParams.weight = 0.5f
- firstChild.layoutParams = childParams
- secondChild.layoutParams = childParams
+ firstChildParams.weight = 0.5f
+ if (includeMarginsOnFirstChild) {
+ firstChildParams.leftMargin = M_LEFT
+ firstChildParams.topMargin = M_TOP
+ firstChildParams.rightMargin = M_RIGHT
+ firstChildParams.bottomMargin = M_BOTTOM
+ }
+ firstChild.layoutParams = firstChildParams
+
+ val secondChildParams = LinearLayout.LayoutParams(
+ 0 /* width */,
+ LinearLayout.LayoutParams.MATCH_PARENT
+ )
+ secondChildParams.weight = 0.5f
+ secondChild.layoutParams = secondChildParams
+
firstGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */)
(firstGrandChild.layoutParams as RelativeLayout.LayoutParams)
.addRule(RelativeLayout.ALIGN_PARENT_START)
@@ -1232,3 +1573,9 @@
}
}
}
+
+// Margin values.
+private const val M_LEFT = 14
+private const val M_TOP = 16
+private const val M_RIGHT = 18
+private const val M_BOTTOM = 20
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
new file mode 100644
index 0000000..dd9683f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.assist.ui;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+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)
+public class DisplayUtilsTest extends SysuiTestCase {
+
+ @Mock
+ Resources mResources;
+ @Mock
+ Context mMockContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testGetCornerRadii_noOverlay() {
+ assertEquals(0, DisplayUtils.getCornerRadiusBottom(mContext));
+ assertEquals(0, DisplayUtils.getCornerRadiusTop(mContext));
+ }
+
+ @Test
+ public void testGetCornerRadii_onlyDefaultOverridden() {
+ when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size)).thenReturn(100);
+ when(mMockContext.getResources()).thenReturn(mResources);
+
+ assertEquals(100, DisplayUtils.getCornerRadiusBottom(mMockContext));
+ assertEquals(100, DisplayUtils.getCornerRadiusTop(mMockContext));
+ }
+
+ @Test
+ public void testGetCornerRadii_allOverridden() {
+ when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size)).thenReturn(100);
+ when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size_top)).thenReturn(
+ 150);
+ when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size_bottom)).thenReturn(
+ 200);
+ when(mMockContext.getResources()).thenReturn(mResources);
+
+ assertEquals(200, DisplayUtils.getCornerRadiusBottom(mMockContext));
+ assertEquals(150, DisplayUtils.getCornerRadiusTop(mMockContext));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 37bb0c2..0b528a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -117,13 +117,12 @@
}
@Test
- fun testFingerprintTrigger_KeyguardVisible_Ripple() {
- // GIVEN fp exists, keyguard is visible, user doesn't need strong auth
+ fun testFingerprintTrigger_KeyguardShowing_Ripple() {
+ // GIVEN fp exists, keyguard is showing, user doesn't need strong auth
val fpsLocation = Point(5, 5)
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
- `when`(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
// WHEN fingerprint authenticated
@@ -140,39 +139,15 @@
}
@Test
- fun testFingerprintTrigger_Dreaming_Ripple() {
- // GIVEN fp exists, keyguard is visible, user doesn't need strong auth
- val fpsLocation = Point(5, 5)
- `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
- controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(false)
- `when`(keyguardUpdateMonitor.isDreaming).thenReturn(true)
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
-
- // WHEN fingerprint authenticated
- val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
- verify(keyguardUpdateMonitor).registerCallback(captor.capture())
- captor.value.onBiometricAuthenticated(
- 0 /* userId */,
- BiometricSourceType.FINGERPRINT /* type */,
- false /* isStrongBiometric */)
-
- // THEN update sensor location and show ripple
- verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
- verify(rippleView).startUnlockedRipple(any())
- }
-
- @Test
- fun testFingerprintTrigger_KeyguardNotVisible_NotDreaming_NoRipple() {
+ fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
// GIVEN fp exists & user doesn't need strong auth
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
- // WHEN keyguard is NOT visible & fingerprint authenticated
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(false)
- `when`(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+ // WHEN keyguard is NOT showing & fingerprint authenticated
+ `when`(keyguardStateController.isShowing).thenReturn(false)
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
captor.value.onBiometricAuthenticated(
@@ -186,11 +161,11 @@
@Test
fun testFingerprintTrigger_StrongAuthRequired_NoRipple() {
- // GIVEN fp exists & keyguard is visible
+ // GIVEN fp exists & keyguard is showing
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
// WHEN user needs strong auth & fingerprint authenticated
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(true)
@@ -207,12 +182,12 @@
@Test
fun testFaceTriggerBypassEnabled_Ripple() {
- // GIVEN face auth sensor exists, keyguard is visible & strong auth isn't required
+ // GIVEN face auth sensor exists, keyguard is showing & strong auth isn't required
val faceLocation = Point(5, 5)
`when`(authController.faceSensorLocation).thenReturn(faceLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
// WHEN bypass is enabled & face authenticated
@@ -299,7 +274,7 @@
val fpsLocation = Point(5, 5)
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
@@ -317,7 +292,7 @@
val faceLocation = Point(5, 5)
`when`(authController.faceSensorLocation).thenReturn(faceLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
`when`(authController.isUdfpsFingerDown).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 5c564e6..baeabc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -17,13 +17,23 @@
package com.android.systemui.biometrics
import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants.*
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
-import android.view.*
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.Surface
import android.view.Surface.Rotation
+import android.view.View
+import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
@@ -32,11 +42,11 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
@@ -52,8 +62,8 @@
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
private const val REQUEST_ID = 2L
@@ -75,7 +85,7 @@
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var statusBarStateController: StatusBarStateController
- @Mock private lateinit var panelExpansionStateManager: PanelExpansionStateManager
+ @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dialogManager: SystemUIDialogManager
@@ -117,7 +127,7 @@
private fun withReason(@ShowReason reason: Int, block: () -> Unit) {
controllerOverlay = UdfpsControllerOverlay(
context, fingerprintManager, inflater, windowManager, accessibilityManager,
- statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager,
+ statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 53e8c6e..8923ba8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.when;
import android.graphics.Rect;
+import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
@@ -71,12 +72,12 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.Execution;
@@ -245,7 +246,7 @@
mWindowManager,
mStatusBarStateController,
mFgExecutor,
- new PanelExpansionStateManager(),
+ new ShadeExpansionStateManager(),
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
@@ -682,7 +683,7 @@
}
@Test
- public void aodInterruptCancelTimeoutActionWhenFingerUp() throws RemoteException {
+ public void aodInterruptCancelTimeoutActionOnFingerUp() throws RemoteException {
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
@@ -734,6 +735,56 @@
}
@Test
+ public void aodInterruptCancelTimeoutActionOnAcquired() throws RemoteException {
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+
+ // GIVEN AOD interrupt
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mScreenObserver.onScreenTurnedOn();
+ mFgExecutor.runAllReady();
+ mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
+ mFgExecutor.runAllReady();
+
+ // Configure UdfpsView to accept the acquired event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+ // WHEN acquired is received
+ mOverlayController.onAcquired(TEST_UDFPS_SENSOR_ID,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD);
+
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+
+ // WHEN ACTION_DOWN is received
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricsExecutor.runAllReady();
+ downEvent.recycle();
+
+ // WHEN ACTION_MOVE is received
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+ mBiometricsExecutor.runAllReady();
+ moveEvent.recycle();
+ mFgExecutor.runAllReady();
+
+ // Configure UdfpsView to accept the finger up event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+ // WHEN it times out
+ mFgExecutor.advanceClockToNext();
+ mFgExecutor.runAllReady();
+
+ // THEN the display should be unconfigured once. If the timeout action is not
+ // cancelled, the display would be unconfigured twice which would cause two
+ // FP attempts.
+ verify(mUdfpsView, times(1)).unconfigureDisplay();
+ }
+
+ @Test
public void aodInterruptScreenOff() throws RemoteException {
// GIVEN screen off
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index b61bda8..c0f9c82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -41,14 +41,14 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -76,7 +76,7 @@
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
- private PanelExpansionStateManager mPanelExpansionStateManager;
+ private ShadeExpansionStateManager mShadeExpansionStateManager;
@Mock
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock
@@ -109,8 +109,8 @@
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
private StatusBarStateController.StateListener mStatusBarStateListener;
- @Captor private ArgumentCaptor<PanelExpansionListener> mExpansionListenerCaptor;
- private List<PanelExpansionListener> mExpansionListeners;
+ @Captor private ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
+ private List<ShadeExpansionListener> mExpansionListeners;
@Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
mAltAuthInterceptorCaptor;
@@ -130,7 +130,7 @@
mController = new UdfpsKeyguardViewController(
mView,
mStatusBarStateController,
- mPanelExpansionStateManager,
+ mShadeExpansionStateManager,
mStatusBarKeyguardViewManager,
mKeyguardUpdateMonitor,
mDumpManager,
@@ -182,8 +182,8 @@
mController.onViewDetached();
verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
- for (PanelExpansionListener listener : mExpansionListeners) {
- verify(mPanelExpansionStateManager).removeExpansionListener(listener);
+ for (ShadeExpansionListener listener : mExpansionListeners) {
+ verify(mShadeExpansionStateManager).removeExpansionListener(listener);
}
verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback);
}
@@ -513,7 +513,7 @@
}
private void captureStatusBarExpansionListeners() {
- verify(mPanelExpansionStateManager, times(2))
+ verify(mShadeExpansionStateManager, times(2))
.addExpansionListener(mExpansionListenerCaptor.capture());
// first (index=0) is from super class, UdfpsAnimationViewController.
// second (index=1) is from UdfpsKeyguardViewController
@@ -521,10 +521,10 @@
}
private void updateStatusBarExpansion(float fraction, boolean expanded) {
- PanelExpansionChangeEvent event =
- new PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent event =
+ new ShadeExpansionChangeEvent(
fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
- for (PanelExpansionListener listener : mExpansionListeners) {
+ for (ShadeExpansionListener listener : mExpansionListeners) {
listener.onPanelExpansionChanged(event);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 434cb48..25bc91f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -96,7 +96,7 @@
@Mock
private lateinit var removalPendingStore: PendingRemovalStore
- private lateinit var executor: Executor
+ private lateinit var mainExecutor: Executor
@Captor
private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
@@ -108,11 +108,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
- executor = FakeExecutor(FakeSystemClock())
- `when`(mockContext.mainExecutor).thenReturn(executor)
+ mainExecutor = FakeExecutor(FakeSystemClock())
+ `when`(mockContext.mainExecutor).thenReturn(mainExecutor)
broadcastDispatcher = TestBroadcastDispatcher(
mockContext,
+ mainExecutor,
testableLooper.looper,
mock(Executor::class.java),
mock(DumpManager::class.java),
@@ -148,9 +149,9 @@
@Test
fun testAddingReceiverToCorrectUBR_executor() {
- broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0)
+ broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mainExecutor, user0)
broadcastDispatcher.registerReceiver(
- broadcastReceiverOther, intentFilterOther, executor, user1)
+ broadcastReceiverOther, intentFilterOther, mainExecutor, user1)
testableLooper.processAllMessages()
@@ -427,8 +428,9 @@
private class TestBroadcastDispatcher(
context: Context,
- bgLooper: Looper,
- executor: Executor,
+ mainExecutor: Executor,
+ backgroundRunningLooper: Looper,
+ backgroundRunningExecutor: Executor,
dumpManager: DumpManager,
logger: BroadcastDispatcherLogger,
userTracker: UserTracker,
@@ -436,8 +438,9 @@
var mockUBRMap: Map<Int, UserBroadcastDispatcher>
) : BroadcastDispatcher(
context,
- bgLooper,
- executor,
+ mainExecutor,
+ backgroundRunningLooper,
+ backgroundRunningExecutor,
dumpManager,
logger,
userTracker,
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 b2a9e82..6bc7308 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 @@
}
@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/classifier/TypeClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
index d70d6fc..588edb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
@@ -19,6 +19,7 @@
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
import static com.android.systemui.classifier.Classifier.PULSE_EXPAND;
@@ -406,4 +407,46 @@
when(mDataProvider.isRight()).thenReturn(true);
assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isTrue();
}
+
+ @Test
+ public void testPass_MediaSeekbar() {
+ when(mDataProvider.isVertical()).thenReturn(false);
+
+ when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect.
+ when(mDataProvider.isRight()).thenReturn(false);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isFalse();
+
+ when(mDataProvider.isUp()).thenReturn(true);
+ when(mDataProvider.isRight()).thenReturn(false);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isFalse();
+
+ when(mDataProvider.isUp()).thenReturn(false);
+ when(mDataProvider.isRight()).thenReturn(true);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isFalse();
+
+ when(mDataProvider.isUp()).thenReturn(true);
+ when(mDataProvider.isRight()).thenReturn(true);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isFalse();
+ }
+
+ @Test
+ public void testFalse_MediaSeekbar() {
+ when(mDataProvider.isVertical()).thenReturn(true);
+
+ when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect.
+ when(mDataProvider.isRight()).thenReturn(false);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isTrue();
+
+ when(mDataProvider.isUp()).thenReturn(true);
+ when(mDataProvider.isRight()).thenReturn(false);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isTrue();
+
+ when(mDataProvider.isUp()).thenReturn(false);
+ when(mDataProvider.isRight()).thenReturn(true);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isTrue();
+
+ when(mDataProvider.isUp()).thenReturn(true);
+ when(mDataProvider.isRight()).thenReturn(true);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index eec33ca..f370be1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -27,10 +28,10 @@
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
-import android.service.dreams.DreamService;
import android.service.dreams.IDreamOverlay;
import android.service.dreams.IDreamOverlayCallback;
import android.testing.AndroidTestingRunner;
+import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -53,6 +54,8 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -61,6 +64,7 @@
public class DreamOverlayServiceTest extends SysuiTestCase {
private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package",
"lowlight");
+ private static final String DREAM_COMPONENT = "package/dream";
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -108,12 +112,14 @@
@Mock
UiEventLogger mUiEventLogger;
+ @Captor
+ ArgumentCaptor<View> mViewCaptor;
+
DreamOverlayService mService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mContext.addMockSystemService(WindowManager.class, mWindowManager);
when(mDreamOverlayComponent.getDreamOverlayContainerViewController())
.thenReturn(mDreamOverlayContainerViewController);
@@ -129,7 +135,7 @@
when(mDreamOverlayContainerViewController.getContainerView())
.thenReturn(mDreamOverlayContainerView);
- mService = new DreamOverlayService(mContext, mMainExecutor,
+ mService = new DreamOverlayService(mContext, mMainExecutor, mWindowManager,
mDreamOverlayComponentFactory,
mStateController,
mKeyguardUpdateMonitor,
@@ -143,7 +149,8 @@
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
@@ -157,7 +164,8 @@
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
verify(mWindowManager).addView(any(), any());
@@ -169,7 +177,8 @@
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewController).init();
@@ -186,49 +195,76 @@
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView);
}
@Test
- public void testShouldShowComplicationsFalseByDefault() {
- mService.onBind(new Intent());
+ public void testShouldShowComplicationsSetByStartDream() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
- assertThat(mService.shouldShowComplications()).isFalse();
- }
-
- @Test
- public void testShouldShowComplicationsSetByIntentExtra() {
- final Intent intent = new Intent();
- intent.putExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, true);
- mService.onBind(intent);
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ true /*shouldShowComplication*/);
assertThat(mService.shouldShowComplications()).isTrue();
}
@Test
- public void testLowLightSetByIntentExtra() throws RemoteException {
- final Intent intent = new Intent();
- intent.putExtra(DreamService.EXTRA_DREAM_COMPONENT, LOW_LIGHT_COMPONENT);
-
- final IBinder proxy = mService.onBind(intent);
+ public void testLowLightSetByStartDream() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
- assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback,
+ LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
+ assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT);
verify(mStateController).setLowLightActive(true);
}
@Test
- public void testDestroy() {
+ public void testDestroy() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback,
+ LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ // Verify view added.
+ verify(mWindowManager).addView(mViewCaptor.capture(), any());
+
+ // Service destroyed.
mService.onDestroy();
mMainExecutor.runAllReady();
+ // Verify view removed.
+ verify(mWindowManager).removeView(mViewCaptor.getValue());
+
+ // Verify state correctly set.
+ verify(mKeyguardUpdateMonitor).removeCallback(any());
+ verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
+ verify(mStateController).setOverlayActive(false);
+ verify(mStateController).setLowLightActive(false);
+ }
+
+ @Test
+ public void testDoNotRemoveViewOnDestroyIfOverlayNotStarted() {
+ // Service destroyed without ever starting dream.
+ mService.onDestroy();
+ mMainExecutor.runAllReady();
+
+ // Verify no view is removed.
+ verify(mWindowManager, never()).removeView(any());
+
+ // Verify state still correctly set.
verify(mKeyguardUpdateMonitor).removeCallback(any());
verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
verify(mStateController).setOverlayActive(false);
@@ -245,7 +281,8 @@
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
// Destroy the service.
mService.onDestroy();
@@ -255,4 +292,44 @@
verify(mWindowManager, never()).addView(any(), any());
}
+
+ @Test
+ public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting. Do not show dream complications.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ // Verify that a new window is added.
+ verify(mWindowManager).addView(mViewCaptor.capture(), any());
+ final View windowDecorView = mViewCaptor.getValue();
+
+ // Assert that the overlay is not showing complications.
+ assertThat(mService.shouldShowComplications()).isFalse();
+
+ clearInvocations(mDreamOverlayComponent);
+ clearInvocations(mWindowManager);
+
+ // New dream starting with dream complications showing. Note that when a new dream is
+ // binding to the dream overlay service, it receives the same instance of IBinder as the
+ // first one.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ true /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ // Assert that the overlay is showing complications.
+ assertThat(mService.shouldShowComplications()).isTrue();
+
+ // Verify that the old overlay window has been removed, and a new one created.
+ verify(mWindowManager).removeView(windowDecorView);
+ verify(mWindowManager).addView(any(), any());
+
+ // Verify that new instances of overlay container view controller and overlay touch monitor
+ // are created.
+ verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
+ verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index 2448f1a..849ac5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -297,10 +297,10 @@
}
/**
- * Ensures margin is applied
+ * Ensures default margin is applied
*/
@Test
- public void testMargin() {
+ public void testDefaultMargin() {
final int margin = 5;
final ComplicationLayoutEngine engine =
new ComplicationLayoutEngine(mLayout, margin, mTouchSession, 0, 0);
@@ -373,6 +373,74 @@
}
/**
+ * Ensures complication margin is applied
+ */
+ @Test
+ public void testComplicationMargin() {
+ final int defaultMargin = 5;
+ final int complicationMargin = 10;
+ final ComplicationLayoutEngine engine =
+ new ComplicationLayoutEngine(mLayout, defaultMargin, mTouchSession, 0, 0);
+
+ final ViewInfo firstViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0,
+ complicationMargin),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ addComplication(engine, firstViewInfo);
+
+ final ViewInfo secondViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 0),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, secondViewInfo);
+
+ firstViewInfo.clearInvocations();
+ secondViewInfo.clearInvocations();
+
+ final ViewInfo thirdViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 1),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, thirdViewInfo);
+
+ // The first added view should now be underneath the second view.
+ verifyChange(firstViewInfo, false, lp -> {
+ assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.topMargin).isEqualTo(complicationMargin);
+ });
+
+ // The second view should be in underneath the third view.
+ verifyChange(secondViewInfo, false, lp -> {
+ assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
+ assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.getMarginEnd()).isEqualTo(defaultMargin);
+ });
+ }
+
+ /**
* Ensures layout in a particular position updates.
*/
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index 967b30d..cb7e47b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -97,6 +97,35 @@
}
/**
+ * Ensures unspecified margin uses default.
+ */
+ @Test
+ public void testUnspecifiedMarginUsesDefault() {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 3);
+ assertThat(params.getMargin(10) == 10).isTrue();
+ }
+
+ /**
+ * Ensures specified margin is used instead of default.
+ */
+ @Test
+ public void testSpecifiedMargin() {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 3,
+ 10);
+ assertThat(params.getMargin(5) == 10).isTrue();
+ }
+
+ /**
* Ensures ComplicationLayoutParams is properly duplicated on copy construction.
*/
@Test
@@ -106,12 +135,36 @@
100,
ComplicationLayoutParams.POSITION_TOP,
ComplicationLayoutParams.DIRECTION_DOWN,
+ 3,
+ 10);
+ final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
+
+ assertThat(copy.getDirection() == params.getDirection()).isTrue();
+ assertThat(copy.getPosition() == params.getPosition()).isTrue();
+ assertThat(copy.getWeight() == params.getWeight()).isTrue();
+ assertThat(copy.getMargin(0) == params.getMargin(1)).isTrue();
+ assertThat(copy.height == params.height).isTrue();
+ assertThat(copy.width == params.width).isTrue();
+ }
+
+ /**
+ * Ensures ComplicationLayoutParams is properly duplicated on copy construction with unspecified
+ * margin.
+ */
+ @Test
+ public void testCopyConstructionWithUnspecifiedMargin() {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
3);
final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
assertThat(copy.getDirection() == params.getDirection()).isTrue();
assertThat(copy.getPosition() == params.getPosition()).isTrue();
assertThat(copy.getWeight() == params.getWeight()).isTrue();
+ assertThat(copy.getMargin(1) == params.getMargin(1)).isTrue();
assertThat(copy.height == params.height).isTrue();
assertThat(copy.width == params.width).isTrue();
}
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 571dd3d..9f4a7c8 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 @@
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 314a30b..ec448f9 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 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 db6082d..aa8c93e 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 @@
@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 @@
@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 @@
@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 @@
@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 fa8f88a..c8b2b25 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.when;
import android.app.smartspace.SmartspaceTarget;
-import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.view.View;
@@ -48,8 +47,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class SmartSpaceComplicationTest extends SysuiTestCase {
- @Mock
- private Context mContext;
@Mock
private DreamSmartspaceController mSmartspaceController;
@@ -80,7 +77,6 @@
private SmartSpaceComplication.Registrant getRegistrant() {
return new SmartSpaceComplication.Registrant(
- mContext,
mDreamOverlayStateController,
mComplication,
mSmartspaceController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index c3fca29..4bd53c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -41,12 +41,12 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
import com.android.wm.shell.animation.FlingAnimationUtils;
import org.junit.Before;
@@ -285,8 +285,8 @@
final float dragDownAmount = event2.getY() - event1.getY();
// Ensure correct expansion passed in.
- PanelExpansionChangeEvent event =
- new PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent event =
+ new ShadeExpansionChangeEvent(
expansion, /* expanded= */ false, /* tracking= */ true, dragDownAmount);
verify(mStatusBarKeyguardViewManager).onPanelExpansionChanged(event);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 4511193..20a82c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -21,15 +21,10 @@
import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
import android.test.suitebuilder.annotation.SmallTest
-import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
@@ -46,18 +41,16 @@
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyString
-import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
/**
- * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
- * overriding, and should never return any value other than the one provided as the default.
+ * NOTE: This test is for the version of FeatureFlagManager in src-debug, which allows overriding
+ * the default.
*/
@SmallTest
class FeatureFlagsDebugTest : SysuiTestCase() {
@@ -68,10 +61,8 @@
@Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var systemProperties: SystemPropertiesHelper
@Mock private lateinit var resources: Resources
- @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var commandRegistry: CommandRegistry
- @Mock private lateinit var barService: IStatusBarService
- @Mock private lateinit var pw: PrintWriter
+ @Mock private lateinit var restarter: Restarter
private val flagMap = mutableMapOf<Int, Flag<*>>()
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<Int>
@@ -92,12 +83,10 @@
secureSettings,
systemProperties,
resources,
- dumpManager,
deviceConfig,
serverFlagReader,
flagMap,
- commandRegistry,
- barService
+ restarter
)
verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor {
@@ -366,53 +355,6 @@
}
@Test
- fun statusBarCommand_IsRegistered() {
- verify(commandRegistry).registerCommand(anyString(), any())
- }
-
- @Test
- fun noOpCommand() {
- val cmd = captureCommand()
-
- cmd.execute(pw, ArrayList())
- verify(pw, atLeastOnce()).println()
- verify(flagManager).readFlagValue<Boolean>(eq(1), any())
- verifyZeroInteractions(secureSettings)
- }
-
- @Test
- fun readFlagCommand() {
- addFlag(UnreleasedFlag(1))
- val cmd = captureCommand()
- cmd.execute(pw, listOf("1"))
- verify(flagManager).readFlagValue<Boolean>(eq(1), any())
- }
-
- @Test
- fun setFlagCommand() {
- addFlag(UnreleasedFlag(1))
- val cmd = captureCommand()
- cmd.execute(pw, listOf("1", "on"))
- verifyPutData(1, "{\"type\":\"boolean\",\"value\":true}")
- }
-
- @Test
- fun toggleFlagCommand() {
- addFlag(ReleasedFlag(1))
- val cmd = captureCommand()
- cmd.execute(pw, listOf("1", "toggle"))
- verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}", 2)
- }
-
- @Test
- fun eraseFlagCommand() {
- addFlag(ReleasedFlag(1))
- val cmd = captureCommand()
- cmd.execute(pw, listOf("1", "erase"))
- verify(secureSettings).putStringForUser(eq("key-1"), eq(""), anyInt())
- }
-
- @Test
fun dumpFormat() {
val flag1 = ReleasedFlag(1)
val flag2 = ResourceBooleanFlag(2, 1002)
@@ -471,13 +413,6 @@
return flag
}
- private fun captureCommand(): Command {
- val captor = argumentCaptor<Function0<Command>>()
- verify(commandRegistry).registerCommand(anyString(), capture(captor))
-
- return captor.value.invoke()
- }
-
private fun dumpToString(): String {
val sw = StringWriter()
val pw = PrintWriter(sw)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index e94b520..575c142 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -19,17 +19,12 @@
import android.content.res.Resources
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
import com.android.systemui.util.DeviceConfigProxyFake
-import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
-import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
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
@@ -43,7 +38,6 @@
@Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
- @Mock private lateinit var mDumpManager: DumpManager
private val serverFlagReader = ServerFlagReaderFake()
private val deviceConfig = DeviceConfigProxyFake()
@@ -55,15 +49,7 @@
mResources,
mSystemProperties,
deviceConfig,
- serverFlagReader,
- mDumpManager)
- }
-
- @After
- fun onFinished() {
- // The dump manager should be registered with even for the release version, but that's it.
- verify(mDumpManager).registerDumpable(any(), any())
- verifyNoMoreInteractions(mDumpManager)
+ serverFlagReader)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
new file mode 100644
index 0000000..4c61138
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import java.io.PrintWriter
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class FlagCommandTest : SysuiTestCase() {
+
+ @Mock private lateinit var featureFlags: FeatureFlagsDebug
+ @Mock private lateinit var pw: PrintWriter
+ private val flagMap = mutableMapOf<Int, Flag<*>>()
+ private val flagA = UnreleasedFlag(500)
+ private val flagB = ReleasedFlag(501)
+
+ private lateinit var cmd: FlagCommand
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(featureFlags.isEnabled(any(UnreleasedFlag::class.java))).thenReturn(false)
+ whenever(featureFlags.isEnabled(any(ReleasedFlag::class.java))).thenReturn(true)
+ flagMap.put(flagA.id, flagA)
+ flagMap.put(flagB.id, flagB)
+
+ cmd = FlagCommand(featureFlags, flagMap)
+ }
+
+ @Test
+ fun noOpCommand() {
+ cmd.execute(pw, ArrayList())
+ Mockito.verify(pw, Mockito.atLeastOnce()).println()
+ Mockito.verify(featureFlags).isEnabled(flagA)
+ Mockito.verify(featureFlags).isEnabled(flagB)
+ }
+
+ @Test
+ fun readFlagCommand() {
+ cmd.execute(pw, listOf(flagA.id.toString()))
+ Mockito.verify(featureFlags).isEnabled(flagA)
+ }
+
+ @Test
+ fun setFlagCommand() {
+ cmd.execute(pw, listOf(flagB.id.toString(), "on"))
+ Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true)
+ }
+
+ @Test
+ fun toggleFlagCommand() {
+ cmd.execute(pw, listOf(flagB.id.toString(), "toggle"))
+ Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false)
+ }
+
+ @Test
+ fun eraseFlagCommand() {
+ cmd.execute(pw, listOf(flagA.id.toString(), "erase"))
+ Mockito.verify(featureFlags).eraseFlag(flagA)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
index d418836..7f55d38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
@@ -49,6 +49,7 @@
import org.junit.Rule;
import org.junit.Test;
+import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
@@ -148,8 +149,12 @@
return false;
}
- private static void executeShellCommand(String cmd) {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(cmd);
+ private void executeShellCommand(String cmd) {
+ try {
+ runShellCommand(cmd);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
deleted file mode 100644
index b5e9e8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.AnimatableClockController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.Utils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.statusbar.policy.BatteryController;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AnimatableClockControllerTest extends SysuiTestCase {
- @Mock
- private AnimatableClockView mClockView;
- @Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- private BatteryController mBatteryController;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private Resources mResources;
- @Mock
- private Executor mMainExecutor;
- @Mock
- private Executor mBgExecutor;
- @Mock
- private FeatureFlags mFeatureFlags;
-
- private MockitoSession mStaticMockSession;
- private AnimatableClockController mAnimatableClockController;
-
- // Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- private View.OnAttachStateChangeListener mAttachListener;
-
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor;
- private StatusBarStateController.StateListener mStatusBarStateCallback;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mStaticMockSession = mockitoSession()
- .mockStatic(Utils.class)
- .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used
- .startMocking();
- when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0);
-
- mAnimatableClockController = new AnimatableClockController(
- mClockView,
- mStatusBarStateController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
- mResources,
- mMainExecutor,
- mBgExecutor,
- mFeatureFlags
- );
- mAnimatableClockController.init();
- captureAttachListener();
- }
-
- @After
- public void tearDown() {
- mStaticMockSession.finishMocking();
- }
-
- @Test
- public void testOnAttachedUpdatesDozeStateToTrue() {
- // GIVEN dozing
- when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
-
- // WHEN the clock view gets attached
- mAttachListener.onViewAttachedToWindow(mClockView);
-
- // THEN the clock controller updated its dozing state to true
- assertTrue(mAnimatableClockController.isDozing());
- }
-
- @Test
- public void testOnAttachedUpdatesDozeStateToFalse() {
- // GIVEN not dozing
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
-
- // WHEN the clock view gets attached
- mAttachListener.onViewAttachedToWindow(mClockView);
-
- // THEN the clock controller updated its dozing state to false
- assertFalse(mAnimatableClockController.isDozing());
- }
-
- private void captureAttachListener() {
- verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture());
- mAttachListener = mAttachCaptor.getValue();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 21c018a..4c986bf 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 @@
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;
@@ -176,7 +177,7 @@
// and the keyguard goes away
mViewMediator.setShowingLocked(false);
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
TestableLooper.get(this).processAllMessages();
@@ -201,7 +202,7 @@
// and the keyguard goes away
mViewMediator.setShowingLocked(false);
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
TestableLooper.get(this).processAllMessages();
@@ -229,6 +230,28 @@
}
@Test
+ public void testBouncerPrompt_nonStrongIdleTimeout() {
+ // GIVEN trust agents enabled and biometrics are enrolled
+ when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(true);
+
+ // WHEN the strong auth reason is STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+ KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker =
+ mock(KeyguardUpdateMonitor.StrongAuthTracker.class);
+ when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(strongAuthTracker);
+ when(strongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
+ when(strongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(
+ anyInt())).thenReturn(false);
+ when(strongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT);
+
+ // THEN the bouncer prompt reason should return
+ // STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+ assertEquals(KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT,
+ mViewMediator.mViewMediatorCallback.getBouncerPromptReason());
+ }
+
+ @Test
public void testHideSurfaceBehindKeyguardMarksKeyguardNotGoingAway() {
mViewMediator.hideSurfaceBehindKeyguard();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index eea2e95..7a15680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -33,7 +34,6 @@
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
@@ -153,6 +153,21 @@
}
@Test
+ fun `isDozing - starts with correct initial value for isDozing`() = runBlockingTest {
+ var latest: Boolean? = null
+
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+ var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isTrue()
+ job.cancel()
+
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isFalse()
+ job.cancel()
+ }
+
+ @Test
fun dozeAmount() = runBlockingTest {
val values = mutableListOf<Float>()
val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
new file mode 100644
index 0000000..1b34100
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.animation.AnimationHandler.AnimationFrameCallbackProvider
+import android.animation.ValueAnimator
+import android.util.Log
+import android.util.Log.TerribleFailure
+import android.util.Log.TerribleFailureHandler
+import android.view.Choreographer.FrameCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import java.math.BigDecimal
+import java.math.RoundingMode
+import java.util.UUID
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardTransitionRepository
+ private lateinit var oldWtfHandler: TerribleFailureHandler
+ private lateinit var wtfHandler: WtfHandler
+
+ @Before
+ fun setUp() {
+ underTest = KeyguardTransitionRepository()
+ wtfHandler = WtfHandler()
+ oldWtfHandler = Log.setWtfHandler(wtfHandler)
+ }
+
+ @After
+ fun tearDown() {
+ oldWtfHandler?.let { Log.setWtfHandler(it) }
+ }
+
+ @Test
+ fun `startTransition runs animator to completion`() =
+ runBlocking(IMMEDIATE) {
+ val (animator, provider) = setupAnimator(this)
+
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+ underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
+
+ val startTime = System.currentTimeMillis()
+ while (animator.isRunning()) {
+ yield()
+ if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
+ fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
+ }
+ }
+
+ assertSteps(steps, listWithStep(BigDecimal(.1)))
+
+ job.cancel()
+ provider.stop()
+ }
+
+ @Test
+ fun `startTransition called during another transition fails`() {
+ underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null))
+ underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null))
+
+ assertThat(wtfHandler.failed).isTrue()
+ }
+
+ @Test
+ fun `Null animator enables manual control with updateTransition`() =
+ runBlocking(IMMEDIATE) {
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+ val uuid =
+ underTest.startTransition(
+ TransitionInfo(
+ ownerName = OWNER_NAME,
+ from = AOD,
+ to = LOCKSCREEN,
+ animator = null,
+ )
+ )
+
+ checkNotNull(uuid).let {
+ underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
+ underTest.updateTransition(it, 1f, TransitionState.FINISHED)
+ }
+
+ assertThat(steps.size).isEqualTo(3)
+ assertThat(steps[0])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ assertThat(steps[1])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+ assertThat(steps[2])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ job.cancel()
+ }
+
+ @Test
+ fun `Attempt to manually update transition with invalid UUID throws exception`() {
+ underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
+ assertThat(wtfHandler.failed).isTrue()
+ }
+
+ @Test
+ fun `Attempt to manually update transition after FINISHED state throws exception`() {
+ val uuid =
+ underTest.startTransition(
+ TransitionInfo(
+ ownerName = OWNER_NAME,
+ from = AOD,
+ to = LOCKSCREEN,
+ animator = null,
+ )
+ )
+
+ checkNotNull(uuid).let {
+ underTest.updateTransition(it, 1f, TransitionState.FINISHED)
+ underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
+ }
+ assertThat(wtfHandler.failed).isTrue()
+ }
+
+ private fun listWithStep(step: BigDecimal): List<BigDecimal> {
+ val steps = mutableListOf<BigDecimal>()
+
+ var i = BigDecimal.ZERO
+ while (i.compareTo(BigDecimal.ONE) <= 0) {
+ steps.add(i)
+ i = (i + step).setScale(2, RoundingMode.HALF_UP)
+ }
+
+ return steps
+ }
+
+ private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
+ // + 2 accounts for start and finish of automated transition
+ assertThat(steps.size).isEqualTo(fractions.size + 2)
+
+ assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ fractions.forEachIndexed { index, fraction ->
+ assertThat(steps[index + 1])
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+ )
+ }
+ assertThat(steps[steps.size - 1])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+
+ assertThat(wtfHandler.failed).isFalse()
+ }
+
+ private fun setupAnimator(
+ scope: CoroutineScope
+ ): Pair<ValueAnimator, TestFrameCallbackProvider> {
+ val animator =
+ ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(ANIMATION_DURATION)
+ }
+
+ val provider = TestFrameCallbackProvider(animator, scope)
+ provider.start()
+
+ return Pair(animator, provider)
+ }
+
+ /** Gives direct control over ValueAnimator. See [AnimationHandler] */
+ private class TestFrameCallbackProvider(
+ private val animator: ValueAnimator,
+ private val scope: CoroutineScope,
+ ) : AnimationFrameCallbackProvider {
+
+ private var frameCount = 1L
+ private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
+ private var job: Job? = null
+
+ fun start() {
+ animator.getAnimationHandler().setProvider(this)
+
+ job =
+ scope.launch {
+ frames.collect {
+ // Delay is required for AnimationHandler to properly register a callback
+ delay(1)
+ val (frameNumber, callback) = it
+ callback?.doFrame(frameNumber)
+ }
+ }
+ }
+
+ fun stop() {
+ job?.cancel()
+ animator.getAnimationHandler().setProvider(null)
+ }
+
+ override fun postFrameCallback(cb: FrameCallback) {
+ frames.value = Pair(++frameCount, cb)
+ }
+ override fun postCommitCallback(runnable: Runnable) {}
+ override fun getFrameTime() = frameCount
+ override fun getFrameDelay() = 1L
+ override fun setFrameDelay(delay: Long) {}
+ }
+
+ private class WtfHandler : TerribleFailureHandler {
+ var failed = false
+ override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) {
+ failed = true
+ }
+ }
+
+ companion object {
+ private const val MAX_TEST_DURATION = 100L
+ private const val ANIMATION_DURATION = 10L
+ private const val OWNER_NAME = "Test"
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index b6d7559..b4d5464 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 @@
@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 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(expandable.activityLaunchController()).thenReturn(animationController)
homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
underTest =
@@ -259,7 +261,7 @@
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
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 1dd919a..65fd6e5 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.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 @@
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
+ toggle = KeyguardQuickAffordanceToggleState.On,
)
)
@@ -123,6 +124,7 @@
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 @@
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 6ea1daa..e99c139 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 @@
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 dede4ec..a809f05 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 @@
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 @@
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 @@
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 @@
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 0a4478f..98dc4c4 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 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.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 @@
@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 96544e7..d674c89 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 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.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 @@
@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 @@
TestConfig(
isVisible = true,
isClickable = true,
+ isActivated = true,
icon = mock(),
canShowWhileLocked = false,
intent = Intent("action"),
@@ -505,6 +507,12 @@
}
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 @@
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 @@
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 b8e9cf4..dc5522e 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 @@
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 5ad3542..7e0be6d 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.classifier.FalsingCollector
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
+import com.android.systemui.media.MediaCarouselController.Companion.DURATION
+import com.android.systemui.media.MediaCarouselController.Companion.PAGINATION_DELAY
+import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER
+import com.android.systemui.media.MediaHierarchyManager.Companion.LOCATION_QS
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
@@ -71,7 +76,6 @@
@Mock lateinit var dumpManager: DumpManager
@Mock lateinit var logger: MediaUiEventLogger
@Mock lateinit var debugLogger: MediaCarouselControllerLogger
- @Mock lateinit var mediaPlayer: MediaControlPanel
@Mock lateinit var mediaViewController: MediaViewController
@Mock lateinit var smartspaceMediaData: SmartspaceMediaData
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
@@ -102,8 +106,8 @@
verify(mediaDataManager).addListener(capture(listener))
verify(visualStabilityProvider)
.addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
- whenever(mediaControlPanelFactory.get()).thenReturn(mediaPlayer)
- whenever(mediaPlayer.mediaViewController).thenReturn(mediaViewController)
+ whenever(mediaControlPanelFactory.get()).thenReturn(panel)
+ whenever(panel.mediaViewController).thenReturn(mediaViewController)
whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
MediaPlayerData.clear()
}
@@ -184,6 +188,10 @@
for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
assertEquals(expected.get(index).first, key.data.notificationKey)
}
+
+ for ((index, key) in MediaPlayerData.visiblePlayerKeys().withIndex()) {
+ assertEquals(expected.get(index).first, key.data.notificationKey)
+ }
}
@Test
@@ -199,6 +207,22 @@
}
@Test
+ fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
+ testPlayerOrdering()
+
+ // If smartspace is prioritized
+ listener.value.onSmartspaceMediaDataLoaded(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
+ true
+ )
+
+ // Then it should be shown immediately after any actively playing controls
+ assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
+ assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec)
+ }
+
+ @Test
fun testOrderWithSmartspace_notPrioritized() {
testPlayerOrdering()
@@ -212,6 +236,31 @@
}
@Test
+ fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
+ testPlayerOrdering()
+ // playing paused player
+ listener.value.onMediaDataLoaded("paused local",
+ "paused local",
+ DATA.copy(active = true, isPlaying = true,
+ playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false))
+ listener.value.onMediaDataLoaded("playing local",
+ "playing local",
+ DATA.copy(active = true, isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true)
+ )
+
+ assertEquals(
+ MediaPlayerData.getMediaPlayerIndex("paused local"),
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ )
+ // paused player order should stays the same in visibleMediaPLayer map.
+ // paused player order should be first in mediaPlayer map.
+ assertEquals(
+ MediaPlayerData.visiblePlayerKeys().elementAt(3),
+ MediaPlayerData.playerKeys().elementAt(0)
+ )
+ }
+ @Test
fun testSwipeDismiss_logged() {
mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
@@ -276,6 +325,7 @@
verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
}
+ @Test
fun testMediaLoaded_ScrollToActivePlayer() {
listener.value.onMediaDataLoaded("playing local",
null,
@@ -287,9 +337,9 @@
DATA.copy(active = true, isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false))
// adding a media recommendation card.
- MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
- false, clock)
- mediaCarouselController.shouldScrollToActivePlayer = true
+ listener.value.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA,
+ false)
+ mediaCarouselController.shouldScrollToKey = true
// switching between media players.
listener.value.onMediaDataLoaded("playing local",
"playing local",
@@ -309,8 +359,11 @@
@Test
fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
- MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
- false, clock)
+ listener.value.onSmartspaceMediaDataLoaded(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
+ false
+ )
listener.value.onMediaDataLoaded("playing local",
null,
DATA.copy(active = true, isPlaying = true,
@@ -326,10 +379,12 @@
// Replaying the same media player one more time.
// And check that the card stays in its position.
+ mediaCarouselController.shouldScrollToKey = true
listener.value.onMediaDataLoaded("playing local",
null,
DATA.copy(active = true, isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)
+ playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false,
+ packageName = "PACKAGE_NAME")
)
playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
assertEquals(playerIndex, 0)
@@ -398,4 +453,24 @@
// added to the end because it was active less recently.
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
}
+
+ @Test
+ fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
+ val delta = 0.0001F
+ val paginationSquishMiddle = TRANSFORM_BEZIER.getInterpolation(
+ (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION)
+ val paginationSquishEnd = TRANSFORM_BEZIER.getInterpolation(
+ (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION)
+ whenever(mediaHostStatesManager.mediaHostStates)
+ .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
+ whenever(mediaHostState.visible).thenReturn(true)
+ mediaCarouselController.currentEndLocation = LOCATION_QS
+ whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle)
+ mediaCarouselController.updatePageIndicatorAlpha()
+ assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta)
+
+ whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd)
+ mediaCarouselController.updatePageIndicatorAlpha()
+ assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index 3d3ac83..83168cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -305,7 +305,7 @@
// Then we save an update with the current time
verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
- ?.dropLastWhile { it.isEmpty() }.forEach {
+ .dropLastWhile { it.isEmpty() }.forEach {
val result = it.split("/")
assertThat(result.size).isEqualTo(3)
assertThat(result[2].toLong()).isEqualTo(currentTime)
@@ -392,7 +392,7 @@
// Then we store the new lastPlayed time
verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
- ?.dropLastWhile { it.isEmpty() }.forEach {
+ .dropLastWhile { it.isEmpty() }.forEach {
val result = it.split("/")
assertThat(result.size).isEqualTo(3)
assertThat(result[2].toLong()).isEqualTo(currentTime)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
new file mode 100644
index 0000000..622a512
--- /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 2f52950..af53016 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 @@
@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 @@
@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 @@
@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 void testOnMediaDataLoaded_mediaComplicationDisabled_doesNotAddComplication() {
when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(false);
- final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+ final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager,
mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags);
sentinel.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index 2a13053..d828193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -64,6 +64,7 @@
context,
FakeExecutor(FakeSystemClock()),
)
+ mediaTttCommandLineHelper.start()
}
@Test(expected = IllegalStateException::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 37f6434..7c83cb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -19,9 +19,7 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import android.widget.FrameLayout
import androidx.test.filters.SmallTest
-import com.android.internal.widget.CachingIconView
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
@@ -90,48 +88,6 @@
assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName)
assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME)
}
-
- @Test
- fun setIcon_viewHasIconAndContentDescription() {
- val view = CachingIconView(context)
- val icon = context.getDrawable(R.drawable.ic_celebration)!!
- val contentDescription = "Happy birthday!"
-
- MediaTttUtils.setIcon(view, icon, contentDescription)
-
- assertThat(view.drawable).isEqualTo(icon)
- assertThat(view.contentDescription).isEqualTo(contentDescription)
- }
-
- @Test
- fun setIcon_iconSizeNull_viewSizeDoesNotChange() {
- val view = CachingIconView(context)
- val size = 456
- view.layoutParams = FrameLayout.LayoutParams(size, size)
-
- MediaTttUtils.setIcon(view, context.getDrawable(R.drawable.ic_cake)!!, "desc")
-
- assertThat(view.layoutParams.width).isEqualTo(size)
- assertThat(view.layoutParams.height).isEqualTo(size)
- }
-
- @Test
- fun setIcon_iconSizeProvided_viewSizeUpdates() {
- val view = CachingIconView(context)
- val size = 456
- view.layoutParams = FrameLayout.LayoutParams(size, size)
-
- val newSize = 40
- MediaTttUtils.setIcon(
- view,
- context.getDrawable(R.drawable.ic_cake)!!,
- "desc",
- iconSize = newSize
- )
-
- assertThat(view.layoutParams.width).isEqualTo(newSize)
- assertThat(view.layoutParams.height).isEqualTo(newSize)
- }
}
private const val PACKAGE_NAME = "com.android.systemui"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index d41ad48..8c3ae3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -34,6 +34,7 @@
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -41,6 +42,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -48,6 +50,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -69,8 +72,12 @@
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
+ private lateinit var mediaTttFlags: MediaTttFlags
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
+ private lateinit var viewUtil: ViewUtil
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var commandQueue: CommandQueue
@@ -82,6 +89,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
@@ -104,8 +112,11 @@
configurationController,
powerManager,
Handler.getMain(),
- receiverUiEventLogger
+ mediaTttFlags,
+ receiverUiEventLogger,
+ viewUtil,
)
+ controllerReceiver.start()
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue).addCallback(callbackCaptor.capture())
@@ -113,6 +124,30 @@
}
@Test
+ fun commandQueueCallback_flagOff_noCallbackAdded() {
+ reset(commandQueue)
+ whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false)
+
+ controllerReceiver = MediaTttChipControllerReceiver(
+ commandQueue,
+ context,
+ logger,
+ windowManager,
+ FakeExecutor(FakeSystemClock()),
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ Handler.getMain(),
+ mediaTttFlags,
+ receiverUiEventLogger,
+ viewUtil,
+ )
+ controllerReceiver.start()
+
+ verify(commandQueue, never()).addCallback(any())
+ }
+
+ @Test
fun commandQueueCallback_closeToSender_triggersChip() {
val appName = "FakeAppName"
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
@@ -212,35 +247,27 @@
}
@Test
- fun updateView_isAppIcon_usesAppIconSize() {
+ fun updateView_isAppIcon_usesAppIconPadding() {
controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME))
+
val chipView = getChipView()
-
- chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
- )
-
- val expectedSize =
- context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver)
- assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize)
- assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize)
+ assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(0)
+ assertThat(chipView.getAppIconView().paddingRight).isEqualTo(0)
+ assertThat(chipView.getAppIconView().paddingTop).isEqualTo(0)
+ assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(0)
}
@Test
- fun updateView_notAppIcon_usesGenericIconSize() {
+ fun updateView_notAppIcon_usesGenericIconPadding() {
controllerReceiver.displayView(getChipReceiverInfo(packageName = null))
+
val chipView = getChipView()
-
- chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
- )
-
- val expectedSize =
- context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_size_receiver)
- assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize)
- assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize)
+ val expectedPadding =
+ context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding)
+ assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(expectedPadding)
+ assertThat(chipView.getAppIconView().paddingRight).isEqualTo(expectedPadding)
+ assertThat(chipView.getAppIconView().paddingTop).isEqualTo(expectedPadding)
+ assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(expectedPadding)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
deleted file mode 100644
index 098086a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ /dev/null
@@ -1,834 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.taptotransfer.sender
-
-import android.app.StatusBarManager
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
-import android.media.MediaRoute2Info
-import android.os.PowerManager
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowManager
-import android.view.accessibility.AccessibilityManager
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.statusbar.IUndoMediaTransferCallback
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class MediaTttChipControllerSenderTest : SysuiTestCase() {
- private lateinit var controllerSender: MediaTttChipControllerSender
-
- @Mock
- private lateinit var packageManager: PackageManager
- @Mock
- private lateinit var applicationInfo: ApplicationInfo
- @Mock
- private lateinit var logger: MediaTttLogger
- @Mock
- private lateinit var accessibilityManager: AccessibilityManager
- @Mock
- private lateinit var configurationController: ConfigurationController
- @Mock
- private lateinit var powerManager: PowerManager
- @Mock
- private lateinit var windowManager: WindowManager
- @Mock
- private lateinit var commandQueue: CommandQueue
- @Mock
- private lateinit var lazyFalsingManager: Lazy<FalsingManager>
- @Mock
- private lateinit var falsingManager: FalsingManager
- @Mock
- private lateinit var lazyFalsingCollector: Lazy<FalsingCollector>
- @Mock
- private lateinit var falsingCollector: FalsingCollector
- private lateinit var commandQueueCallback: CommandQueue.Callbacks
- private lateinit var fakeAppIconDrawable: Drawable
- private lateinit var fakeClock: FakeSystemClock
- private lateinit var fakeExecutor: FakeExecutor
- private lateinit var uiEventLoggerFake: UiEventLoggerFake
- private lateinit var senderUiEventLogger: MediaTttSenderUiEventLogger
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
- whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
- whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
- whenever(packageManager.getApplicationInfo(
- eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>()
- )).thenReturn(applicationInfo)
- context.setMockPackageManager(packageManager)
-
- fakeClock = FakeSystemClock()
- fakeExecutor = FakeExecutor(fakeClock)
-
- uiEventLoggerFake = UiEventLoggerFake()
- senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
-
- whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
- whenever(lazyFalsingManager.get()).thenReturn(falsingManager)
- whenever(lazyFalsingCollector.get()).thenReturn(falsingCollector)
-
- controllerSender = MediaTttChipControllerSender(
- commandQueue,
- context,
- logger,
- windowManager,
- fakeExecutor,
- accessibilityManager,
- configurationController,
- powerManager,
- senderUiEventLogger,
- lazyFalsingManager,
- lazyFalsingCollector
- )
-
- val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
- verify(commandQueue).addCallback(callbackCaptor.capture())
- commandQueueCallback = callbackCaptor.value!!
- }
-
- @Test
- fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- almostCloseToStartCast().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id
- )
- }
-
- @Test
- fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- almostCloseToEndCast().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToReceiverSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToThisDeviceSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToReceiverFailed().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToThisDeviceFailed().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id
- )
- }
-
- @Test
- fun commandQueueCallback_farFromReceiver_noChipShown() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
-
- verify(windowManager, never()).addView(any(), any())
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER.id
- )
- }
-
- @Test
- fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
- routeInfo,
- null
- )
-
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
-
- val viewCaptor = ArgumentCaptor.forClass(View::class.java)
- verify(windowManager).addView(viewCaptor.capture(), any())
- verify(windowManager).removeView(viewCaptor.value)
- }
-
- @Test
- fun commandQueueCallback_invalidStateParam_noChipShown() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- 100,
- routeInfo,
- null
- )
-
- verify(windowManager, never()).addView(any(), any())
- }
-
- @Test
- fun receivesNewStateFromCommandQueue_isLogged() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
- routeInfo,
- null
- )
-
- verify(logger).logStateChange(any(), any(), any())
- }
-
- @Test
- fun almostCloseToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
- val state = almostCloseToStartCast()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun almostCloseToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
- val state = almostCloseToEndCast()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToReceiverTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
- val state = transferToReceiverTriggered()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToThisDeviceTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
- val state = transferToThisDeviceTriggered()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
- val state = transferToReceiverSucceeded()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() {
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback = null))
-
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
-
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
- }
-
- @Test
- fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
-
- assertThat(undoCallbackCalled).isTrue()
- }
-
- @Test
- fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
- whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(true)
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
-
- assertThat(undoCallbackCalled).isFalse()
- }
-
- @Test
- fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
- whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(false)
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
-
- assertThat(undoCallbackCalled).isTrue()
- }
-
- @Test
- fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
-
- getChipView().getUndoButton().performClick()
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id
- )
- }
-
- @Test
- fun transferToThisDeviceSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
- val state = transferToThisDeviceSucceeded()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() {
- controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback = null))
-
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback))
-
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
- }
-
- @Test
- fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
-
- assertThat(undoCallbackCalled).isTrue()
- }
-
- @Test
- fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback))
-
- getChipView().getUndoButton().performClick()
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
- )
- }
-
- @Test
- fun transferToReceiverFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
- val state = transferToReceiverFailed()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(getChipView().getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun transferToThisDeviceFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
- val state = transferToThisDeviceFailed()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(getChipView().getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun changeFromAlmostCloseToStartToTransferTriggered_loadingIconAppears() {
- controllerSender.displayView(almostCloseToStartCast())
- controllerSender.displayView(transferToReceiverTriggered())
-
- assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() {
- controllerSender.displayView(transferToReceiverTriggered())
- controllerSender.displayView(transferToReceiverSucceeded())
-
- assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
- }
-
- @Test
- fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() {
- controllerSender.displayView(transferToReceiverTriggered())
- controllerSender.displayView(
- transferToReceiverSucceeded(
- object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- )
- )
-
- assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun changeFromTransferSucceededToAlmostCloseToStart_undoButtonDisappears() {
- controllerSender.displayView(transferToReceiverSucceeded())
- controllerSender.displayView(almostCloseToStartCast())
-
- assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun changeFromTransferTriggeredToTransferFailed_failureIconAppears() {
- controllerSender.displayView(transferToReceiverTriggered())
- controllerSender.displayView(transferToReceiverFailed())
-
- assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun transferToReceiverTriggeredThenRemoveView_viewStillDisplayed() {
- controllerSender.displayView(transferToReceiverTriggered())
- fakeClock.advanceTime(1000L)
-
- controllerSender.removeView("fakeRemovalReason")
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayed() {
- controllerSender.displayView(transferToReceiverTriggered())
-
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToReceiverTriggeredThenRemoveView_eventuallyTimesOut() {
- controllerSender.displayView(transferToReceiverTriggered())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeClock.advanceTime(TIMEOUT + 1L)
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun transferToThisDeviceTriggeredThenRemoveView_viewStillDisplayed() {
- controllerSender.displayView(transferToThisDeviceTriggered())
- fakeClock.advanceTime(1000L)
-
- controllerSender.removeView("fakeRemovalReason")
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToThisDeviceTriggeredThenRemoveView_eventuallyTimesOut() {
- controllerSender.displayView(transferToThisDeviceTriggered())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeClock.advanceTime(TIMEOUT + 1L)
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayed() {
- controllerSender.displayView(transferToThisDeviceTriggered())
-
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToReceiverSucceededThenRemoveView_viewStillDisplayed() {
- controllerSender.displayView(transferToReceiverSucceeded())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToReceiverSucceededThenRemoveView_eventuallyTimesOut() {
- controllerSender.displayView(transferToReceiverSucceeded())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeClock.advanceTime(TIMEOUT + 1L)
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayed() {
- controllerSender.displayView(transferToReceiverSucceeded())
-
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToThisDeviceSucceededThenRemoveView_viewStillDisplayed() {
- controllerSender.displayView(transferToThisDeviceSucceeded())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToThisDeviceSucceededThenRemoveView_eventuallyTimesOut() {
- controllerSender.displayView(transferToThisDeviceSucceeded())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeClock.advanceTime(TIMEOUT + 1L)
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayed() {
- controllerSender.displayView(transferToThisDeviceSucceeded())
-
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
-
- private fun ViewGroup.getChipText(): String =
- (this.requireViewById<TextView>(R.id.text)).text as String
-
- private fun ViewGroup.getLoadingIconVisibility(): Int =
- this.requireViewById<View>(R.id.loading).visibility
-
- private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.undo)
-
- private fun ViewGroup.getFailureIcon(): View = this.requireViewById(R.id.failure_icon)
-
- private fun getChipView(): ViewGroup {
- val viewCaptor = ArgumentCaptor.forClass(View::class.java)
- verify(windowManager).addView(viewCaptor.capture(), any())
- return viewCaptor.value as ViewGroup
- }
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun almostCloseToStartCast() =
- ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun almostCloseToEndCast() =
- ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverTriggered() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceTriggered() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, undoCallback)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, undoCallback)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverFailed() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceFailed() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
-}
-
-private const val APP_NAME = "Fake app name"
-private const val OTHER_DEVICE_NAME = "My Tablet"
-private const val PACKAGE_NAME = "com.android.systemui"
-private const val TIMEOUT = 10000
-
-private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
- .addFeature("feature")
- .setClientPackageName(PACKAGE_NAME)
- .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
new file mode 100644
index 0000000..110bbb8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.sender
+
+import android.app.StatusBarManager
+import android.media.MediaRoute2Info
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.temporarydisplay.chipbar.ChipSenderInfo
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaTttSenderCoordinatorTest : SysuiTestCase() {
+ private lateinit var underTest: MediaTttSenderCoordinator
+
+ @Mock private lateinit var accessibilityManager: AccessibilityManager
+ @Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var mediaTttFlags: MediaTttFlags
+ @Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var viewUtil: ViewUtil
+ @Mock private lateinit var windowManager: WindowManager
+ private lateinit var chipbarCoordinator: ChipbarCoordinator
+ private lateinit var commandQueueCallback: CommandQueue.Callbacks
+ private lateinit var fakeClock: FakeSystemClock
+ private lateinit var fakeExecutor: FakeExecutor
+ private lateinit var uiEventLoggerFake: UiEventLoggerFake
+ private lateinit var uiEventLogger: MediaTttSenderUiEventLogger
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
+ whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+
+ fakeClock = FakeSystemClock()
+ fakeExecutor = FakeExecutor(fakeClock)
+
+ uiEventLoggerFake = UiEventLoggerFake()
+ uiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
+
+ chipbarCoordinator =
+ FakeChipbarCoordinator(
+ context,
+ logger,
+ windowManager,
+ fakeExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ uiEventLogger,
+ falsingManager,
+ falsingCollector,
+ viewUtil,
+ )
+ chipbarCoordinator.start()
+
+ underTest =
+ MediaTttSenderCoordinator(
+ chipbarCoordinator,
+ commandQueue,
+ context,
+ logger,
+ mediaTttFlags,
+ uiEventLogger,
+ )
+ underTest.start()
+
+ val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+ verify(commandQueue).addCallback(callbackCaptor.capture())
+ commandQueueCallback = callbackCaptor.value!!
+ }
+
+ @Test
+ fun commandQueueCallback_flagOff_noCallbackAdded() {
+ reset(commandQueue)
+ whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false)
+ underTest =
+ MediaTttSenderCoordinator(
+ chipbarCoordinator,
+ commandQueue,
+ context,
+ logger,
+ mediaTttFlags,
+ uiEventLogger,
+ )
+ underTest.start()
+
+ verify(commandQueue, never()).addCallback(any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(almostCloseToStartCast().state.getChipTextString(context, OTHER_DEVICE_NAME))
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id)
+ }
+
+ @Test
+ fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(almostCloseToEndCast().state.getChipTextString(context, OTHER_DEVICE_NAME))
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id)
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(
+ transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id)
+ }
+
+ @Test
+ fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(
+ transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id)
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(
+ transferToReceiverSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
+ }
+
+ @Test
+ fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(
+ transferToThisDeviceSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(
+ transferToReceiverFailed().state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
+ }
+
+ @Test
+ fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(
+ transferToThisDeviceFailed().state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
+ }
+
+ @Test
+ fun commandQueueCallback_farFromReceiver_noChipShown() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER.id)
+ }
+
+ @Test
+ fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ verify(windowManager).removeView(viewCaptor.value)
+ }
+
+ @Test
+ fun commandQueueCallback_invalidStateParam_noChipShown() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(100, routeInfo, null)
+
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun receivesNewStateFromCommandQueue_isLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logStateChange(any(), any(), any())
+ }
+
+ @Test
+ fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayedButStillTimesOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ verify(windowManager).removeView(any())
+ }
+
+ private fun getChipView(): ViewGroup {
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ return viewCaptor.value as ViewGroup
+ }
+
+ private fun ViewGroup.getChipText(): String =
+ (this.requireViewById<TextView>(R.id.text)).text as String
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun almostCloseToStartCast() =
+ ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST, routeInfo)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun almostCloseToEndCast() =
+ ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST, routeInfo)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToReceiverTriggered() =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToThisDeviceTriggered() =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, undoCallback)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, undoCallback)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToReceiverFailed() =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToThisDeviceFailed() =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
+}
+
+private const val OTHER_DEVICE_NAME = "My Tablet"
+private const val TIMEOUT = 10000
+
+private val routeInfo =
+ MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
+ .addFeature("feature")
+ .setClientPackageName("com.android.systemui")
+ .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 00b1f32..19d2d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -25,6 +25,7 @@
private val controller = MediaProjectionAppSelectorController(
taskListProvider,
+ view,
scope,
appSelectorComponentName
)
@@ -33,7 +34,7 @@
fun initNoRecentTasks_bindsEmptyList() {
taskListProvider.tasks = emptyList()
- controller.init(view)
+ controller.init()
verify(view).bind(emptyList())
}
@@ -44,7 +45,7 @@
createRecentTask(taskId = 1)
)
- controller.init(view)
+ controller.init()
verify(view).bind(
listOf(
@@ -62,7 +63,7 @@
)
taskListProvider.tasks = tasks
- controller.init(view)
+ controller.init()
verify(view).bind(
listOf(
@@ -84,7 +85,7 @@
)
taskListProvider.tasks = tasks
- controller.init(view)
+ controller.init()
verify(view).bind(
listOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
new file mode 100644
index 0000000..464acb6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Rect
+import android.util.DisplayMetrics.DENSITY_DEFAULT
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.min
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class TaskPreviewSizeProviderTest : SysuiTestCase() {
+
+ private val mockContext: Context = mock()
+ private val resources: Resources = mock()
+ private val windowManager: WindowManager = mock()
+ private val sizeUpdates = arrayListOf<Rect>()
+ private val testConfigurationController = FakeConfigurationController()
+
+ @Before
+ fun setup() {
+ whenever(mockContext.getSystemService(eq(WindowManager::class.java)))
+ .thenReturn(windowManager)
+ whenever(mockContext.resources).thenReturn(resources)
+ }
+
+ @Test
+ fun size_phoneDisplay_thumbnailSizeIsSmallerAndProportionalToTheScreenSize() {
+ givenDisplay(width = 400, height = 600, isTablet = false)
+
+ val size = createSizeProvider().size
+
+ assertThat(size).isEqualTo(Rect(0, 0, 100, 150))
+ }
+
+ @Test
+ fun size_tabletDisplay_thumbnailSizeProportionalToTheScreenSizeExcludingTaskbar() {
+ givenDisplay(width = 400, height = 600, isTablet = true)
+ givenTaskbarSize(20)
+
+ val size = createSizeProvider().size
+
+ assertThat(size).isEqualTo(Rect(0, 0, 97, 140))
+ }
+
+ @Test
+ fun size_phoneDisplayAndRotate_emitsSizeUpdate() {
+ givenDisplay(width = 400, height = 600, isTablet = false)
+ createSizeProvider()
+
+ givenDisplay(width = 600, height = 400, isTablet = false)
+ testConfigurationController.onConfigurationChanged(Configuration())
+
+ assertThat(sizeUpdates).containsExactly(Rect(0, 0, 150, 100))
+ }
+
+ @Test
+ fun size_phoneDisplayAndRotateConfigurationChange_returnsUpdatedSize() {
+ givenDisplay(width = 400, height = 600, isTablet = false)
+ val sizeProvider = createSizeProvider()
+
+ givenDisplay(width = 600, height = 400, isTablet = false)
+ testConfigurationController.onConfigurationChanged(Configuration())
+
+ assertThat(sizeProvider.size).isEqualTo(Rect(0, 0, 150, 100))
+ }
+
+ private fun givenTaskbarSize(size: Int) {
+ whenever(resources.getDimensionPixelSize(eq(R.dimen.taskbar_frame_height))).thenReturn(size)
+ }
+
+ private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
+ val bounds = Rect(0, 0, width, height)
+ val windowMetrics = WindowMetrics(bounds, null)
+ whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
+ whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
+
+ val minDimension = min(width, height)
+
+ // Calculate DPI so the smallest width is either considered as tablet or as phone
+ val targetSmallestWidthDpi =
+ if (isTablet) SMALLEST_WIDTH_DPI_TABLET else SMALLEST_WIDTH_DPI_PHONE
+ val densityDpi = minDimension * DENSITY_DEFAULT / targetSmallestWidthDpi
+
+ val configuration = Configuration(context.resources.configuration)
+ configuration.densityDpi = densityDpi
+ whenever(resources.configuration).thenReturn(configuration)
+ }
+
+ private fun createSizeProvider(): TaskPreviewSizeProvider {
+ val listener =
+ object : TaskPreviewSizeListener {
+ override fun onTaskSizeChanged(size: Rect) {
+ sizeUpdates.add(size)
+ }
+ }
+
+ return TaskPreviewSizeProvider(mockContext, windowManager, testConfigurationController)
+ .also { it.addCallback(listener) }
+ }
+
+ private companion object {
+ private const val SMALLEST_WIDTH_DPI_TABLET = 800
+ private const val SMALLEST_WIDTH_DPI_PHONE = 400
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 8073103..6c03730 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -39,7 +39,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.keyguard.KeyguardViewController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -49,6 +48,7 @@
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -113,7 +113,7 @@
mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager,
mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
mSystemActions, mOverviewProxyService, mAssistManagerLazy,
- () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardViewController.class),
+ () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
mNavigationModeController, mUserTracker, mDumpManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index b0cf061..9bf27a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -19,6 +19,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -30,20 +32,25 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -72,11 +79,14 @@
private NavigationBarController mNavigationBarController;
private NavigationBar mDefaultNavBar;
private NavigationBar mSecondaryNavBar;
+ private StaticMockitoSession mMockitoSession;
@Mock
private CommandQueue mCommandQueue;
@Mock
private NavigationBarComponent.Factory mNavigationBarFactory;
+ @Mock
+ TaskbarDelegate mTaskbarDelegate;
@Before
public void setUp() {
@@ -90,7 +100,7 @@
Dependency.get(Dependency.MAIN_HANDLER),
mock(ConfigurationController.class),
mock(NavBarHelper.class),
- mock(TaskbarDelegate.class),
+ mTaskbarDelegate,
mNavigationBarFactory,
mock(StatusBarKeyguardViewManager.class),
mock(DumpManager.class),
@@ -100,6 +110,7 @@
Optional.of(mock(BackAnimation.class)),
mock(FeatureFlags.class)));
initializeNavigationBars();
+ mMockitoSession = mockitoSession().mockStatic(Utilities.class).startMocking();
}
private void initializeNavigationBars() {
@@ -120,6 +131,7 @@
mNavigationBarController = null;
mDefaultNavBar = null;
mSecondaryNavBar = null;
+ mMockitoSession.finishMocking();
}
@Test
@@ -268,4 +280,22 @@
public void test3ButtonTaskbarFlagDisabledNoRegister() {
verify(mCommandQueue, never()).addCallback(any(TaskbarDelegate.class));
}
+
+
+ @Test
+ public void testConfigurationChange_taskbarNotInitialized() {
+ Configuration configuration = mContext.getResources().getConfiguration();
+ when(Utilities.isTablet(any())).thenReturn(true);
+ mNavigationBarController.onConfigChanged(configuration);
+ verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration);
+ }
+
+ @Test
+ public void testConfigurationChange_taskbarInitialized() {
+ Configuration configuration = mContext.getResources().getConfiguration();
+ when(Utilities.isTablet(any())).thenReturn(true);
+ when(mTaskbarDelegate.isInitialized()).thenReturn(true);
+ mNavigationBarController.onConfigChanged(configuration);
+ verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration);
+ }
}
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 51f0953..6adce7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -72,7 +72,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardViewController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
@@ -81,6 +80,7 @@
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;
@@ -194,10 +194,12 @@
@Mock
private CentralSurfaces mCentralSurfaces;
@Mock
- private KeyguardViewController mKeyguardViewController;
+ private KeyguardStateController mKeyguardStateController;
@Mock
private UserContextProvider mUserContextProvider;
@Mock
+ private WakefulnessLifecycle mWakefulnessLifecycle;
+ @Mock
private Resources mResources;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@@ -240,7 +242,7 @@
mock(AccessibilityButtonTargetsObserver.class),
mSystemActions, mOverviewProxyService,
() -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
- mKeyguardViewController, mock(NavigationModeController.class),
+ mKeyguardStateController, mock(NavigationModeController.class),
mock(UserTracker.class), mock(DumpManager.class)));
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
@@ -380,7 +382,7 @@
// Verify navbar didn't alter and showing back icon when the keyguard is showing without
// requesting IME insets visible.
- doReturn(true).when(mKeyguardViewController).isShowing();
+ doReturn(true).when(mKeyguardStateController).isShowing();
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
@@ -475,7 +477,8 @@
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 4e9b232..c377c37 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.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 @@
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 @@
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/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 3cad2a0..b847ad0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -277,7 +277,7 @@
// Then the layout changes
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
- verify(mHorizontalLayoutListener).run(); // not invoked
+ verify(mHorizontalLayoutListener).run();
// When it is rotated back to portrait
mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
@@ -300,4 +300,24 @@
verify(mQSTile).refreshState();
verify(mOtherTile, never()).refreshState();
}
+
+ @Test
+ public void configurationChange_onlySplitShadeConfigChanges_horizontalLayoutStatusUpdated() {
+ // Preconditions for horizontal layout
+ when(mMediaHost.getVisible()).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+ reset(mHorizontalLayoutListener);
+
+ // Only split shade status changes
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Horizontal layout is updated accordingly.
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ verify(mHorizontalLayoutListener).run();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 5eb9a98..e539705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -6,6 +6,7 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.FalsingManager
@@ -52,6 +53,7 @@
@Mock private lateinit var tile: QSTile
@Mock private lateinit var otherTile: QSTile
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock private lateinit var featureFlags: FeatureFlags
private lateinit var controller: QSPanelController
@@ -82,7 +84,8 @@
brightnessControllerFactory,
brightnessSliderFactory,
falsingManager,
- statusBarKeyguardViewManager
+ statusBarKeyguardViewManager,
+ featureFlags
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 2db58be..7c930b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -159,6 +159,32 @@
}
@Test
+ fun testTopPadding_notCombinedHeaders() {
+ qsPanel.setUsingCombinedHeaders(false)
+ val padding = 10
+ val paddingCombined = 100
+ context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+ context.orCreateTestableResources.addOverride(
+ R.dimen.qs_panel_padding_top_combined_headers, paddingCombined)
+
+ qsPanel.updatePadding()
+ assertThat(qsPanel.paddingTop).isEqualTo(padding)
+ }
+
+ @Test
+ fun testTopPadding_combinedHeaders() {
+ qsPanel.setUsingCombinedHeaders(true)
+ val padding = 10
+ val paddingCombined = 100
+ context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+ context.orCreateTestableResources.addOverride(
+ R.dimen.qs_panel_padding_top_combined_headers, paddingCombined)
+
+ qsPanel.updatePadding()
+ assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined)
+ }
+
+ @Test
fun testSetSquishinessFraction_noCrash() {
qsPanel.addView(qsPanel.mTileLayout as View, 0)
qsPanel.addView(FrameLayout(context))
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 2a4996f..760bb9b 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 @@
// 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 0000000..b6a595b
--- /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 0000000..c6ce51a
--- /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 073c23c..46a502a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -28,7 +28,6 @@
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
@@ -100,13 +99,14 @@
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)
}
@@ -140,66 +140,6 @@
}
@Test
- fun testSelectedRegionScreenshot_workProfilePolicyDisabled() = runBlocking {
- flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
-
- val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
-
- val processedRequest = processor.process(request)
-
- // No changes
- assertThat(processedRequest).isEqualTo(request)
- }
-
- @Test
- fun testSelectedRegionScreenshot() = runBlocking {
- flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
-
- val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
-
- policy.setManagedProfile(USER_ID, false)
- policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
-
- val processedRequest = processor.process(request)
-
- // Request has topComponent added, but otherwise unchanged.
- assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
- assertThat(processedRequest.topComponent).isEqualTo(component)
- }
-
- @Test
- fun testSelectedRegionScreenshot_managedProfile() = runBlocking {
- flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
-
- // Provide a fake task bitmap when asked
- val bitmap = makeHardwareBitmap(100, 100)
- imageCapture.image = bitmap
-
- val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
-
- // Indicate that the primary content belongs to a manged profile
- policy.setManagedProfile(USER_ID, true)
- policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
-
- val processedRequest = processor.process(request)
-
- // Expect a task snapshot is taken, overriding the selected region mode
- assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
- assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
- assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
- assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
- assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
- assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
- assertThat(processedRequest.userId).isEqualTo(USER_ID)
- assertThat(processedRequest.topComponent).isEqualTo(component)
- }
-
- @Test
fun testProvidedImageScreenshot_workProfilePolicyDisabled() = runBlocking {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 002ef29..3a4da86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -33,7 +33,6 @@
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.ScreenshotHelper
@@ -175,28 +174,6 @@
}
@Test
- fun takeScreenshotPartial() {
- val request = ScreenshotRequest(
- TAKE_SCREENSHOT_SELECTED_REGION,
- SCREENSHOT_KEY_CHORD,
- /* topComponent = */ null)
-
- service.handleRequest(request, { /* onSaved */ }, callback)
-
- verify(controller, times(1)).takeScreenshotPartial(
- /* topComponent = */ isNull(),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any())
-
- assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
- val logEvent = eventLogger.get(0)
-
- assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
- assertEquals("Expected empty package name in UiEvent", "", eventLogger.get(0).packageName)
- }
-
- @Test
fun takeScreenshotProvidedImage() {
val bounds = Rect(50, 50, 150, 150)
val bitmap = makeHardwareBitmap(100, 100)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index c76d9e7..0151822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -645,6 +645,20 @@
verify(animator).start()
}
+ @Test
+ fun privacyChipParentVisibleFromStart() {
+ verify(privacyIconsController).onParentVisible()
+ }
+
+ @Test
+ fun privacyChipParentVisibleAlways() {
+ controller.largeScreenActive = true
+ controller.largeScreenActive = false
+ controller.largeScreenActive = true
+
+ verify(privacyIconsController, never()).onParentInvisible()
+ }
+
private fun createWindowInsets(
topCutout: Rect? = Rect()
): WindowInsets {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 0c60d3c..c0dae03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -20,12 +20,12 @@
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+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;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
-import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
-import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
-import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
import static com.google.common.truth.Truth.assertThat;
@@ -156,7 +156,6 @@
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.phone.TapAgainViewController;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -297,8 +296,8 @@
private final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
private final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
- private final PanelExpansionStateManager mPanelExpansionStateManager =
- new PanelExpansionStateManager();
+ private final ShadeExpansionStateManager mShadeExpansionStateManager =
+ new ShadeExpansionStateManager();
private FragmentHostManager.FragmentListener mFragmentListener;
@Before
@@ -475,7 +474,7 @@
mLargeScreenShadeHeaderController,
mScreenOffAnimationController,
mLockscreenGestureLogger,
- mPanelExpansionStateManager,
+ mShadeExpansionStateManager,
mNotificationRemoteInputManager,
mSysUIUnfoldComponent,
mInteractionJankMonitor,
@@ -717,6 +716,40 @@
}
@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);
@@ -1252,10 +1285,10 @@
@Test
public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
enableSplitShade(/* enabled= */ true);
- mPanelExpansionStateManager.updateState(STATE_CLOSED);
+ mShadeExpansionStateManager.updateState(STATE_CLOSED);
assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
- mPanelExpansionStateManager.updateState(STATE_OPENING);
+ mShadeExpansionStateManager.updateState(STATE_OPENING);
assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue();
}
@@ -1263,11 +1296,11 @@
@Test
public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() {
enableSplitShade(/* enabled= */ true);
- mPanelExpansionStateManager.updateState(STATE_CLOSED);
+ mShadeExpansionStateManager.updateState(STATE_CLOSED);
mStatusBarStateController.setState(KEYGUARD);
// going to lockscreen would trigger STATE_OPENING
- mPanelExpansionStateManager.updateState(STATE_OPENING);
+ mShadeExpansionStateManager.updateState(STATE_OPENING);
assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
}
@@ -1275,11 +1308,11 @@
@Test
public void testQsImmediateResetsWhenPanelOpensOrCloses() {
mNotificationPanelViewController.mQsExpandImmediate = true;
- mPanelExpansionStateManager.updateState(STATE_OPEN);
+ mShadeExpansionStateManager.updateState(STATE_OPEN);
assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
mNotificationPanelViewController.mQsExpandImmediate = true;
- mPanelExpansionStateManager.updateState(STATE_CLOSED);
+ mShadeExpansionStateManager.updateState(STATE_CLOSED);
assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
}
@@ -1300,7 +1333,7 @@
@Test
public void testPanelClosedWhenClosingQsInSplitShade() {
- mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+ mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
/* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
enableSplitShade(/* enabled= */ true);
mNotificationPanelViewController.setExpandedFraction(1f);
@@ -1312,7 +1345,7 @@
@Test
public void testPanelStaysOpenWhenClosingQs() {
- mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+ mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
/* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
mNotificationPanelViewController.setExpandedFraction(1f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 481e4e9..db7e017 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -41,7 +41,6 @@
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -117,7 +116,7 @@
notificationShadeDepthController,
view,
notificationPanelViewController,
- PanelExpansionStateManager(),
+ ShadeExpansionStateManager(),
stackScrollLayoutController,
statusBarKeyguardViewManager,
statusBarWindowStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 4a7dec9..26a0770 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -51,7 +51,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.tuner.TunerService;
@@ -118,7 +117,7 @@
mNotificationShadeDepthController,
mView,
mNotificationPanelViewController,
- new PanelExpansionStateManager(),
+ new ShadeExpansionStateManager(),
mNotificationStackScrollLayoutController,
mStatusBarKeyguardViewManager,
mStatusBarWindowStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt
new file mode 100644
index 0000000..a601b67
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class ShadeExpansionStateManagerTest : SysuiTestCase() {
+
+ private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+
+ @Before
+ fun setUp() {
+ shadeExpansionStateManager = ShadeExpansionStateManager()
+ }
+
+ @Test
+ fun onPanelExpansionChanged_listenerNotified() {
+ val listener = TestShadeExpansionListener()
+ shadeExpansionStateManager.addExpansionListener(listener)
+ val fraction = 0.6f
+ val expanded = true
+ val tracking = true
+ val dragDownAmount = 1234f
+
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction,
+ expanded,
+ tracking,
+ dragDownAmount
+ )
+
+ assertThat(listener.fraction).isEqualTo(fraction)
+ assertThat(listener.expanded).isEqualTo(expanded)
+ assertThat(listener.tracking).isEqualTo(tracking)
+ assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
+ }
+
+ @Test
+ fun addExpansionListener_listenerNotifiedOfCurrentValues() {
+ val fraction = 0.6f
+ val expanded = true
+ val tracking = true
+ val dragDownAmount = 1234f
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction,
+ expanded,
+ tracking,
+ dragDownAmount
+ )
+ val listener = TestShadeExpansionListener()
+
+ shadeExpansionStateManager.addExpansionListener(listener)
+
+ assertThat(listener.fraction).isEqualTo(fraction)
+ assertThat(listener.expanded).isEqualTo(expanded)
+ assertThat(listener.tracking).isEqualTo(tracking)
+ assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
+ }
+
+ @Test
+ fun updateState_listenerNotified() {
+ val listener = TestShadeStateListener()
+ shadeExpansionStateManager.addStateListener(listener)
+
+ shadeExpansionStateManager.updateState(STATE_OPEN)
+
+ assertThat(listener.state).isEqualTo(STATE_OPEN)
+ }
+
+ /* ***** [PanelExpansionStateManager.onPanelExpansionChanged] test cases *******/
+
+ /* Fraction < 1 test cases */
+
+ @Test
+ fun onPEC_fractionLessThanOne_expandedTrue_trackingFalse_becomesStateOpening() {
+ val listener = TestShadeStateListener()
+ shadeExpansionStateManager.addStateListener(listener)
+
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction = 0.5f,
+ expanded = true,
+ tracking = false,
+ dragDownPxAmount = 0f
+ )
+
+ assertThat(listener.state).isEqualTo(STATE_OPENING)
+ }
+
+ @Test
+ fun onPEC_fractionLessThanOne_expandedTrue_trackingTrue_becomesStateOpening() {
+ val listener = TestShadeStateListener()
+ shadeExpansionStateManager.addStateListener(listener)
+
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction = 0.5f,
+ expanded = true,
+ tracking = true,
+ dragDownPxAmount = 0f
+ )
+
+ assertThat(listener.state).isEqualTo(STATE_OPENING)
+ }
+
+ @Test
+ fun onPEC_fractionLessThanOne_expandedFalse_trackingFalse_becomesStateClosed() {
+ val listener = TestShadeStateListener()
+ shadeExpansionStateManager.addStateListener(listener)
+ // Start out on a different state
+ shadeExpansionStateManager.updateState(STATE_OPEN)
+
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction = 0.5f,
+ expanded = false,
+ tracking = false,
+ dragDownPxAmount = 0f
+ )
+
+ assertThat(listener.state).isEqualTo(STATE_CLOSED)
+ }
+
+ @Test
+ fun onPEC_fractionLessThanOne_expandedFalse_trackingTrue_doesNotBecomeStateClosed() {
+ val listener = TestShadeStateListener()
+ shadeExpansionStateManager.addStateListener(listener)
+ // Start out on a different state
+ shadeExpansionStateManager.updateState(STATE_OPEN)
+
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction = 0.5f,
+ expanded = false,
+ tracking = true,
+ dragDownPxAmount = 0f
+ )
+
+ assertThat(listener.state).isEqualTo(STATE_OPEN)
+ }
+
+ /* Fraction = 1 test cases */
+
+ @Test
+ fun onPEC_fractionOne_expandedTrue_trackingFalse_becomesStateOpeningThenStateOpen() {
+ val listener = TestShadeStateListener()
+ shadeExpansionStateManager.addStateListener(listener)
+
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction = 1f,
+ expanded = true,
+ tracking = false,
+ dragDownPxAmount = 0f
+ )
+
+ assertThat(listener.previousState).isEqualTo(STATE_OPENING)
+ assertThat(listener.state).isEqualTo(STATE_OPEN)
+ }
+
+ @Test
+ fun onPEC_fractionOne_expandedTrue_trackingTrue_becomesStateOpening() {
+ val listener = TestShadeStateListener()
+ shadeExpansionStateManager.addStateListener(listener)
+
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction = 1f,
+ expanded = true,
+ tracking = true,
+ dragDownPxAmount = 0f
+ )
+
+ assertThat(listener.state).isEqualTo(STATE_OPENING)
+ }
+
+ @Test
+ fun onPEC_fractionOne_expandedFalse_trackingFalse_becomesStateClosed() {
+ val listener = TestShadeStateListener()
+ shadeExpansionStateManager.addStateListener(listener)
+ // Start out on a different state
+ shadeExpansionStateManager.updateState(STATE_OPEN)
+
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction = 1f,
+ expanded = false,
+ tracking = false,
+ dragDownPxAmount = 0f
+ )
+
+ assertThat(listener.state).isEqualTo(STATE_CLOSED)
+ }
+
+ @Test
+ fun onPEC_fractionOne_expandedFalse_trackingTrue_doesNotBecomeStateClosed() {
+ val listener = TestShadeStateListener()
+ shadeExpansionStateManager.addStateListener(listener)
+ // Start out on a different state
+ shadeExpansionStateManager.updateState(STATE_OPEN)
+
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction = 1f,
+ expanded = false,
+ tracking = true,
+ dragDownPxAmount = 0f
+ )
+
+ assertThat(listener.state).isEqualTo(STATE_OPEN)
+ }
+
+ /* ***** end [PanelExpansionStateManager.onPanelExpansionChanged] test cases ******/
+
+ class TestShadeExpansionListener : ShadeExpansionListener {
+ var fraction: Float = 0f
+ var expanded: Boolean = false
+ var tracking: Boolean = false
+ var dragDownAmountPx: Float = 0f
+
+ override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
+ this.fraction = event.fraction
+ this.expanded = event.expanded
+ this.tracking = event.tracking
+ this.dragDownAmountPx = event.dragDownPxAmount
+ }
+ }
+
+ class TestShadeStateListener : ShadeStateListener {
+ @PanelState var previousState: Int = STATE_CLOSED
+ @PanelState var state: Int = STATE_CLOSED
+
+ override fun onPanelStateChanged(state: Int) {
+ this.previousState = this.state
+ this.state = state
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index 6be76a6..84f8656 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -5,13 +5,13 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.shade.STATE_CLOSED
+import com.android.systemui.shade.STATE_OPEN
+import com.android.systemui.shade.STATE_OPENING
+import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.HeadsUpManager
import org.junit.Before
@@ -148,7 +148,7 @@
companion object {
val EXPANSION_EVENT =
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
index b6f8326..7cac854 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
@@ -7,12 +7,12 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.shade.STATE_OPENING
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.FakeConfigurationController
import org.junit.Before
import org.junit.Test
@@ -40,7 +40,7 @@
private lateinit var controller: ShadeTransitionController
private val configurationController = FakeConfigurationController()
- private val panelExpansionStateManager = PanelExpansionStateManager()
+ private val shadeExpansionStateManager = ShadeExpansionStateManager()
@Before
fun setUp() {
@@ -49,7 +49,7 @@
controller =
ShadeTransitionController(
configurationController,
- panelExpansionStateManager,
+ shadeExpansionStateManager,
dumpManager,
context,
splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
@@ -166,7 +166,7 @@
}
private fun startPanelExpansion() {
- panelExpansionStateManager.onPanelExpansionChanged(
+ shadeExpansionStateManager.onPanelExpansionChanged(
DEFAULT_EXPANSION_EVENT.fraction,
DEFAULT_EXPANSION_EVENT.expanded,
DEFAULT_EXPANSION_EVENT.tracking,
@@ -194,7 +194,7 @@
companion object {
private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
private val DEFAULT_EXPANSION_EVENT =
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 0.5f,
expanded = true,
tracking = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt
index aafd871..0e48b48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt
@@ -7,11 +7,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.shade.STATE_CLOSED
+import com.android.systemui.shade.STATE_OPEN
+import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.FakeConfigurationController
import org.junit.Before
import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
index a4a89a4..7a74b12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -27,8 +27,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -42,8 +42,8 @@
private val viewsIdToRegister =
setOf(
- ViewIdToTranslate(LEFT_VIEW_ID, Direction.LEFT),
- ViewIdToTranslate(RIGHT_VIEW_ID, Direction.RIGHT))
+ ViewIdToTranslate(START_VIEW_ID, Direction.START),
+ ViewIdToTranslate(END_VIEW_ID, Direction.END))
@Before
fun setup() {
@@ -66,41 +66,62 @@
}
@Test
- fun onTransition_oneMovesLeft() {
+ fun onTransition_oneMovesStartWithLTR() {
// GIVEN one view with a matching id
val view = View(context)
- whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(view)
+ whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(view)
- moveAndValidate(listOf(view to LEFT))
+ moveAndValidate(listOf(view to START), View.LAYOUT_DIRECTION_LTR)
}
@Test
- fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
+ fun onTransition_oneMovesStartWithRTL() {
+ // GIVEN one view with a matching id
+ val view = View(context)
+ whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(view)
+
+ whenever(parent.getLayoutDirection()).thenReturn(View.LAYOUT_DIRECTION_RTL)
+ moveAndValidate(listOf(view to START), View.LAYOUT_DIRECTION_RTL)
+ }
+
+ @Test
+ fun onTransition_oneMovesStartAndOneMovesEndMultipleTimes() {
// GIVEN two views with a matching id
val leftView = View(context)
val rightView = View(context)
- whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(leftView)
- whenever(parent.findViewById<View>(RIGHT_VIEW_ID)).thenReturn(rightView)
+ whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(leftView)
+ whenever(parent.findViewById<View>(END_VIEW_ID)).thenReturn(rightView)
- moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
- moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+ moveAndValidate(listOf(leftView to START, rightView to END), View.LAYOUT_DIRECTION_LTR)
+ moveAndValidate(listOf(leftView to START, rightView to END), View.LAYOUT_DIRECTION_LTR)
}
- private fun moveAndValidate(list: List<Pair<View, Int>>) {
+ private fun moveAndValidate(list: List<Pair<View, Int>>, layoutDirection: Int) {
// Compare values as ints because -0f != 0f
// WHEN the transition starts
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0f)
+ val rtlMultiplier = if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ 1
+ } else {
+ -1
+ }
list.forEach { (view, direction) ->
- assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
+ assertEquals(
+ (-MAX_TRANSLATION * direction * rtlMultiplier).toInt(),
+ view.translationX.toInt()
+ )
}
// WHEN the transition progresses, translation is updated
progressProvider.onTransitionProgress(.5f)
list.forEach { (view, direction) ->
- assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
+ assertEquals(
+ (-MAX_TRANSLATION / 2f * direction * rtlMultiplier).toInt(),
+ view.translationX.toInt()
+ )
}
// WHEN the transition ends, translation is completed
@@ -110,12 +131,12 @@
}
companion object {
- private val LEFT = Direction.LEFT.multiplier.toInt()
- private val RIGHT = Direction.RIGHT.multiplier.toInt()
+ private val START = Direction.START.multiplier.toInt()
+ private val END = Direction.END.multiplier.toInt()
private const val MAX_TRANSLATION = 42f
- private const val LEFT_VIEW_ID = 1
- private const val RIGHT_VIEW_ID = 2
+ private const val START_VIEW_ID = 1
+ private const val END_VIEW_ID = 2
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 8be138a..ffb41e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -22,7 +22,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProviderPlugin
@@ -48,8 +48,8 @@
@JvmField @Rule val mockito = MockitoJUnit.rule()
@Mock private lateinit var mockContext: Context
@Mock private lateinit var mockPluginManager: PluginManager
- @Mock private lateinit var mockClock: Clock
- @Mock private lateinit var mockDefaultClock: Clock
+ @Mock private lateinit var mockClock: ClockController
+ @Mock private lateinit var mockDefaultClock: ClockController
@Mock private lateinit var mockThumbnail: Drawable
@Mock private lateinit var mockHandler: Handler
@Mock private lateinit var mockContentResolver: ContentResolver
@@ -60,7 +60,7 @@
private var settingValue: String = ""
companion object {
- private fun failFactory(): Clock {
+ private fun failFactory(): ClockController {
fail("Unexpected call to createClock")
return null!!
}
@@ -73,17 +73,17 @@
private class FakeClockPlugin : ClockProviderPlugin {
private val metadata = mutableListOf<ClockMetadata>()
- private val createCallbacks = mutableMapOf<ClockId, () -> Clock>()
+ private val createCallbacks = mutableMapOf<ClockId, () -> ClockController>()
private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
override fun getClocks() = metadata
- override fun createClock(id: ClockId): Clock = createCallbacks[id]!!()
+ override fun createClock(id: ClockId): ClockController = createCallbacks[id]!!()
override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
fun addClock(
id: ClockId,
name: String,
- create: () -> Clock = ::failFactory,
+ create: () -> ClockController = ::failFactory,
getThumbnail: () -> Drawable? = ::failThumbnail
): FakeClockPlugin {
metadata.add(ClockMetadata(id, name))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 2b4a109..539a54b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shared.clocks
import android.content.res.Resources
+import android.graphics.Color
import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.util.TypedValue
@@ -25,7 +26,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.shared.clocks.DefaultClock.Companion.DOZE_COLOR
+import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -88,17 +89,20 @@
// Default clock provider must always provide the default clock
val clock = provider.createClock(DEFAULT_CLOCK_ID)
assertNotNull(clock)
- assertEquals(clock.smallClock, mockSmallClockView)
- assertEquals(clock.largeClock, mockLargeClockView)
+ assertEquals(mockSmallClockView, clock.smallClock.view)
+ assertEquals(mockLargeClockView, clock.largeClock.view)
}
@Test
fun defaultClock_initialize() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ verify(mockSmallClockView).setColors(Color.MAGENTA, Color.MAGENTA)
+ verify(mockLargeClockView).setColors(Color.MAGENTA, Color.MAGENTA)
+
clock.initialize(resources, 0f, 0f)
- verify(mockSmallClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
- verify(mockLargeClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
+ verify(mockSmallClockView).setColors(eq(DOZE_COLOR), anyInt())
+ verify(mockLargeClockView).setColors(eq(DOZE_COLOR), anyInt())
verify(mockSmallClockView).onTimeZoneChanged(notNull())
verify(mockLargeClockView).onTimeZoneChanged(notNull())
verify(mockSmallClockView).refreshTime()
@@ -147,10 +151,14 @@
@Test
fun defaultClock_events_onColorPaletteChanged() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
- clock.events.onColorPaletteChanged(resources, true, true)
- verify(mockSmallClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
- verify(mockLargeClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
+ verify(mockSmallClockView).setColors(Color.MAGENTA, Color.MAGENTA)
+ verify(mockLargeClockView).setColors(Color.MAGENTA, Color.MAGENTA)
+
+ clock.events.onColorPaletteChanged(resources)
+
+ verify(mockSmallClockView).setColors(eq(DOZE_COLOR), anyInt())
+ verify(mockLargeClockView).setColors(eq(DOZE_COLOR), anyInt())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
index f9e279e..5b34a95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
@@ -3,72 +3,72 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import java.lang.Thread.UncaughtExceptionHandler
import org.junit.Assert.assertThrows
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito.only
import org.mockito.Mockito.any
+import org.mockito.Mockito.only
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.lang.Thread.UncaughtExceptionHandler
@SmallTest
class UncaughtExceptionPreHandlerTest : SysuiTestCase() {
- private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager
+ private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager
- @Mock
- private lateinit var mockHandler: UncaughtExceptionHandler
+ @Mock private lateinit var mockHandler: UncaughtExceptionHandler
- @Mock
- private lateinit var mockHandler2: UncaughtExceptionHandler
+ @Mock private lateinit var mockHandler2: UncaughtExceptionHandler
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- Thread.setUncaughtExceptionPreHandler(null)
- preHandlerManager = UncaughtExceptionPreHandlerManager()
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ Thread.setUncaughtExceptionPreHandler(null)
+ preHandlerManager = UncaughtExceptionPreHandlerManager()
+ }
+
+ @Test
+ fun registerHandler_registersOnceOnly() {
+ preHandlerManager.registerHandler(mockHandler)
+ preHandlerManager.registerHandler(mockHandler)
+ preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+ verify(mockHandler, only()).uncaughtException(any(), any())
+ }
+
+ @Test
+ fun registerHandler_setsUncaughtExceptionPreHandler() {
+ Thread.setUncaughtExceptionPreHandler(null)
+ preHandlerManager.registerHandler(mockHandler)
+ assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull()
+ }
+
+ @Test
+ fun registerHandler_preservesOriginalHandler() {
+ Thread.setUncaughtExceptionPreHandler(mockHandler)
+ preHandlerManager.registerHandler(mockHandler2)
+ preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+ verify(mockHandler, only()).uncaughtException(any(), any())
+ }
+
+ @Test
+ @Ignore
+ fun registerHandler_toleratesHandlersThatThrow() {
+ `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException())
+ preHandlerManager.registerHandler(mockHandler2)
+ preHandlerManager.registerHandler(mockHandler)
+ preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+ verify(mockHandler2, only()).uncaughtException(any(), any())
+ verify(mockHandler, only()).uncaughtException(any(), any())
+ }
+
+ @Test
+ fun registerHandler_doesNotSetUpTwice() {
+ UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2)
+ assertThrows(IllegalStateException::class.java) {
+ preHandlerManager.registerHandler(mockHandler)
}
-
- @Test
- fun registerHandler_registersOnceOnly() {
- preHandlerManager.registerHandler(mockHandler)
- preHandlerManager.registerHandler(mockHandler)
- preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
- verify(mockHandler, only()).uncaughtException(any(), any())
- }
-
- @Test
- fun registerHandler_setsUncaughtExceptionPreHandler() {
- Thread.setUncaughtExceptionPreHandler(null)
- preHandlerManager.registerHandler(mockHandler)
- assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull()
- }
-
- @Test
- fun registerHandler_preservesOriginalHandler() {
- Thread.setUncaughtExceptionPreHandler(mockHandler)
- preHandlerManager.registerHandler(mockHandler2)
- preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
- verify(mockHandler, only()).uncaughtException(any(), any())
- }
-
- @Test
- fun registerHandler_toleratesHandlersThatThrow() {
- `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException())
- preHandlerManager.registerHandler(mockHandler2)
- preHandlerManager.registerHandler(mockHandler)
- preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
- verify(mockHandler2, only()).uncaughtException(any(), any())
- verify(mockHandler, only()).uncaughtException(any(), any())
- }
-
- @Test
- fun registerHandler_doesNotSetUpTwice() {
- UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2)
- assertThrows(IllegalStateException::class.java) {
- preHandlerManager.registerHandler(mockHandler)
- }
- }
-}
\ No newline at end of file
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
deleted file mode 100644
index 5432a74..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static org.junit.Assert.assertFalse;
-
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Verifies that particular sets of dependencies don't have dependencies on others. For example,
- * code managing notifications shouldn't directly depend on CentralSurfaces, since there are
- * platforms which want to manage notifications, but don't use CentralSurfaces.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NonPhoneDependencyTest extends SysuiTestCase {
- @Mock private NotificationPresenter mPresenter;
- @Mock private NotificationListContainer mListContainer;
- @Mock private RemoteInputController.Delegate mDelegate;
- @Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
- @Mock private OnSettingsClickListener mOnSettingsClickListener;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
- mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
- new Handler(TestableLooper.get(this).getLooper()));
- }
-
- @Ignore("Causes binder calls which fail")
- @Test
- public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
- mDependency.injectMockDependency(ShadeController.class);
- NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
- NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
- NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
- NotificationRemoteInputManager remoteInputManager =
- Dependency.get(NotificationRemoteInputManager.class);
- NotificationLockscreenUserManager lockscreenUserManager =
- Dependency.get(NotificationLockscreenUserManager.class);
- gutsManager.setUpWithPresenter(mPresenter, mListContainer,
- mOnSettingsClickListener);
- notificationLogger.setUpWithContainer(mListContainer);
- mediaManager.setUpWithPresenter(mPresenter);
- remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
- mDelegate);
- lockscreenUserManager.setUpWithPresenter(mPresenter);
-
- TestableLooper.get(this).processAllMessages();
- assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 853d1df..bdafa48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -88,6 +89,8 @@
@Mock
private NotificationClickNotifier mClickNotifier;
@Mock
+ private OverviewProxyService mOverviewProxyService;
+ @Mock
private KeyguardManager mKeyguardManager;
@Mock
private DeviceProvisionedController mDeviceProvisionedController;
@@ -344,6 +347,7 @@
(() -> mVisibilityProvider),
(() -> mNotifCollection),
mClickNotifier,
+ (() -> mOverviewProxyService),
NotificationLockscreenUserManagerTest.this.mKeyguardManager,
mStatusBarStateController,
Handler.createAsync(Looper.myLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 6446fb5..77b1e37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -28,10 +28,10 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.WallpaperController
@@ -137,7 +137,7 @@
@Test
fun onPanelExpansionChanged_apliesBlur_ifShade() {
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@@ -145,7 +145,7 @@
@Test
fun onPanelExpansionChanged_animatesBlurIn_ifShade() {
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 0.01f, expanded = false, tracking = false, dragDownPxAmount = 0f))
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@@ -155,7 +155,7 @@
onPanelExpansionChanged_animatesBlurIn_ifShade()
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 0f, expanded = false, tracking = false, dragDownPxAmount = 0f))
verify(shadeAnimation).animateTo(eq(0))
}
@@ -163,7 +163,7 @@
@Test
fun onPanelExpansionChanged_animatesBlurOut_ifFlick() {
val event =
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f)
onPanelExpansionChanged_apliesBlur_ifShade()
clearInvocations(shadeAnimation)
@@ -184,7 +184,7 @@
onPanelExpansionChanged_animatesBlurOut_ifFlick()
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 0.6f, expanded = true, tracking = true, dragDownPxAmount = 0f))
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@@ -192,7 +192,7 @@
@Test
fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
val event =
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 0f)
notificationShadeDepthController.panelPullDownMinFraction = 0.5f
notificationShadeDepthController.onPanelExpansionChanged(event)
@@ -220,7 +220,7 @@
statusBarState = StatusBarState.KEYGUARD
notificationShadeDepthController.qsPanelExpansion = 1f
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
@@ -231,7 +231,7 @@
statusBarState = StatusBarState.KEYGUARD
notificationShadeDepthController.qsPanelExpansion = 0.25f
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController)
@@ -243,7 +243,7 @@
enableSplitShade()
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
notificationShadeDepthController.updateBlurCallback.doFrame(0)
@@ -255,7 +255,7 @@
disableSplitShade()
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
notificationShadeDepthController.updateBlurCallback.doFrame(0)
@@ -269,7 +269,7 @@
val expanded = true
val tracking = false
val dragDownPxAmount = 0f
- val event = PanelExpansionChangeEvent(rawFraction, expanded, tracking, dragDownPxAmount)
+ val event = ShadeExpansionChangeEvent(rawFraction, expanded, tracking, dragDownPxAmount)
val inOrder = Mockito.inOrder(wallpaperController)
notificationShadeDepthController.onPanelExpansionChanged(event)
@@ -333,7 +333,7 @@
@Test
fun updateBlurCallback_setsBlur_whenExpanded() {
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
@@ -343,7 +343,7 @@
@Test
fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() {
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = true
@@ -361,7 +361,7 @@
@Test
fun ignoreBlurForUnlock_ignores() {
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
@@ -378,7 +378,7 @@
@Test
fun ignoreBlurForUnlock_doesNotIgnore() {
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
@@ -410,7 +410,7 @@
`when`(brightnessSpring.ratio).thenReturn(1f)
// And shade is blurred
notificationShadeDepthController.onPanelExpansionChanged(
- PanelExpansionChangeEvent(
+ ShadeExpansionChangeEvent(
fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index b719c7f..a6381d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -32,7 +32,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Assert;
@@ -58,8 +57,6 @@
mDynamicPrivacyController = new DynamicPrivacyController(
mLockScreenUserManager, mKeyguardStateController,
mock(StatusBarStateController.class));
- mDynamicPrivacyController.setStatusBarKeyguardViewManager(
- mock(StatusBarKeyguardViewManager.class));
mDynamicPrivacyController.addListener(mListener);
// Disable dynamic privacy by default
allowNotificationsInPublic(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 2970807..340bc96 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.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.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 @@
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/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index f4adf69..dcf2455 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -181,7 +181,7 @@
@Test
public void testInflatesNewNotification() {
// WHEN there is a new notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
// THEN we inflate it
@@ -194,7 +194,7 @@
@Test
public void testRebindsInflatedNotificationsOnUpdate() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -213,7 +213,7 @@
@Test
public void testEntrySmartReplyAdditionWillRebindViews() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -232,7 +232,7 @@
@Test
public void testEntryChangedToMinimizedSectionWillRebindViews() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
assertFalse(mParamsCaptor.getValue().isLowPriority());
@@ -254,28 +254,36 @@
public void testMinimizedEntryMovedIntoGroupWillRebindViews() {
// GIVEN an inflated, minimized notification
setSectionIsLowPriority(true);
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
assertTrue(mParamsCaptor.getValue().isLowPriority());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
// WHEN notification is moved under a parent
- NotificationEntryBuilder.setNewParent(mEntry, mock(GroupEntry.class));
- mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
+ NotificationEntry groupSummary = getNotificationEntryBuilder()
+ .setParent(ROOT_ENTRY)
+ .setGroupSummary(mContext, true)
+ .setGroup(mContext, TEST_GROUP_KEY)
+ .build();
+ GroupEntry parent = mock(GroupEntry.class);
+ when(parent.getSummary()).thenReturn(groupSummary);
+ NotificationEntryBuilder.setNewParent(mEntry, parent);
+ mCollectionListener.onEntryInit(groupSummary);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry, groupSummary));
// THEN we rebind it as not-minimized
verify(mNotifInflater).rebindViews(eq(mEntry), mParamsCaptor.capture(), any());
assertFalse(mParamsCaptor.getValue().isLowPriority());
- // THEN we do not filter it because it's not the first inflation.
- assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0));
+ // THEN we filter it because the parent summary is not yet inflated.
+ assertTrue(mUninflatedFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void testEntryRankChangeWillNotRebindViews() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -294,7 +302,7 @@
@Test
public void testDoesntFilterInflatedNotifs() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -330,9 +338,9 @@
mCollectionListener.onEntryInit(entry);
}
- mCollectionListener.onEntryAdded(summary);
+ mCollectionListener.onEntryInit(summary);
for (NotificationEntry entry : children) {
- mCollectionListener.onEntryAdded(entry);
+ mCollectionListener.onEntryInit(entry);
}
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(groupEntry));
@@ -393,6 +401,40 @@
}
@Test
+ public void testPartiallyInflatedGroupsAreNotFilteredOutIfSummaryReinflate() {
+ // GIVEN a newly-posted group with a summary and two children
+ final String groupKey = "test_reinflate_group";
+ final int summaryId = 1;
+ final GroupEntry group = new GroupEntryBuilder()
+ .setKey(groupKey)
+ .setCreationTime(400)
+ .setSummary(getNotificationEntryBuilder().setId(summaryId).setImportance(1).build())
+ .addChild(getNotificationEntryBuilder().setId(2).build())
+ .addChild(getNotificationEntryBuilder().setId(3).build())
+ .build();
+ fireAddEvents(List.of(group));
+ final NotificationEntry summary = group.getSummary();
+ final NotificationEntry child0 = group.getChildren().get(0);
+ final NotificationEntry child1 = group.getChildren().get(1);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // WHEN all of the children (but not the summary) finish inflating
+ mNotifInflater.invokeInflateCallbackForEntry(child0);
+ mNotifInflater.invokeInflateCallbackForEntry(child1);
+ mNotifInflater.invokeInflateCallbackForEntry(summary);
+
+ // WHEN the summary is updated and starts re-inflating
+ summary.setRanking(new RankingBuilder(summary.getRanking()).setImportance(4).build());
+ fireUpdateEvents(summary);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // THEN the entire group is still not filtered out
+ assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
+ assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
+ assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
+ }
+
+ @Test
public void testCompletedInflatedGroupsAreReleased() {
// GIVEN a newly-posted group with a summary and two children
final GroupEntry group = new GroupEntryBuilder()
@@ -412,7 +454,7 @@
mNotifInflater.invokeInflateCallbackForEntry(child1);
mNotifInflater.invokeInflateCallbackForEntry(summary);
- // THEN the entire group is still filtered out
+ // THEN the entire group is no longer filtered out
assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
@@ -494,7 +536,11 @@
private void fireAddEvents(NotificationEntry entry) {
mCollectionListener.onEntryInit(entry);
- mCollectionListener.onEntryAdded(entry);
+ mCollectionListener.onEntryInit(entry);
+ }
+
+ private void fireUpdateEvents(NotificationEntry entry) {
+ mCollectionListener.onEntryUpdated(entry);
}
private static final String TEST_MESSAGE = "TEST_MESSAGE";
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 3f641df..ca65987 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 @@
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 @@
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 @@
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 d59cc54..8b7b4de 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.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 @@
}
@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 @@
}
@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/logging/NotificationMemoryMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
new file mode 100644
index 0000000..16e2441
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
@@ -0,0 +1,321 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.widget.RemoteViews
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationMemoryMonitorTest : SysuiTestCase() {
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_plainNotification() {
+ val notification = createBasicNotification().build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3316,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmap() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
+ val notification = createBasicNotification().setLargeIcon(icon).setSmallIcon(icon).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = 0,
+ extras = 3316,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_customViewNotification_marksTrue() {
+ val notification =
+ createBasicNotification()
+ .setCustomContentView(
+ RemoteViews(context.packageName, android.R.layout.list_content)
+ )
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3384,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = true,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_notificationWithDataIcon_calculatesCorrectly() {
+ val dataIcon = Icon.createWithData(ByteArray(444444), 0, 444444)
+ val notification =
+ createBasicNotification().setLargeIcon(dataIcon).setSmallIcon(dataIcon).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = 444444,
+ largeIcon = 0,
+ extras = 3212,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_bigPictureStyle() {
+ val bigPicture =
+ Icon.createWithBitmap(Bitmap.createBitmap(600, 400, Bitmap.Config.ARGB_8888))
+ val bigPictureIcon =
+ Icon.createWithAdaptiveBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+ val notification =
+ createBasicNotification()
+ .setStyle(
+ Notification.BigPictureStyle()
+ .bigPicture(bigPicture)
+ .bigLargeIcon(bigPictureIcon)
+ )
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 4092,
+ bigPicture = bigPicture.bitmap.allocationByteCount,
+ extender = 0,
+ style = "BigPictureStyle",
+ styleIcon = bigPictureIcon.bitmap.allocationByteCount,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_callingStyle() {
+ val personIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+ val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+ val fakeIntent =
+ PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+ val notification =
+ createBasicNotification()
+ .setStyle(Notification.CallStyle.forIncomingCall(person, fakeIntent, fakeIntent))
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 4084,
+ bigPicture = 0,
+ extender = 0,
+ style = "CallStyle",
+ styleIcon = personIcon.bitmap.allocationByteCount,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_messagingStyle() {
+ val personIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+ val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+ val message = Notification.MessagingStyle.Message("Message!", 4323, person)
+ val historicPersonIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(348, 382, Bitmap.Config.ARGB_8888))
+ val historicPerson =
+ Person.Builder().setIcon(historicPersonIcon).setName("Historic person").build()
+ val historicMessage =
+ Notification.MessagingStyle.Message("Historic message!", 5848, historicPerson)
+
+ val notification =
+ createBasicNotification()
+ .setStyle(
+ Notification.MessagingStyle(person)
+ .addMessage(message)
+ .addHistoricMessage(historicMessage)
+ )
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 5024,
+ bigPicture = 0,
+ extender = 0,
+ style = "MessagingStyle",
+ styleIcon =
+ personIcon.bitmap.allocationByteCount +
+ historicPersonIcon.bitmap.allocationByteCount,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_carExtender() {
+ val carIcon = Bitmap.createBitmap(432, 322, Bitmap.Config.ARGB_8888)
+ val extender = Notification.CarExtender().setLargeIcon(carIcon)
+ val notification = createBasicNotification().extend(extender).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3612,
+ bigPicture = 0,
+ extender = 556656,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_tvWearExtender() {
+ val tvExtender = Notification.TvExtender().setChannel("channel2")
+ val wearBackground = Bitmap.createBitmap(443, 433, Bitmap.Config.ARGB_8888)
+ val wearExtender = Notification.WearableExtender().setBackground(wearBackground)
+ val notification = createBasicNotification().extend(tvExtender).extend(wearExtender).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3820,
+ bigPicture = 0,
+ extender = 388 + wearBackground.allocationByteCount,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ private fun createBasicNotification(): Notification.Builder {
+ val smallIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(250, 250, Bitmap.Config.ARGB_8888))
+ val largeIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888))
+ return Notification.Builder(context)
+ .setSmallIcon(smallIcon)
+ .setLargeIcon(largeIcon)
+ .setContentTitle("This is a title")
+ .setContentText("This is content text.")
+ }
+
+ /** This will generate a nicer error message than comparing objects */
+ private fun assertNotificationObjectSizes(
+ memoryUse: NotificationMemoryUsage,
+ smallIcon: Int,
+ largeIcon: Int,
+ extras: Int,
+ bigPicture: Int,
+ extender: Int,
+ style: String?,
+ styleIcon: Int,
+ hasCustomView: Boolean
+ ) {
+ assertThat(memoryUse.packageName).isEqualTo("test_pkg")
+ assertThat(memoryUse.notificationId)
+ .isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
+ assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
+ assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
+ assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
+ if (style == null) {
+ assertThat(memoryUse.objectUsage.style).isNull()
+ } else {
+ assertThat(memoryUse.objectUsage.style).isEqualTo(style)
+ }
+ assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
+ assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
+ }
+
+ private fun getUseObject(
+ singleItemUseList: List<NotificationMemoryUsage>
+ ): NotificationMemoryUsage {
+ assertThat(singleItemUseList).hasSize(1)
+ return singleItemUseList[0]
+ }
+
+ private fun createNMMWithNotifications(
+ notifications: List<Notification>
+ ): NotificationMemoryMonitor {
+ val notifPipeline: NotifPipeline = mock()
+ val notificationEntries =
+ notifications.map { n ->
+ NotificationEntryBuilder().setTag("test").setNotification(n).build()
+ }
+ whenever(notifPipeline.allNotifs).thenReturn(notificationEntries)
+ return NotificationMemoryMonitor(notifPipeline, mock())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 682ff1f..81b8e98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -32,6 +32,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.widget.NotificationActionListLayout;
import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -142,4 +143,60 @@
verify(mockExpandedEB, times(1)).requestAccessibilityFocus();
verify(mockHeadsUpEB, times(0)).requestAccessibilityFocus();
}
+
+ @Test
+ @UiThreadTest
+ public void testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() {
+ View mockContracted = mock(NotificationHeaderView.class);
+
+ View mockExpandedActions = mock(NotificationActionListLayout.class);
+ View mockExpanded = mock(NotificationHeaderView.class);
+ when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockExpandedActions);
+
+ View mockHeadsUpActions = mock(NotificationActionListLayout.class);
+ View mockHeadsUp = mock(NotificationHeaderView.class);
+ when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockHeadsUpActions);
+
+ mView.setContractedChild(mockContracted);
+ mView.setExpandedChild(mockExpanded);
+ mView.setHeadsUpChild(mockHeadsUp);
+
+ mView.setRemoteInputVisible(true);
+
+ verify(mockContracted, times(0)).findViewById(0);
+ verify(mockExpandedActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() {
+ View mockContracted = mock(NotificationHeaderView.class);
+
+ View mockExpandedActions = mock(NotificationActionListLayout.class);
+ View mockExpanded = mock(NotificationHeaderView.class);
+ when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockExpandedActions);
+
+ View mockHeadsUpActions = mock(NotificationActionListLayout.class);
+ View mockHeadsUp = mock(NotificationHeaderView.class);
+ when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockHeadsUpActions);
+
+ mView.setContractedChild(mockContracted);
+ mView.setExpandedChild(mockExpanded);
+ mView.setHeadsUpChild(mockHeadsUp);
+
+ mView.setRemoteInputVisible(false);
+
+ verify(mockContracted, times(0)).findViewById(0);
+ verify(mockExpandedActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
}
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 ad3bd71..7c99568 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.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 android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.Log;
import androidx.test.filters.SmallTest;
@@ -100,6 +105,67 @@
verify(mBinder).unbindContent(eq(mEntry), any(), eq(flags));
}
+ class CountingWtfHandler implements Log.TerribleFailureHandler {
+ private Log.TerribleFailureHandler mOldHandler = null;
+ private int mWtfCount = 0;
+
+ public void register() {
+ mOldHandler = Log.setWtfHandler(this);
+ }
+
+ public void unregister() {
+ Log.setWtfHandler(mOldHandler);
+ mOldHandler = null;
+ }
+
+ @Override
+ public void onTerribleFailure(String tag, Log.TerribleFailure what, boolean system) {
+ mWtfCount++;
+ }
+
+ public int getWtfCount() {
+ return mWtfCount;
+ }
+ }
+
+ @Test
+ public void testGetStageParamsAfterCleanUp() {
+ // GIVEN an entry whose params have already been deleted.
+ RowContentBindParams originalParams = mRowContentBindStage.getStageParams(mEntry);
+ mRowContentBindStage.deleteStageParams(mEntry);
+
+ // WHEN a caller calls getStageParams.
+ CountingWtfHandler countingWtfHandler = new CountingWtfHandler();
+ countingWtfHandler.register();
+
+ RowContentBindParams blankParams = mRowContentBindStage.getStageParams(mEntry);
+
+ countingWtfHandler.unregister();
+
+ // THEN getStageParams logs a WTF and returns blank params created to avoid a crash.
+ assertEquals(1, countingWtfHandler.getWtfCount());
+ assertNotNull(blankParams);
+ assertNotSame(originalParams, blankParams);
+ }
+
+ @Test
+ public void testTryGetStageParamsAfterCleanUp() {
+ // GIVEN an entry whose params have already been deleted.
+ mRowContentBindStage.deleteStageParams(mEntry);
+
+ // WHEN a caller calls getStageParams.
+ CountingWtfHandler countingWtfHandler = new CountingWtfHandler();
+ countingWtfHandler.register();
+
+ RowContentBindParams nullParams = mRowContentBindStage.tryGetStageParams(mEntry);
+
+ countingWtfHandler.unregister();
+
+ // THEN getStageParams does NOT log a WTF and returns null to indicate missing params.
+ assertEquals(0, countingWtfHandler.getWtfCount());
+ assertNull(nullParams);
+ }
+
@Test
public void testRebindAllContentViews() {
// GIVEN a view with content bound.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 8be9eb5..1c9b0be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
@@ -127,6 +128,7 @@
@Mock private NotificationStackScrollLogger mLogger;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock private ShadeTransitionController mShadeTransitionController;
+ @Mock private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -174,7 +176,8 @@
mJankMonitor,
mStackLogger,
mLogger,
- mNotificationStackSizeCalculator
+ mNotificationStackSizeCalculator,
+ mFeatureFlags
);
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 1305d79..4ea1c71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -18,10 +18,13 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -51,10 +54,15 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Answer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
/**
* Tests for {@link NotificationSwipeHelper}.
*/
@@ -74,7 +82,11 @@
private Runnable mFalsingCheck;
private FeatureFlags mFeatureFlags;
- @Rule public MockitoRule mockito = MockitoJUnit.rule();
+ private static final int FAKE_ROW_WIDTH = 20;
+ private static final int FAKE_ROW_HEIGHT = 20;
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
@Before
public void setUp() throws Exception {
@@ -444,8 +456,8 @@
doReturn(5f).when(mEvent).getRawX();
doReturn(10f).when(mEvent).getRawY();
- doReturn(20).when(mView).getWidth();
- doReturn(20).when(mView).getHeight();
+ doReturn(FAKE_ROW_WIDTH).when(mView).getWidth();
+ doReturn(FAKE_ROW_HEIGHT).when(mView).getHeight();
Answer answer = (Answer) invocation -> {
int[] arr = invocation.getArgument(0);
@@ -472,8 +484,8 @@
doReturn(5f).when(mEvent).getRawX();
doReturn(10f).when(mEvent).getRawY();
- doReturn(20).when(mNotificationRow).getWidth();
- doReturn(20).when(mNotificationRow).getActualHeight();
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getWidth();
+ doReturn(FAKE_ROW_HEIGHT).when(mNotificationRow).getActualHeight();
Answer answer = (Answer) invocation -> {
int[] arr = invocation.getArgument(0);
@@ -491,4 +503,56 @@
assertFalse("Touch is not within the view",
mSwipeHelper.isTouchInView(mEvent, mNotificationRow));
}
+
+ @Test
+ public void testContentAlphaRemainsUnchangedWhenNotificationIsNotDismissible() {
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+ mSwipeHelper.onTranslationUpdate(mNotificationRow, 12, false);
+
+ verify(mNotificationRow, never()).setContentAlpha(anyFloat());
+ }
+
+ @Test
+ public void testContentAlphaRemainsUnchangedWhenFeatureFlagIsDisabled() {
+
+ // Returning true prevents alpha fade. In an unmocked scenario the callback is instantiated
+ // within NotificationStackScrollLayoutController and returns the inverted value of the
+ // feature flag
+ doReturn(true).when(mCallback).updateSwipeProgress(any(), anyBoolean(), anyFloat());
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+ mSwipeHelper.onTranslationUpdate(mNotificationRow, 12, true);
+
+ verify(mNotificationRow, never()).setContentAlpha(anyFloat());
+ }
+
+ @Test
+ public void testContentAlphaFadeAnimationSpecs() {
+ // The alpha fade should be linear from 1f to 0f as translation progresses from 0 to 60% of
+ // view-width, and 0f at translations higher than that.
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+ List<Integer> translations = Arrays.asList(
+ -FAKE_ROW_WIDTH * 2,
+ -FAKE_ROW_WIDTH,
+ (int) (-FAKE_ROW_WIDTH * 0.3),
+ 0,
+ (int) (FAKE_ROW_WIDTH * 0.3),
+ (int) (FAKE_ROW_WIDTH * 0.6),
+ FAKE_ROW_WIDTH,
+ FAKE_ROW_WIDTH * 2);
+ List<Float> expectedAlphas = translations.stream().map(translation ->
+ mSwipeHelper.getSwipeAlpha(Math.abs((float) translation / FAKE_ROW_WIDTH)))
+ .collect(Collectors.toList());
+
+ for (Integer translation : translations) {
+ mSwipeHelper.onTranslationUpdate(mNotificationRow, translation, true);
+ }
+
+ ArgumentCaptor<Float> capturedValues = ArgumentCaptor.forClass(Float.class);
+ verify(mNotificationRow, times(translations.size())).setContentAlpha(
+ capturedValues.capture());
+ assertEquals(expectedAlphas, capturedValues.getAllValues());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
new file mode 100644
index 0000000..da543d4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.util.Log
+import android.util.Log.TerribleFailureHandler
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import kotlin.math.log2
+import kotlin.math.sqrt
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ViewStateTest : SysuiTestCase() {
+ private val viewState = ViewState()
+
+ private var wtfHandler: TerribleFailureHandler? = null
+ private var wtfCount = 0
+
+ @Suppress("DIVISION_BY_ZERO")
+ @Test
+ fun testWtfs() {
+ interceptWtfs()
+
+ // Setting valid values doesn't cause any wtfs.
+ viewState.alpha = 0.1f
+ viewState.xTranslation = 0f
+ viewState.yTranslation = 10f
+ viewState.zTranslation = 20f
+ viewState.scaleX = 0.5f
+ viewState.scaleY = 0.25f
+
+ expectWtfs(0)
+
+ // Setting NaN values leads to wtfs being logged, and the value not being changed.
+ viewState.alpha = 0.0f / 0.0f
+ expectWtfs(1)
+ Assert.assertEquals(viewState.alpha, 0.1f)
+
+ viewState.xTranslation = Float.NaN
+ expectWtfs(2)
+ Assert.assertEquals(viewState.xTranslation, 0f)
+
+ viewState.yTranslation = log2(-10.0).toFloat()
+ expectWtfs(3)
+ Assert.assertEquals(viewState.yTranslation, 10f)
+
+ viewState.zTranslation = sqrt(-1.0).toFloat()
+ expectWtfs(4)
+ Assert.assertEquals(viewState.zTranslation, 20f)
+
+ viewState.scaleX = Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY
+ expectWtfs(5)
+ Assert.assertEquals(viewState.scaleX, 0.5f)
+
+ viewState.scaleY = Float.POSITIVE_INFINITY * 0
+ expectWtfs(6)
+ Assert.assertEquals(viewState.scaleY, 0.25f)
+ }
+
+ private fun interceptWtfs() {
+ wtfCount = 0
+ wtfHandler =
+ Log.setWtfHandler { _: String?, e: Log.TerribleFailure, _: Boolean ->
+ Log.e("ViewStateTest", "Observed WTF: $e")
+ wtfCount++
+ }
+ }
+
+ private fun expectWtfs(expectedWtfCount: Int) {
+ Assert.assertNotNull(wtfHandler)
+ Assert.assertEquals(expectedWtfCount, wtfCount)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index cd0cc33..6fa2174 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -100,8 +100,6 @@
@Mock
private AuthController mAuthController;
@Mock
- private DozeParameters mDozeParameters;
- @Mock
private MetricsLogger mMetricsLogger;
@Mock
private NotificationMediaManager mNotificationMediaManager;
@@ -127,7 +125,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
TestableResources res = getContext().getOrCreateTestableResources();
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
when(mKeyguardStateController.isUnlocked()).thenReturn(false);
@@ -139,7 +137,7 @@
mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController,
mKeyguardViewMediator, mScrimController, mShadeController,
mNotificationShadeWindowController, mKeyguardStateController, mHandler,
- mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
+ mUpdateMonitor, res.getResources(), mKeyguardBypassController,
mMetricsLogger, mDumpManager, mPowerManager,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
@@ -177,7 +175,7 @@
public void onBiometricAuthenticated_whenFingerprintAndNotInteractive_wakeAndUnlock() {
reset(mUpdateMonitor);
reset(mStatusBarKeyguardViewManager);
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mDozeScrimController.isPulsing()).thenReturn(true);
// the value of isStrongBiometric doesn't matter here since we only care about the returned
@@ -194,7 +192,7 @@
public void onBiometricAuthenticated_whenDeviceIsAlreadyUnlocked_wakeAndUnlock() {
reset(mUpdateMonitor);
reset(mStatusBarKeyguardViewManager);
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
when(mKeyguardStateController.isUnlocked()).thenReturn(true);
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mDozeScrimController.isPulsing()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f510e48..ad497a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -116,6 +116,7 @@
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -150,7 +151,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -413,7 +413,7 @@
mNotificationGutsManager,
notificationLogger,
mNotificationInterruptStateProvider,
- new PanelExpansionStateManager(),
+ new ShadeExpansionStateManager(),
mKeyguardViewMediator,
new DisplayMetrics(),
mMetricsLogger,
@@ -486,7 +486,7 @@
when(mKeyguardViewMediator.registerCentralSurfaces(
any(CentralSurfacesImpl.class),
any(NotificationPanelViewController.class),
- any(PanelExpansionStateManager.class),
+ any(ShadeExpansionStateManager.class),
any(BiometricUnlockController.class),
any(ViewGroup.class),
any(KeyguardBypassController.class)))
@@ -516,32 +516,32 @@
@Test
public void executeRunnableDismissingKeyguard_nullRunnable_showingAndOccluded() {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, false, false, false);
}
@Test
public void executeRunnableDismissingKeyguard_nullRunnable_showing() {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, false, false, false);
}
@Test
public void executeRunnableDismissingKeyguard_nullRunnable_notShowing() {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, false, false, false);
}
@Test
public void executeRunnableDismissingKeyguard_dreaming_notShowing() throws RemoteException {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true);
mCentralSurfaces.executeRunnableDismissingKeyguard(() -> {},
@@ -555,8 +555,8 @@
@Test
public void executeRunnableDismissingKeyguard_notDreaming_notShowing() throws RemoteException {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(false);
mCentralSurfaces.executeRunnableDismissingKeyguard(() -> {},
@@ -571,10 +571,10 @@
@Test
public void lockscreenStateMetrics_notShowing() {
// uninteresting state, except that fingerprint must be non-zero
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
// interesting state
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
when(mKeyguardStateController.isMethodSecure()).thenReturn(false);
mCentralSurfaces.onKeyguardViewManagerStatesUpdated();
@@ -589,10 +589,10 @@
@Test
public void lockscreenStateMetrics_notShowing_secure() {
// uninteresting state, except that fingerprint must be non-zero
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
// interesting state
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
@@ -608,10 +608,10 @@
@Test
public void lockscreenStateMetrics_isShowing() {
// uninteresting state, except that fingerprint must be non-zero
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
// interesting state
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
when(mKeyguardStateController.isMethodSecure()).thenReturn(false);
@@ -627,10 +627,10 @@
@Test
public void lockscreenStateMetrics_isShowing_secure() {
// uninteresting state, except that fingerprint must be non-zero
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
// interesting state
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
@@ -646,10 +646,10 @@
@Test
public void lockscreenStateMetrics_isShowingBouncer() {
// uninteresting state, except that fingerprint must be non-zero
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
// interesting state
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
@@ -1053,9 +1053,9 @@
}
@Test
- public void startActivityDismissingKeyguard_isShowingandIsOccluded() {
- when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(true);
+ public void startActivityDismissingKeyguard_isShowingAndIsOccluded() {
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
mCentralSurfaces.startActivityDismissingKeyguard(
new Intent(),
/* onlyProvisioned = */false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
new file mode 100644
index 0000000..a986777
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+/**
+ * Mock implementation of KeyguardStateController which tracks showing and occluded states
+ * based on {@link #notifyKeyguardState(boolean showing, boolean occluded)}}.
+ */
+public class FakeKeyguardStateController implements KeyguardStateController {
+ private boolean mShowing;
+ private boolean mOccluded;
+ private boolean mCanDismissLockScreen;
+
+ @Override
+ public void notifyKeyguardState(boolean showing, boolean occluded) {
+ mShowing = showing;
+ mOccluded = occluded;
+ }
+
+ @Override
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ @Override
+ public boolean isOccluded() {
+ return mOccluded;
+ }
+
+ public void setCanDismissLockScreen(boolean canDismissLockScreen) {
+ mCanDismissLockScreen = canDismissLockScreen;
+ }
+
+ @Override
+ public boolean canDismissLockScreen() {
+ return mCanDismissLockScreen;
+ }
+
+ @Override
+ public boolean isBouncerShowing() {
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardScreenRotationAllowed() {
+ return false;
+ }
+
+ @Override
+ public boolean isMethodSecure() {
+ return true;
+ }
+
+ @Override
+ public boolean isTrusted() {
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardGoingAway() {
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardFadingAway() {
+ return false;
+ }
+
+ @Override
+ public boolean isLaunchTransitionFadingAway() {
+ return false;
+ }
+
+ @Override
+ public long getKeyguardFadingAwayDuration() {
+ return 0;
+ }
+
+ @Override
+ public long getKeyguardFadingAwayDelay() {
+ return 0;
+ }
+
+ @Override
+ public long calculateGoingToFullShadeDelay() {
+ return 0;
+ }
+
+ @Override
+ public float getDismissAmount() {
+ return 0f;
+ }
+
+ @Override
+ public boolean isDismissingFromSwipe() {
+ return false;
+ }
+
+ @Override
+ public boolean isFlingingToDismissKeyguard() {
+ return false;
+ }
+
+ @Override
+ public boolean isFlingingToDismissKeyguardDuringSwipeGesture() {
+ return false;
+ }
+
+ @Override
+ public boolean isSnappingKeyguardBackAfterSwipe() {
+ return false;
+ }
+
+ @Override
+ public void notifyPanelFlingStart(boolean dismiss) {
+ }
+
+ @Override
+ public void notifyPanelFlingEnd() {
+ }
+
+ @Override
+ public void addCallback(Callback listener) {
+ }
+
+ @Override
+ public void removeCallback(Callback listener) {
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 34399b8..9c56c26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -44,6 +44,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -80,6 +81,7 @@
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
mock(WifiViewModel.class),
+ mock(MobileUiAdapter.class),
mMobileContextProvider,
mock(DarkIconDispatcher.class));
testCallOnAdd_forManager(manager);
@@ -123,12 +125,14 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
+ MobileUiAdapter mobileUiAdapter,
MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(group,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
contextProvider,
darkIconDispatcher);
}
@@ -169,6 +173,7 @@
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
mock(WifiViewModel.class),
+ mock(MobileUiAdapter.class),
contextProvider);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index f52bfca..8da8d04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
+
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -23,11 +25,10 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -35,6 +36,10 @@
import android.testing.TestableLooper;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
import androidx.test.filters.SmallTest;
@@ -57,14 +62,13 @@
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.google.common.truth.Truth;
@@ -73,6 +77,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -84,12 +89,11 @@
@TestableLooper.RunWithLooper
public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
- private static final PanelExpansionChangeEvent EXPANSION_EVENT =
+ private static final ShadeExpansionChangeEvent EXPANSION_EVENT =
expansionEvent(/* fraction= */ 0.5f, /* expanded= */ false, /* tracking= */ true);
@Mock private ViewMediatorCallback mViewMediatorCallback;
@Mock private LockPatternUtils mLockPatternUtils;
- @Mock private KeyguardStateController mKeyguardStateController;
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private ViewGroup mContainer;
@Mock private NotificationPanelViewController mNotificationPanelView;
@@ -118,6 +122,14 @@
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
+ private FakeKeyguardStateController mKeyguardStateController =
+ spy(new FakeKeyguardStateController());
+
+ @Mock private ViewRootImpl mViewRootImpl;
+ @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+ @Captor
+ private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+
@Before
public void setUp() {
@@ -154,15 +166,21 @@
mFeatureFlags,
mBouncerCallbackInteractor,
mBouncerInteractor,
- mBouncerView);
+ mBouncerView) {
+ @Override
+ public ViewRootImpl getViewRootImpl() {
+ return mViewRootImpl;
+ }
+ };
+ when(mViewRootImpl.getOnBackInvokedDispatcher())
+ .thenReturn(mOnBackInvokedDispatcher);
mStatusBarKeyguardViewManager.registerCentralSurfaces(
mCentralSurfaces,
mNotificationPanelView,
- new PanelExpansionStateManager(),
+ new ShadeExpansionStateManager(),
mBiometricUnlockController,
mNotificationContainer,
mBypassController);
- when(mKeyguardStateController.isOccluded()).thenReturn(false);
mStatusBarKeyguardViewManager.show(null);
ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback> callbackArgumentCaptor =
ArgumentCaptor.forClass(KeyguardBouncer.BouncerExpansionCallback.class);
@@ -235,7 +253,7 @@
@Test
public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+ mKeyguardStateController.setCanDismissLockScreen(false);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
verify(mBouncer).show(eq(false), eq(false));
@@ -322,13 +340,12 @@
}
@Test
- public void setOccluded_onKeyguardOccludedChangedCalledCorrectly() {
+ public void setOccluded_onKeyguardOccludedChangedCalled() {
clearInvocations(mKeyguardStateController);
clearInvocations(mKeyguardUpdateMonitor);
- // Should be false to start, so no invocations
mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
- verify(mKeyguardStateController, never()).notifyKeyguardState(anyBoolean(), anyBoolean());
+ verify(mKeyguardStateController).notifyKeyguardState(true, false);
clearInvocations(mKeyguardUpdateMonitor);
clearInvocations(mKeyguardStateController);
@@ -339,8 +356,8 @@
clearInvocations(mKeyguardUpdateMonitor);
clearInvocations(mKeyguardStateController);
- mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
- verify(mKeyguardStateController, never()).notifyKeyguardState(anyBoolean(), anyBoolean());
+ mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, false);
}
@Test
@@ -408,7 +425,7 @@
when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
assertTrue(
"Is showing not accurate when alternative auth showing",
- mStatusBarKeyguardViewManager.isShowing());
+ mStatusBarKeyguardViewManager.isBouncerShowing());
}
@Test
@@ -502,13 +519,44 @@
Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isFalse();
}
- private static PanelExpansionChangeEvent expansionEvent(
+ private static ShadeExpansionChangeEvent expansionEvent(
float fraction, boolean expanded, boolean tracking) {
- return new PanelExpansionChangeEvent(
+ return new ShadeExpansionChangeEvent(
fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
}
@Test
+ public void testPredictiveBackCallback_registration() {
+ /* verify that a predictive back callback is registered when the bouncer becomes visible */
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ mOnBackInvokedCallback.capture());
+
+ /* verify that the same callback is unregistered when the bouncer becomes invisible */
+ mBouncerExpansionCallback.onVisibilityChanged(false);
+ verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
+ eq(mOnBackInvokedCallback.getValue()));
+ }
+
+ @Test
+ public void testPredictiveBackCallback_invocationHidesBouncer() {
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ /* capture the predictive back callback during registration */
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ mOnBackInvokedCallback.capture());
+
+ when(mBouncer.isShowing()).thenReturn(true);
+ when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
+ /* invoke the back callback directly */
+ mOnBackInvokedCallback.getValue().onBackInvoked();
+
+ /* verify that the bouncer will be hidden as a result of the invocation */
+ verify(mCentralSurfaces).setBouncerShowing(eq(false));
+ }
+
+ @Test
public void testReportBouncerOnDreamWhenVisible() {
mBouncerExpansionCallback.onVisibilityChanged(true);
verify(mCentralSurfaces).setBouncerShowingOverDream(false);
@@ -528,25 +576,10 @@
verify(mCentralSurfaces).setBouncerShowingOverDream(false);
}
-
@Test
- public void testSetDozing_bouncerShowing_Dozing() {
- clearInvocations(mBouncer);
- when(mBouncer.isShowing()).thenReturn(true);
- doAnswer(invocation -> {
- when(mBouncer.isShowing()).thenReturn(false);
- return null;
- }).when(mBouncer).hide(false);
- mStatusBarKeyguardViewManager.onDozingChanged(true);
- verify(mBouncer, times(1)).hide(false);
- }
-
- @Test
- public void testSetDozing_bouncerShowing_notDozing() {
- mStatusBarKeyguardViewManager.onDozingChanged(true);
- when(mBouncer.isShowing()).thenReturn(true);
- clearInvocations(mBouncer);
- mStatusBarKeyguardViewManager.onDozingChanged(false);
- verify(mBouncer, times(1)).hide(false);
+ public void flag_off_DoesNotCallBouncerInteractor() {
+ when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
+ mStatusBarKeyguardViewManager.hideBouncer(false);
+ verify(mBouncerInteractor, never()).hide();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index a3c6e95..63467e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -54,6 +54,7 @@
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger;
import com.android.systemui.statusbar.OperatorNameViewController;
@@ -66,7 +67,6 @@
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -441,7 +441,7 @@
mAnimationScheduler,
mLocationPublisher,
mMockNotificationAreaController,
- new PanelExpansionStateManager(),
+ new ShadeExpansionStateManager(),
mock(FeatureFlags.class),
mStatusBarIconController,
mIconManagerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt
deleted file mode 100644
index c4f8049..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.panelstate
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-
-@SmallTest
-class PanelExpansionStateManagerTest : SysuiTestCase() {
-
- private lateinit var panelExpansionStateManager: PanelExpansionStateManager
-
- @Before
- fun setUp() {
- panelExpansionStateManager = PanelExpansionStateManager()
- }
-
- @Test
- fun onPanelExpansionChanged_listenerNotified() {
- val listener = TestPanelExpansionListener()
- panelExpansionStateManager.addExpansionListener(listener)
- val fraction = 0.6f
- val expanded = true
- val tracking = true
- val dragDownAmount = 1234f
-
- panelExpansionStateManager.onPanelExpansionChanged(
- fraction, expanded, tracking, dragDownAmount)
-
- assertThat(listener.fraction).isEqualTo(fraction)
- assertThat(listener.expanded).isEqualTo(expanded)
- assertThat(listener.tracking).isEqualTo(tracking)
- assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
- }
-
- @Test
- fun addExpansionListener_listenerNotifiedOfCurrentValues() {
- val fraction = 0.6f
- val expanded = true
- val tracking = true
- val dragDownAmount = 1234f
- panelExpansionStateManager.onPanelExpansionChanged(
- fraction, expanded, tracking, dragDownAmount)
- val listener = TestPanelExpansionListener()
-
- panelExpansionStateManager.addExpansionListener(listener)
-
- assertThat(listener.fraction).isEqualTo(fraction)
- assertThat(listener.expanded).isEqualTo(expanded)
- assertThat(listener.tracking).isEqualTo(tracking)
- assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
- }
-
- @Test
- fun updateState_listenerNotified() {
- val listener = TestPanelStateListener()
- panelExpansionStateManager.addStateListener(listener)
-
- panelExpansionStateManager.updateState(STATE_OPEN)
-
- assertThat(listener.state).isEqualTo(STATE_OPEN)
- }
-
- /* ***** [PanelExpansionStateManager.onPanelExpansionChanged] test cases *******/
-
- /* Fraction < 1 test cases */
-
- @Test
- fun onPEC_fractionLessThanOne_expandedTrue_trackingFalse_becomesStateOpening() {
- val listener = TestPanelStateListener()
- panelExpansionStateManager.addStateListener(listener)
-
- panelExpansionStateManager.onPanelExpansionChanged(
- fraction = 0.5f, expanded = true, tracking = false, dragDownPxAmount = 0f)
-
- assertThat(listener.state).isEqualTo(STATE_OPENING)
- }
-
- @Test
- fun onPEC_fractionLessThanOne_expandedTrue_trackingTrue_becomesStateOpening() {
- val listener = TestPanelStateListener()
- panelExpansionStateManager.addStateListener(listener)
-
- panelExpansionStateManager.onPanelExpansionChanged(
- fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 0f)
-
- assertThat(listener.state).isEqualTo(STATE_OPENING)
- }
-
- @Test
- fun onPEC_fractionLessThanOne_expandedFalse_trackingFalse_becomesStateClosed() {
- val listener = TestPanelStateListener()
- panelExpansionStateManager.addStateListener(listener)
- // Start out on a different state
- panelExpansionStateManager.updateState(STATE_OPEN)
-
- panelExpansionStateManager.onPanelExpansionChanged(
- fraction = 0.5f, expanded = false, tracking = false, dragDownPxAmount = 0f)
-
- assertThat(listener.state).isEqualTo(STATE_CLOSED)
- }
-
- @Test
- fun onPEC_fractionLessThanOne_expandedFalse_trackingTrue_doesNotBecomeStateClosed() {
- val listener = TestPanelStateListener()
- panelExpansionStateManager.addStateListener(listener)
- // Start out on a different state
- panelExpansionStateManager.updateState(STATE_OPEN)
-
- panelExpansionStateManager.onPanelExpansionChanged(
- fraction = 0.5f, expanded = false, tracking = true, dragDownPxAmount = 0f)
-
- assertThat(listener.state).isEqualTo(STATE_OPEN)
- }
-
- /* Fraction = 1 test cases */
-
- @Test
- fun onPEC_fractionOne_expandedTrue_trackingFalse_becomesStateOpeningThenStateOpen() {
- val listener = TestPanelStateListener()
- panelExpansionStateManager.addStateListener(listener)
-
- panelExpansionStateManager.onPanelExpansionChanged(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f)
-
- assertThat(listener.previousState).isEqualTo(STATE_OPENING)
- assertThat(listener.state).isEqualTo(STATE_OPEN)
- }
-
- @Test
- fun onPEC_fractionOne_expandedTrue_trackingTrue_becomesStateOpening() {
- val listener = TestPanelStateListener()
- panelExpansionStateManager.addStateListener(listener)
-
- panelExpansionStateManager.onPanelExpansionChanged(
- fraction = 1f, expanded = true, tracking = true, dragDownPxAmount = 0f)
-
- assertThat(listener.state).isEqualTo(STATE_OPENING)
- }
-
- @Test
- fun onPEC_fractionOne_expandedFalse_trackingFalse_becomesStateClosed() {
- val listener = TestPanelStateListener()
- panelExpansionStateManager.addStateListener(listener)
- // Start out on a different state
- panelExpansionStateManager.updateState(STATE_OPEN)
-
- panelExpansionStateManager.onPanelExpansionChanged(
- fraction = 1f, expanded = false, tracking = false, dragDownPxAmount = 0f)
-
- assertThat(listener.state).isEqualTo(STATE_CLOSED)
- }
-
- @Test
- fun onPEC_fractionOne_expandedFalse_trackingTrue_doesNotBecomeStateClosed() {
- val listener = TestPanelStateListener()
- panelExpansionStateManager.addStateListener(listener)
- // Start out on a different state
- panelExpansionStateManager.updateState(STATE_OPEN)
-
- panelExpansionStateManager.onPanelExpansionChanged(
- fraction = 1f, expanded = false, tracking = true, dragDownPxAmount = 0f)
-
- assertThat(listener.state).isEqualTo(STATE_OPEN)
- }
-
- /* ***** end [PanelExpansionStateManager.onPanelExpansionChanged] test cases ******/
-
- class TestPanelExpansionListener : PanelExpansionListener {
- var fraction: Float = 0f
- var expanded: Boolean = false
- var tracking: Boolean = false
- var dragDownAmountPx: Float = 0f
-
- override fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
- this.fraction = event.fraction
- this.expanded = event.expanded
- this.tracking = event.tracking
- this.dragDownAmountPx = event.dragDownPxAmount
- }
- }
-
- class TestPanelStateListener : PanelStateListener {
- @PanelState var previousState: Int = STATE_CLOSED
- @PanelState var state: Int = STATE_CLOSED
-
- override fun onPanelStateChanged(state: Int) {
- this.previousState = this.state
- this.state = state
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
new file mode 100644
index 0000000..0d15268
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
+ private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
+ override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
+
+ private val _activeMobileDataSubscriptionId =
+ MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
+
+ private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
+ override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
+ return subIdFlows[subId]
+ ?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
+ }
+
+ fun setSubscriptions(subs: List<SubscriptionInfo>) {
+ _subscriptionsFlow.value = subs
+ }
+
+ fun setActiveMobileDataSubscriptionId(subId: Int) {
+ _activeMobileDataSubscriptionId.value = subId
+ }
+
+ fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
+ val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
+ subscription.value = model
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
new file mode 100644
index 0000000..6c495c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -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.statusbar.pipeline.mobile.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Defaults to `true` */
+class FakeUserSetupRepository : UserSetupRepository {
+ private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+
+ fun setUserSetup(setup: Boolean) {
+ _isUserSetup.value = setup
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
new file mode 100644
index 0000000..316b795
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileSubscriptionRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: MobileSubscriptionRepositoryImpl
+
+ @Mock private lateinit var subscriptionManager: SubscriptionManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ MobileSubscriptionRepositoryImpl(
+ subscriptionManager,
+ telephonyManager,
+ IMMEDIATE,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_initiallyEmpty() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+ }
+
+ @Test
+ fun testSubscriptions_listUpdates() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_removingSub_updatesList() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ // WHEN 2 networks show up
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN one network is removed
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // THEN the subscriptions list represents the newest change
+ assertThat(latest).isEqualTo(listOf(SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.activeMobileDataSubscriptionId.value)
+ .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_updates() =
+ runBlocking(IMMEDIATE) {
+ var active: Int? = null
+
+ val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+
+ getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(active).isEqualTo(SUB_2_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_default() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(MobileSubscriptionModel())
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly_toggles() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<ServiceStateListener>()
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+ callback.onServiceStateChanged(serviceState)
+ serviceState.isEmergencyOnly = false
+ callback.onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_signalStrengths_levelsUpdate() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
+ val strength = signalStrength(1, 2, true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest?.isGsm).isEqualTo(true)
+ assertThat(latest?.primaryLevel).isEqualTo(1)
+ assertThat(latest?.cdmaLevel).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(100, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(100)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataActivity() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DataActivityListener>()
+ callback.onDataActivity(3)
+
+ assertThat(latest?.dataActivityDirection).isEqualTo(3)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_carrierNetworkChange() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
+ callback.onCarrierNetworkChange(true)
+
+ assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_displayInfo() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DisplayInfoListener>()
+ val ti = mock<TelephonyDisplayInfo>()
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest?.displayInfo).isEqualTo(ti)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_isCached() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ val state1 = underTest.getFlowForSubId(SUB_1_ID)
+ val state2 = underTest.getFlowForSubId(SUB_1_ID)
+
+ assertThat(state1).isEqualTo(state2)
+ }
+
+ @Test
+ fun testFlowForSubId_isRemovedAfterFinish() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+
+ // Start collecting on some flow
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ // There should be once cached flow now
+ assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
+
+ // When the job is canceled, the cache should be cleared
+ job.cancel()
+
+ assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
+ }
+
+ private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+ val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ verify(subscriptionManager)
+ .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
+ private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
+ getTelephonyCallbackForType()
+
+ private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ private inline fun <reified T> getTelephonyCallbackForType(): T {
+ val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+
+ /** Convenience constructor for SignalStrength */
+ private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.isGsm).thenReturn(isGsm)
+ whenever(signalStrength.level).thenReturn(gsmLevel)
+ val cdmaStrength =
+ mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+ whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+ .thenReturn(listOf(cdmaStrength))
+
+ return signalStrength
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
new file mode 100644
index 0000000..91c233a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class UserSetupRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: UserSetupRepository
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ UserSetupRepositoryImpl(
+ deviceProvisionedController,
+ IMMEDIATE,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testUserSetup_defaultFalse() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+
+ val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun testUserSetup_updatesOnChange() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+
+ val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+ val callback = getDeviceProvisionedListener()
+ callback.onUserSetupChanged()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
+ val captor = argumentCaptor<DeviceProvisionedListener>()
+ verify(deviceProvisionedController).addCallback(captor.capture())
+ return captor.value!!
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
new file mode 100644
index 0000000..8ec68f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CellSignalStrength
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconInteractor : MobileIconInteractor {
+ private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
+ override val iconGroup = _iconGroup
+
+ private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
+ override val isEmergencyOnly = _isEmergencyOnly
+
+ private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val level = _level
+
+ private val _numberOfLevels = MutableStateFlow<Int>(4)
+ override val numberOfLevels = _numberOfLevels
+
+ private val _cutOut = MutableStateFlow<Boolean>(false)
+ override val cutOut = _cutOut
+
+ fun setIconGroup(group: SignalIcon.MobileIconGroup) {
+ _iconGroup.value = group
+ }
+
+ fun setIsEmergencyOnly(emergency: Boolean) {
+ _isEmergencyOnly.value = emergency
+ }
+
+ fun setLevel(level: Int) {
+ _level.value = level
+ }
+
+ fun setNumberOfLevels(num: Int) {
+ _numberOfLevels.value = num
+ }
+
+ fun setCutOut(cutOut: Boolean) {
+ _cutOut.value = cutOut
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
new file mode 100644
index 0000000..2f07d9c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CellSignalStrength
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class MobileIconInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconInteractor
+ private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
+ private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
+
+ @Before
+ fun setUp() {
+ underTest = MobileIconInteractorImpl(sub1Flow)
+ }
+
+ @Test
+ fun gsm_level_default_unknown() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(isGsm = true),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ job.cancel()
+ }
+
+ @Test
+ fun gsm_usesGsmLevel() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ isGsm = true,
+ primaryLevel = GSM_LEVEL,
+ cdmaLevel = CDMA_LEVEL
+ ),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(GSM_LEVEL)
+
+ job.cancel()
+ }
+
+ @Test
+ fun cdma_level_default_unknown() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(isGsm = false),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ job.cancel()
+ }
+
+ @Test
+ fun cdma_usesCdmaLevel() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ isGsm = false,
+ primaryLevel = GSM_LEVEL,
+ cdmaLevel = CDMA_LEVEL
+ ),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CDMA_LEVEL)
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+
+ private const val GSM_LEVEL = 1
+ private const val CDMA_LEVEL = 2
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
new file mode 100644
index 0000000..89ad9cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconsInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconsInteractor
+ private val userSetupRepository = FakeUserSetupRepository()
+ private val subscriptionsRepository = FakeMobileSubscriptionRepository()
+
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ MobileIconsInteractor(
+ subscriptionsRepository,
+ carrierConfigTracker,
+ userSetupRepository,
+ )
+ }
+
+ @After fun tearDown() {}
+
+ @Test
+ fun filteredSubscriptions_default() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf<SubscriptionInfo>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(SUB_3_OPP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(SUB_4_OPP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(SUB_1))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(SUB_1))
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+ private const val SUB_3_ID = 3
+ private val SUB_3_OPP =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_3_ID)
+ whenever(it.isOpportunistic).thenReturn(true)
+ }
+
+ private const val SUB_4_ID = 4
+ private val SUB_4_OPP =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_4_ID)
+ whenever(it.isOpportunistic).thenReturn(true)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
new file mode 100644
index 0000000..b374abb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.graph.SignalDrawable
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconViewModel
+ private val interactor = FakeMobileIconInteractor()
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ interactor.apply {
+ setLevel(1)
+ setCutOut(false)
+ setIconGroup(TelephonyIcons.THREE_G)
+ setIsEmergencyOnly(false)
+ setNumberOfLevels(4)
+ }
+ underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
+ }
+
+ @Test
+ fun iconId_correctLevel_notCutout() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 929e529..a3ad028 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -23,9 +23,9 @@
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.statusbar.connectivity.WifiIcons
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
@@ -144,7 +144,12 @@
/** A function that, given a context, calculates the correct content description string. */
val contentDescription: (Context) -> String,
- )
+
+ /** A human-readable description used for the test names. */
+ val description: String,
+ ) {
+ override fun toString() = description
+ }
// Note: We use default values for the boolean parameters to reflect a "typical configuration"
// for wifi. This allows each TestCase to only define the parameter values that are critical
@@ -158,12 +163,21 @@
/** The expected output. Null if we expect the output to be null. */
val expected: Expected?
- )
+ ) {
+ override fun toString(): String {
+ return "when INPUT(enabled=$enabled, " +
+ "forceHidden=$forceHidden, " +
+ "showWhenEnabled=$alwaysShowIconWhenEnabled, " +
+ "hasDataCaps=$hasDataCapabilities, " +
+ "network=$network) then " +
+ "EXPECTED($expected)"
+ }
+ }
companion object {
- @Parameters(name = "{0}")
- @JvmStatic
- fun data(): Collection<TestCase> =
+ @Parameters(name = "{0}") @JvmStatic fun data(): Collection<TestCase> = testData
+
+ private val testData: List<TestCase> =
listOf(
// Enabled = false => no networks shown
TestCase(
@@ -215,11 +229,12 @@
network = WifiNetworkModel.Inactive,
expected =
Expected(
- iconResource = WifiIcons.WIFI_NO_NETWORK,
+ iconResource = WIFI_NO_NETWORK,
contentDescription = { context ->
"${context.getString(WIFI_NO_CONNECTION)}," +
context.getString(NO_INTERNET)
- }
+ },
+ description = "No network icon",
),
),
TestCase(
@@ -231,7 +246,8 @@
contentDescription = { context ->
"${context.getString(WIFI_CONNECTION_STRENGTH[4])}," +
context.getString(NO_INTERNET)
- }
+ },
+ description = "No internet level 4 icon",
),
),
TestCase(
@@ -242,7 +258,8 @@
iconResource = WIFI_FULL_ICONS[2],
contentDescription = { context ->
context.getString(WIFI_CONNECTION_STRENGTH[2])
- }
+ },
+ description = "Full internet level 2 icon",
),
),
@@ -252,11 +269,12 @@
network = WifiNetworkModel.Inactive,
expected =
Expected(
- iconResource = WifiIcons.WIFI_NO_NETWORK,
+ iconResource = WIFI_NO_NETWORK,
contentDescription = { context ->
"${context.getString(WIFI_NO_CONNECTION)}," +
context.getString(NO_INTERNET)
- }
+ },
+ description = "No network icon",
),
),
TestCase(
@@ -268,7 +286,8 @@
contentDescription = { context ->
"${context.getString(WIFI_CONNECTION_STRENGTH[2])}," +
context.getString(NO_INTERNET)
- }
+ },
+ description = "No internet level 2 icon",
),
),
TestCase(
@@ -279,7 +298,8 @@
iconResource = WIFI_FULL_ICONS[0],
contentDescription = { context ->
context.getString(WIFI_CONNECTION_STRENGTH[0])
- }
+ },
+ description = "Full internet level 0 icon",
),
),
@@ -309,7 +329,8 @@
iconResource = WIFI_FULL_ICONS[4],
contentDescription = { context ->
context.getString(WIFI_CONNECTION_STRENGTH[4])
- }
+ },
+ description = "Full internet level 4 icon",
),
),
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 76ecc1c..169f4fb 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.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 @@
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 @@
}
@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 @@
bgExecutor.runAllReady()
verify(userManager).createGuest(context)
}
+
+ @Test
+ fun onUserItemClicked_manageUsers() {
+ val manageUserRecord = LegacyUserDataHelper.createRecord(
+ mContext,
+ ownerId,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ isRestricted = false,
+ isSwitchToEnabled = true
+ )
+
+ userSwitcherController.onUserListItemClicked(manageUserRecord, null)
+ val intentCaptor = kotlinArgumentCaptor<Intent>()
+ verify(activityStarter).startActivity(intentCaptor.capture(),
+ eq(true)
+ )
+ Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 921b7ef..b68eb88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -17,7 +17,9 @@
package com.android.systemui.temporarydisplay
import android.content.Context
+import android.graphics.Rect
import android.os.PowerManager
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
@@ -229,17 +231,23 @@
accessibilityManager,
configurationController,
powerManager,
- R.layout.media_ttt_chip,
+ R.layout.chipbar,
"Window Title",
"WAKE_REASON",
) {
var mostRecentViewInfo: ViewInfo? = null
override val windowLayoutParams = commonWindowLayoutParams
+
+ override fun start() {}
+
override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) {
- super.updateView(newInfo, currentView)
mostRecentViewInfo = newInfo
}
+
+ override fun getTouchableRegion(view: View, outRect: Rect) {
+ outRect.setEmpty()
+ }
}
inner class ViewInfo(val name: String) : TemporaryViewInfo {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt
new file mode 100644
index 0000000..7586fe4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt
@@ -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.temporarydisplay
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class TouchableRegionViewControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var view: View
+ @Mock private lateinit var viewTreeObserver: ViewTreeObserver
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+ }
+
+ @Test
+ fun viewAttached_listenerAdded() {
+ val controller = TouchableRegionViewController(view) { _, _ -> }
+
+ controller.onViewAttached()
+
+ verify(viewTreeObserver).addOnComputeInternalInsetsListener(any())
+ }
+
+ @Test
+ fun viewDetached_listenerRemoved() {
+ val controller = TouchableRegionViewController(view) { _, _ -> }
+
+ controller.onViewDetached()
+
+ verify(viewTreeObserver).removeOnComputeInternalInsetsListener(any())
+ }
+
+ @Test
+ fun listener_usesPassedInFunction() {
+ val controller =
+ TouchableRegionViewController(view) { _, outRect -> outRect.set(1, 2, 3, 4) }
+
+ controller.onViewAttached()
+
+ val captor =
+ ArgumentCaptor.forClass(ViewTreeObserver.OnComputeInternalInsetsListener::class.java)
+ verify(viewTreeObserver).addOnComputeInternalInsetsListener(captor.capture())
+ val listener = captor.value!!
+
+ val inoutInfo = ViewTreeObserver.InternalInsetsInfo()
+ listener.onComputeInternalInsets(inoutInfo)
+
+ assertThat(inoutInfo.touchableRegion.bounds).isEqualTo(Rect(1, 2, 3, 4))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
new file mode 100644
index 0000000..6225d0c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay.chipbar
+
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.media.MediaRoute2Info
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.sender.ChipStateSender
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderUiEventLogger
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderUiEvents
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ChipbarCoordinatorTest : SysuiTestCase() {
+ private lateinit var underTest: FakeChipbarCoordinator
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+ @Mock
+ private lateinit var applicationInfo: ApplicationInfo
+ @Mock
+ private lateinit var logger: MediaTttLogger
+ @Mock
+ private lateinit var accessibilityManager: AccessibilityManager
+ @Mock
+ private lateinit var configurationController: ConfigurationController
+ @Mock
+ private lateinit var powerManager: PowerManager
+ @Mock
+ private lateinit var windowManager: WindowManager
+ @Mock
+ private lateinit var falsingManager: FalsingManager
+ @Mock
+ private lateinit var falsingCollector: FalsingCollector
+ @Mock
+ private lateinit var viewUtil: ViewUtil
+ private lateinit var fakeAppIconDrawable: Drawable
+ private lateinit var fakeClock: FakeSystemClock
+ private lateinit var fakeExecutor: FakeExecutor
+ private lateinit var uiEventLoggerFake: UiEventLoggerFake
+ private lateinit var senderUiEventLogger: MediaTttSenderUiEventLogger
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
+ whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
+ whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
+ whenever(packageManager.getApplicationInfo(
+ eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>()
+ )).thenReturn(applicationInfo)
+ context.setMockPackageManager(packageManager)
+
+ fakeClock = FakeSystemClock()
+ fakeExecutor = FakeExecutor(fakeClock)
+
+ uiEventLoggerFake = UiEventLoggerFake()
+ senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
+
+ whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+
+ underTest = FakeChipbarCoordinator(
+ context,
+ logger,
+ windowManager,
+ fakeExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ senderUiEventLogger,
+ falsingManager,
+ falsingCollector,
+ viewUtil,
+ )
+ underTest.start()
+ }
+
+ @Test
+ fun almostCloseToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
+ val state = almostCloseToStartCast()
+ underTest.displayView(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipView.getChipText()).isEqualTo(
+ state.state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun almostCloseToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
+ val state = almostCloseToEndCast()
+ underTest.displayView(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipView.getChipText()).isEqualTo(
+ state.state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToReceiverTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
+ val state = transferToReceiverTriggered()
+ underTest.displayView(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipView.getChipText()).isEqualTo(
+ state.state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToThisDeviceTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
+ val state = transferToThisDeviceTriggered()
+ underTest.displayView(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipView.getChipText()).isEqualTo(
+ state.state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
+ val state = transferToReceiverSucceeded()
+ underTest.displayView(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipView.getChipText()).isEqualTo(
+ state.state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() {
+ underTest.displayView(transferToReceiverSucceeded(undoCallback = null))
+
+ val chipView = getChipView()
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() {
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ underTest.displayView(transferToReceiverSucceeded(undoCallback))
+
+ val chipView = getChipView()
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
+ var undoCallbackCalled = false
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ }
+
+ underTest.displayView(transferToReceiverSucceeded(undoCallback))
+ getChipView().getUndoButton().performClick()
+
+ assertThat(undoCallbackCalled).isTrue()
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
+ var undoCallbackCalled = false
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ }
+
+ underTest.displayView(transferToReceiverSucceeded(undoCallback))
+ getChipView().getUndoButton().performClick()
+
+ assertThat(undoCallbackCalled).isFalse()
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
+ var undoCallbackCalled = false
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ }
+
+ underTest.displayView(transferToReceiverSucceeded(undoCallback))
+ getChipView().getUndoButton().performClick()
+
+ assertThat(undoCallbackCalled).isTrue()
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ underTest.displayView(transferToReceiverSucceeded(undoCallback))
+
+ getChipView().getUndoButton().performClick()
+
+ assertThat(getChipView().getChipText()).isEqualTo(
+ transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
+ MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id
+ )
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
+ val state = transferToThisDeviceSucceeded()
+ underTest.displayView(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipView.getChipText()).isEqualTo(
+ state.state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() {
+ underTest.displayView(transferToThisDeviceSucceeded(undoCallback = null))
+
+ val chipView = getChipView()
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() {
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ underTest.displayView(transferToThisDeviceSucceeded(undoCallback))
+
+ val chipView = getChipView()
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
+ var undoCallbackCalled = false
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ }
+
+ underTest.displayView(transferToThisDeviceSucceeded(undoCallback))
+ getChipView().getUndoButton().performClick()
+
+ assertThat(undoCallbackCalled).isTrue()
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() {
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ underTest.displayView(transferToThisDeviceSucceeded(undoCallback))
+
+ getChipView().getUndoButton().performClick()
+
+ assertThat(getChipView().getChipText()).isEqualTo(
+ transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
+ MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
+ )
+ }
+
+ @Test
+ fun transferToReceiverFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
+ val state = transferToReceiverFailed()
+ underTest.displayView(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(getChipView().getChipText()).isEqualTo(
+ state.state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun transferToThisDeviceFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
+ val state = transferToThisDeviceFailed()
+ underTest.displayView(state)
+
+ val chipView = getChipView()
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(getChipView().getChipText()).isEqualTo(
+ state.state.getChipTextString(context, OTHER_DEVICE_NAME)
+ )
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun changeFromAlmostCloseToStartToTransferTriggered_loadingIconAppears() {
+ underTest.displayView(almostCloseToStartCast())
+ underTest.displayView(transferToReceiverTriggered())
+
+ assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() {
+ underTest.displayView(transferToReceiverTriggered())
+ underTest.displayView(transferToReceiverSucceeded())
+
+ assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() {
+ underTest.displayView(transferToReceiverTriggered())
+ underTest.displayView(
+ transferToReceiverSucceeded(
+ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ }
+ )
+ )
+
+ assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun changeFromTransferSucceededToAlmostCloseToStart_undoButtonDisappears() {
+ underTest.displayView(transferToReceiverSucceeded())
+ underTest.displayView(almostCloseToStartCast())
+
+ assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun changeFromTransferTriggeredToTransferFailed_failureIconAppears() {
+ underTest.displayView(transferToReceiverTriggered())
+ underTest.displayView(transferToReceiverFailed())
+
+ assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE)
+ }
+
+ private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
+
+ private fun ViewGroup.getChipText(): String =
+ (this.requireViewById<TextView>(R.id.text)).text as String
+
+ private fun ViewGroup.getLoadingIconVisibility(): Int =
+ this.requireViewById<View>(R.id.loading).visibility
+
+ private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.undo)
+
+ private fun ViewGroup.getFailureIcon(): View = this.requireViewById(R.id.failure_icon)
+
+ private fun getChipView(): ViewGroup {
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ return viewCaptor.value as ViewGroup
+ }
+
+ // TODO(b/245610654): For now, the below methods are duplicated between this test and
+ // [MediaTttSenderCoordinatorTest]. Once we define a generic API for [ChipbarCoordinator],
+ // these will no longer be duplicated.
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun almostCloseToStartCast() =
+ ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST, routeInfo)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun almostCloseToEndCast() =
+ ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST, routeInfo)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToReceiverTriggered() =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToThisDeviceTriggered() =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, undoCallback)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, undoCallback)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToReceiverFailed() =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferToThisDeviceFailed() =
+ ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
+}
+
+private const val APP_NAME = "Fake app name"
+private const val OTHER_DEVICE_NAME = "My Tablet"
+private const val PACKAGE_NAME = "com.android.systemui"
+private const val TIMEOUT = 10000
+
+private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
+ .addFeature("feature")
+ .setClientPackageName(PACKAGE_NAME)
+ .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
new file mode 100644
index 0000000..10704ac
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay.chipbar
+
+import android.content.Context
+import android.os.PowerManager
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderUiEventLogger
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
+
+/** A fake implementation of [ChipbarCoordinator] for testing. */
+class FakeChipbarCoordinator(
+ context: Context,
+ @MediaTttReceiverLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ uiEventLogger: MediaTttSenderUiEventLogger,
+ falsingManager: FalsingManager,
+ falsingCollector: FalsingCollector,
+ viewUtil: ViewUtil,
+) :
+ ChipbarCoordinator(
+ context,
+ logger,
+ windowManager,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ uiEventLogger,
+ falsingManager,
+ falsingCollector,
+ viewUtil,
+ ) {
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ // Just bypass the animation in tests
+ onAnimationEnd.run()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 50259b5..2a93fff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -885,4 +885,31 @@
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
.isEqualTo(new OverlayIdentifier("ff00ff00"));
}
+
+ // Regression test for b/234603929, where a reboot would generate a wallpaper colors changed
+ // event for the already-set colors that would then set the theme incorrectly on screen sleep.
+ @Test
+ public void onWallpaperColorsSetToSame_keepsTheme() {
+ // Set initial colors and verify.
+ WallpaperColors startingColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+ WallpaperColors sameColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+ mColorsListener.getValue().onColorsChanged(startingColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ clearInvocations(mThemeOverlayApplier);
+
+ // Set to the same colors.
+ mColorsListener.getValue().onColorsChanged(sameColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+ verify(mThemeOverlayApplier, never())
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+
+ // Verify that no change resulted.
+ mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep();
+ verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
+ any());
+ }
}
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 7e07040..e18dd3a 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.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 @@
@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 @@
screenOnStatusProvider,
foldProvider,
activityTypeProvider,
+ rotationChangeProvider,
context.mainExecutor,
handler
)
@@ -92,6 +104,8 @@
})
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 @@
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 0000000..85cfef7
--- /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 b2cedbf..a25469b 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 @@
@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
+
+ @Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>
lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
- private val rotationWatcherCaptor =
- ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java)
-
@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 @@
}
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 3968bb7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.user
-
-import android.app.Application
-import android.os.UserManager
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.view.LayoutInflater
-import android.view.View
-import android.view.Window
-import android.window.OnBackInvokedCallback
-import android.window.OnBackInvokedDispatcher
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
-class UserSwitcherActivityTest : SysuiTestCase() {
- @Mock
- private lateinit var activity: UserSwitcherActivity
- @Mock
- private lateinit var userSwitcherController: UserSwitcherController
- @Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock
- private lateinit var layoutInflater: LayoutInflater
- @Mock
- private lateinit var falsingCollector: FalsingCollector
- @Mock
- private lateinit var falsingManager: FalsingManager
- @Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var flags: FeatureFlags
- @Mock
- private lateinit var viewModelFactoryLazy: dagger.Lazy<UserSwitcherViewModel.Factory>
- @Mock
- private lateinit var onBackDispatcher: OnBackInvokedDispatcher
- @Mock
- private lateinit var decorView: View
- @Mock
- private lateinit var window: Window
- @Mock
- private lateinit var userSwitcherRootView: UserSwitcherRootView
- @Captor
- private lateinit var onBackInvokedCallback: ArgumentCaptor<OnBackInvokedCallback>
- var isFinished = false
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- activity = spy(object : UserSwitcherActivity(
- userSwitcherController,
- broadcastDispatcher,
- falsingCollector,
- falsingManager,
- userManager,
- userTracker,
- flags,
- viewModelFactoryLazy,
- ) {
- override fun getOnBackInvokedDispatcher() = onBackDispatcher
- override fun getMainExecutor(): Executor = FakeExecutor(FakeSystemClock())
- override fun finish() {
- isFinished = true
- }
- })
- `when`(activity.window).thenReturn(window)
- `when`(window.decorView).thenReturn(decorView)
- `when`(activity.findViewById<UserSwitcherRootView>(R.id.user_switcher_root))
- .thenReturn(userSwitcherRootView)
- `when`(activity.findViewById<View>(R.id.cancel)).thenReturn(mock(View::class.java))
- `when`(activity.findViewById<View>(R.id.add)).thenReturn(mock(View::class.java))
- `when`(activity.application).thenReturn(mock(Application::class.java))
- doNothing().`when`(activity).setContentView(anyInt())
- }
-
- @Test
- fun testMaxColumns() {
- assertThat(activity.getMaxColumns(3)).isEqualTo(4)
- assertThat(activity.getMaxColumns(4)).isEqualTo(4)
- assertThat(activity.getMaxColumns(5)).isEqualTo(3)
- assertThat(activity.getMaxColumns(6)).isEqualTo(3)
- assertThat(activity.getMaxColumns(7)).isEqualTo(4)
- assertThat(activity.getMaxColumns(9)).isEqualTo(5)
- }
-
- @Test
- fun onCreate_callbackRegistration() {
- activity.createActivity()
- verify(onBackDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any())
-
- activity.destroyActivity()
- verify(onBackDispatcher).unregisterOnBackInvokedCallback(any())
- }
-
- @Test
- fun onBackInvokedCallback_finishesActivity() {
- activity.createActivity()
- verify(onBackDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), onBackInvokedCallback.capture())
-
- onBackInvokedCallback.value.onBackInvoked()
- assertThat(isFinished).isTrue()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
index 4a8e055..d951f36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
@@ -120,6 +120,27 @@
assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
}
+ @Test
+ fun `refreshUsers - sorts by creation time`() = runSelfCancelingTest {
+ underTest = create(this)
+ val unsortedUsers =
+ setUpUsers(
+ count = 3,
+ selectedIndex = 0,
+ )
+ unsortedUsers[0].creationTime = 900
+ unsortedUsers[1].creationTime = 700
+ unsortedUsers[2].creationTime = 999
+ val expectedUsers = listOf(unsortedUsers[1], unsortedUsers[0], unsortedUsers[2])
+ var userInfos: List<UserInfo>? = null
+ var selectedUserInfo: UserInfo? = null
+ underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+ underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(expectedUsers)
+ }
+
private fun setUpUsers(
count: Int,
hasGuest: Boolean = false,
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 3d5695a..1540f85 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
@@ -47,7 +47,9 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@SmallTest
@@ -73,6 +75,66 @@
}
@Test
+ fun `onRecordSelected - user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+
+ verify(dialogShower).dismiss()
+ verify(activityManager).switchUser(userInfos[1].id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - switch to guest user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+ verify(activityManager).switchUser(userInfos.last().id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - enter guest mode`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+ whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+
+ underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+
+ verify(dialogShower).dismiss()
+ verify(manager).createGuest(any())
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - action`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
+
+ verify(dialogShower, never()).dismiss()
+ verify(activityStarter).startActivity(any(), anyBoolean())
+ }
+
+ @Test
fun `users - switcher enabled`() =
runBlocking(IMMEDIATE) {
val userInfos = createUserInfos(count = 3, includeGuest = true)
@@ -140,6 +202,7 @@
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))
@@ -153,6 +216,7 @@
UserActionModel.ENTER_GUEST_MODE,
UserActionModel.ADD_USER,
UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
@@ -214,6 +278,7 @@
UserActionModel.ENTER_GUEST_MODE,
UserActionModel.ADD_USER,
UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
@@ -221,7 +286,7 @@
}
@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)
@@ -231,7 +296,13 @@
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()
}
@@ -268,7 +339,7 @@
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)
@@ -280,7 +351,7 @@
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)
}
@@ -336,10 +407,14 @@
var dialogRequest: ShowDialogRequestModel? = null
val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
- underTest.selectUser(newlySelectedUserId = guestUserInfo.id)
+ underTest.selectUser(
+ newlySelectedUserId = guestUserInfo.id,
+ dialogShower = dialogShower,
+ )
assertThat(dialogRequest)
.isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+ verify(dialogShower, never()).dismiss()
job.cancel()
}
@@ -355,10 +430,11 @@
var dialogRequest: ShowDialogRequestModel? = null
val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
- underTest.selectUser(newlySelectedUserId = userInfos[0].id)
+ underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
assertThat(dialogRequest)
.isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+ verify(dialogShower, never()).dismiss()
job.cancel()
}
@@ -372,10 +448,11 @@
var dialogRequest: ShowDialogRequestModel? = null
val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
- underTest.selectUser(newlySelectedUserId = userInfos[1].id)
+ underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
assertThat(dialogRequest).isNull()
verify(activityManager).switchUser(userInfos[1].id)
+ verify(dialogShower).dismiss()
job.cancel()
}
@@ -493,6 +570,7 @@
UserActionModel.ENTER_GUEST_MODE,
UserActionModel.ADD_USER,
UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
),
)
}
@@ -637,7 +715,7 @@
name,
/* iconPath= */ "",
/* flags= */ if (isPrimary) {
- UserInfo.FLAG_PRIMARY
+ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
} else {
0
},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 8465f4f..1680c36c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
@@ -46,6 +47,7 @@
@Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock protected lateinit var devicePolicyManager: DevicePolicyManager
@Mock protected lateinit var uiEventLogger: UiEventLogger
+ @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
protected lateinit var underTest: UserInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index 125b362..17d81c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -73,10 +73,16 @@
.addConditions(mConditions);
}
+ private Condition createMockCondition() {
+ final Condition condition = Mockito.mock(Condition.class);
+ when(condition.isConditionSet()).thenReturn(true);
+ return condition;
+ }
+
@Test
public void testOverridingCondition() {
- final Condition overridingCondition = Mockito.mock(Condition.class);
- final Condition regularCondition = Mockito.mock(Condition.class);
+ final Condition overridingCondition = createMockCondition();
+ final Condition regularCondition = createMockCondition();
final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
final Monitor.Callback referenceCallback = Mockito.mock(Monitor.Callback.class);
@@ -127,9 +133,9 @@
*/
@Test
public void testMultipleOverridingConditions() {
- final Condition overridingCondition = Mockito.mock(Condition.class);
- final Condition overridingCondition2 = Mockito.mock(Condition.class);
- final Condition regularCondition = Mockito.mock(Condition.class);
+ final Condition overridingCondition = createMockCondition();
+ final Condition overridingCondition2 = createMockCondition();
+ final Condition regularCondition = createMockCondition();
final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
final Monitor monitor = new Monitor(mExecutor);
@@ -340,4 +346,114 @@
mExecutor.runAllReady();
verify(callback).onConditionsChanged(true);
}
+
+ @Test
+ public void clearCondition_shouldUpdateValue() {
+ mCondition1.fakeUpdateCondition(false);
+ mCondition2.fakeUpdateCondition(true);
+ mCondition3.fakeUpdateCondition(true);
+
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(false);
+
+ mCondition1.clearCondition();
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(true);
+ }
+
+ @Test
+ public void unsetCondition_shouldNotAffectValue() {
+ final FakeCondition settableCondition = new FakeCondition(null, false);
+ mCondition1.fakeUpdateCondition(true);
+ mCondition2.fakeUpdateCondition(true);
+ mCondition3.fakeUpdateCondition(true);
+
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback)
+ .addCondition(settableCondition)
+ .build());
+
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(true);
+ }
+
+ @Test
+ public void setUnsetCondition_shouldAffectValue() {
+ final FakeCondition settableCondition = new FakeCondition(null, false);
+ mCondition1.fakeUpdateCondition(true);
+ mCondition2.fakeUpdateCondition(true);
+ mCondition3.fakeUpdateCondition(true);
+
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback)
+ .addCondition(settableCondition)
+ .build());
+
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(true);
+ clearInvocations(callback);
+
+ settableCondition.fakeUpdateCondition(false);
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(false);
+ clearInvocations(callback);
+
+
+ settableCondition.clearCondition();
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(true);
+ }
+
+ @Test
+ public void clearingOverridingCondition_shouldBeExcluded() {
+ final FakeCondition overridingCondition = new FakeCondition(true, true);
+ mCondition1.fakeUpdateCondition(false);
+ mCondition2.fakeUpdateCondition(false);
+ mCondition3.fakeUpdateCondition(false);
+
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback)
+ .addCondition(overridingCondition)
+ .build());
+
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(true);
+ clearInvocations(callback);
+
+ overridingCondition.clearCondition();
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(false);
+ }
+
+ @Test
+ public void settingUnsetOverridingCondition_shouldBeIncluded() {
+ final FakeCondition overridingCondition = new FakeCondition(null, true);
+ mCondition1.fakeUpdateCondition(false);
+ mCondition2.fakeUpdateCondition(false);
+ mCondition3.fakeUpdateCondition(false);
+
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback)
+ .addCondition(overridingCondition)
+ .build());
+
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(false);
+ clearInvocations(callback);
+
+ overridingCondition.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
index 9e0f863..0b53133 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
@@ -133,4 +133,12 @@
mCondition.fakeUpdateCondition(false);
verify(callback, never()).onConditionChanged(eq(mCondition));
}
+
+ @Test
+ public void clearCondition_reportsNotSet() {
+ mCondition.fakeUpdateCondition(false);
+ assertThat(mCondition.isConditionSet()).isTrue();
+ mCondition.clearCondition();
+ assertThat(mCondition.isConditionSet()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
index 15ba672..4ca1fd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
@@ -26,6 +26,7 @@
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.Assert.assertTrue
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,6 +36,7 @@
private val serializer = IpcSerializer()
+ @Ignore("b/253046405")
@Test
fun serializeManyIncomingIpcs(): Unit = runBlocking(Dispatchers.Main.immediate) {
val processor = launch(start = CoroutineStart.LAZY) { serializer.process() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
index dead159..e3cd9b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
@@ -1,12 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.util.view
+import android.graphics.Rect
import android.view.View
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
@@ -25,6 +44,12 @@
location[0] = VIEW_LEFT
location[1] = VIEW_TOP
`when`(view.locationOnScreen).thenReturn(location)
+ doAnswer { invocation ->
+ val pos = invocation.arguments[0] as IntArray
+ pos[0] = VIEW_LEFT
+ pos[1] = VIEW_TOP
+ null
+ }.`when`(view).getLocationInWindow(any())
}
@Test
@@ -64,6 +89,18 @@
fun touchIsWithinView_yTooLarge_returnsFalse() {
assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT + 1f, VIEW_BOTTOM + 1f)).isFalse()
}
+
+ @Test
+ fun setRectToViewWindowLocation_rectHasLocation() {
+ val outRect = Rect()
+
+ viewUtil.setRectToViewWindowLocation(view, outRect)
+
+ assertThat(outRect.left).isEqualTo(VIEW_LEFT)
+ assertThat(outRect.right).isEqualTo(VIEW_RIGHT)
+ assertThat(outRect.top).isEqualTo(VIEW_TOP)
+ assertThat(outRect.bottom).isEqualTo(VIEW_BOTTOM)
+ }
}
private const val VIEW_LEFT = 30
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 3434376..c254358 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -16,14 +16,23 @@
package com.android.systemui.wallpapers;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.intThat;
import android.app.WallpaperManager;
import android.content.Context;
@@ -31,17 +40,25 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.Surface;
import android.view.SurfaceHolder;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
import org.junit.Before;
@@ -56,7 +73,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-@Ignore
public class ImageWallpaperTest extends SysuiTestCase {
private static final int LOW_BMP_WIDTH = 128;
private static final int LOW_BMP_HEIGHT = 128;
@@ -66,44 +82,86 @@
private static final int DISPLAY_HEIGHT = 1080;
@Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
+ @Mock
+ private DisplayManager mDisplayManager;
+ @Mock
+ private WallpaperManager mWallpaperManager;
+ @Mock
private SurfaceHolder mSurfaceHolder;
@Mock
+ private Surface mSurface;
+ @Mock
private Context mMockContext;
+
@Mock
private Bitmap mWallpaperBitmap;
+ private int mBitmapWidth = 1;
+ private int mBitmapHeight = 1;
+
@Mock
private Handler mHandler;
@Mock
private FeatureFlags mFeatureFlags;
+ FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ FakeExecutor mFakeMainExecutor = new FakeExecutor(mFakeSystemClock);
+ FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
+
private CountDownLatch mEventCountdown;
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
- mEventCountdown = new CountDownLatch(1);
+ //mEventCountdown = new CountDownLatch(1);
- WallpaperManager wallpaperManager = mock(WallpaperManager.class);
+ // set up window manager
+ when(mWindowMetrics.getBounds()).thenReturn(
+ new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mMockContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
+
+ // set up display manager
+ doNothing().when(mDisplayManager).registerDisplayListener(any(), any());
+ when(mMockContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+
+ // set up bitmap
+ when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
+ when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+ when(mWallpaperBitmap.getWidth()).thenReturn(mBitmapWidth);
+ when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight);
+
+ // set up wallpaper manager
+ when(mWallpaperManager.peekBitmapDimensions()).thenReturn(
+ new Rect(0, 0, mBitmapWidth, mBitmapHeight));
+ when(mWallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
+ when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
+
+ // set up surface
+ when(mSurfaceHolder.getSurface()).thenReturn(mSurface);
+ doNothing().when(mSurface).hwuiDestroy();
+
+ // TODO remove code below. Outdated, used in only in old GL tests (that are ignored)
Resources resources = mock(Resources.class);
-
- when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(wallpaperManager);
- when(mMockContext.getResources()).thenReturn(resources);
when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
-
+ when(mMockContext.getResources()).thenReturn(resources);
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_WIDTH;
displayInfo.logicalHeight = DISPLAY_HEIGHT;
when(mMockContext.getDisplay()).thenReturn(
new Display(mock(DisplayManagerGlobal.class), 0, displayInfo, (Resources) null));
+ }
- when(wallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
- when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
- when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+ private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
+ mBitmapWidth = bitmapWidth;
+ mBitmapHeight = bitmapHeight;
}
private ImageWallpaper createImageWallpaper() {
- return new ImageWallpaper(mFeatureFlags) {
+ return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
@Override
public Engine onCreateEngine() {
return new GLEngine(mHandler) {
@@ -130,6 +188,7 @@
}
@Test
+ @Ignore
public void testBitmapWallpaper_normal() {
// Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
// Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH.
@@ -140,6 +199,7 @@
}
@Test
+ @Ignore
public void testBitmapWallpaper_low_resolution() {
// Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
// Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT.
@@ -150,6 +210,7 @@
}
@Test
+ @Ignore
public void testBitmapWallpaper_too_small() {
// Will use a image wallpaper with dimensions INVALID_BMP_WIDTH x INVALID_BMP_HEIGHT.
// Then we expect the surface size will be also MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT.
@@ -166,8 +227,7 @@
ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine);
- when(mWallpaperBitmap.getWidth()).thenReturn(bmpWidth);
- when(mWallpaperBitmap.getHeight()).thenReturn(bmpHeight);
+ setBitmapDimensions(bmpWidth, bmpHeight);
ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext);
doReturn(renderer).when(engineSpy).getRendererInstance();
@@ -177,4 +237,116 @@
assertWithMessage("setFixedSizeAllowed should have been called.").that(
mEventCountdown.getCount()).isEqualTo(0);
}
+
+
+ private ImageWallpaper createImageWallpaperCanvas() {
+ return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
+ @Override
+ public Engine onCreateEngine() {
+ return new CanvasEngine() {
+ @Override
+ public Context getDisplayContext() {
+ return mMockContext;
+ }
+
+ @Override
+ public SurfaceHolder getSurfaceHolder() {
+ return mSurfaceHolder;
+ }
+
+ @Override
+ public void setFixedSizeAllowed(boolean allowed) {
+ super.setFixedSizeAllowed(allowed);
+ assertWithMessage("mFixedSizeAllowed should be true").that(
+ allowed).isTrue();
+ }
+ };
+ }
+ };
+ }
+
+ private ImageWallpaper.CanvasEngine getSpyEngine() {
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+ doNothing().when(spyEngine).drawFrameOnCanvas(any(Bitmap.class));
+ doNothing().when(spyEngine).reportEngineShown(anyBoolean());
+ doAnswer(invocation -> {
+ ((ImageWallpaper.CanvasEngine) invocation.getMock()).onMiniBitmapUpdated();
+ return null;
+ }).when(spyEngine).recomputeColorExtractorMiniBitmap();
+ return spyEngine;
+ }
+
+ @Test
+ public void testMinSurface() {
+
+ // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
+ testMinSurfaceHelper(8, 8);
+ testMinSurfaceHelper(100, 2000);
+ testMinSurfaceHelper(200, 1);
+ testMinSurfaceHelper(0, 1);
+ testMinSurfaceHelper(1, 0);
+ testMinSurfaceHelper(0, 0);
+ }
+
+ private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) {
+
+ clearInvocations(mSurfaceHolder);
+ setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ engine.onCreate(mSurfaceHolder);
+
+ verify(mSurfaceHolder, times(1)).setFixedSize(
+ intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_WIDTH)),
+ intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_HEIGHT)));
+ }
+
+ @Test
+ public void testZeroBitmap() {
+ // test that a frame is never drawn with a 0 bitmap
+ testZeroBitmapHelper(0, 1);
+ testZeroBitmapHelper(1, 0);
+ testZeroBitmapHelper(0, 0);
+ }
+
+ private void testZeroBitmapHelper(int bitmapWidth, int bitmapHeight) {
+
+ clearInvocations(mSurfaceHolder);
+ setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+ spyEngine.onCreate(mSurfaceHolder);
+ spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+ verify(spyEngine, never()).drawFrameOnCanvas(any());
+ }
+
+ @Test
+ public void testLoadDrawAndUnloadBitmap() {
+ setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
+
+ ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
+ spyEngine.onCreate(mSurfaceHolder);
+ spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+ assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
+
+ int n = 0;
+ while (mFakeBackgroundExecutor.numPending() + mFakeMainExecutor.numPending() >= 1) {
+ n++;
+ assertThat(n).isAtMost(10);
+ mFakeBackgroundExecutor.runNextReady();
+ mFakeMainExecutor.runNextReady();
+ mFakeSystemClock.advanceTime(1000);
+ }
+
+ verify(spyEngine, times(1)).drawFrameOnCanvas(mWallpaperBitmap);
+ assertThat(spyEngine.isBitmapLoaded()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java
deleted file mode 100644
index 93f4f82..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java
+++ /dev/null
@@ -1,133 +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.wallpapers.canvas;
-
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.hamcrest.MockitoHamcrest.intThat;
-
-import android.graphics.Bitmap;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.DisplayInfo;
-import android.view.SurfaceHolder;
-
-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
-public class ImageCanvasWallpaperRendererTest extends SysuiTestCase {
-
- private static final int MOBILE_DISPLAY_WIDTH = 720;
- private static final int MOBILE_DISPLAY_HEIGHT = 1600;
-
- @Mock
- private SurfaceHolder mMockSurfaceHolder;
-
- @Mock
- private DisplayInfo mMockDisplayInfo;
-
- @Mock
- private Bitmap mMockBitmap;
-
- @Before
- public void setUp() throws Exception {
- allowTestableLooperAsMainThread();
- MockitoAnnotations.initMocks(this);
- }
-
- private void setDimensions(
- int bitmapWidth, int bitmapHeight,
- int displayWidth, int displayHeight) {
- when(mMockBitmap.getWidth()).thenReturn(bitmapWidth);
- when(mMockBitmap.getHeight()).thenReturn(bitmapHeight);
- mMockDisplayInfo.logicalWidth = displayWidth;
- mMockDisplayInfo.logicalHeight = displayHeight;
- }
-
- private void testMinDimensions(
- int bitmapWidth, int bitmapHeight) {
-
- clearInvocations(mMockSurfaceHolder);
- setDimensions(bitmapWidth, bitmapHeight,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT);
-
- ImageCanvasWallpaperRenderer renderer =
- new ImageCanvasWallpaperRenderer(mMockSurfaceHolder);
- renderer.drawFrame(mMockBitmap, true);
-
- verify(mMockSurfaceHolder, times(1)).setFixedSize(
- intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_WIDTH)),
- intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_HEIGHT)));
- }
-
- @Test
- public void testMinSurface() {
- // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
- testMinDimensions(8, 8);
-
- testMinDimensions(100, 2000);
-
- testMinDimensions(200, 1);
- }
-
- private void testZeroDimensions(int bitmapWidth, int bitmapHeight) {
-
- clearInvocations(mMockSurfaceHolder);
- setDimensions(bitmapWidth, bitmapHeight,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT);
-
- ImageCanvasWallpaperRenderer renderer =
- new ImageCanvasWallpaperRenderer(mMockSurfaceHolder);
- ImageCanvasWallpaperRenderer spyRenderer = spy(renderer);
- spyRenderer.drawFrame(mMockBitmap, true);
-
- verify(mMockSurfaceHolder, never()).setFixedSize(anyInt(), anyInt());
- verify(spyRenderer, never()).drawWallpaperWithCanvas(any());
- }
-
- @Test
- public void testZeroBitmap() {
- // test that updateSurfaceSize is not called with a bitmap of width 0 or height 0
- testZeroDimensions(
- 0, 1
- );
-
- testZeroDimensions(1, 0
- );
-
- testZeroDimensions(0, 0
- );
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
new file mode 100644
index 0000000..76bff1d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
@@ -0,0 +1,350 @@
+/*
+ * 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.wallpapers.canvas;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class WallpaperColorExtractorTest extends SysuiTestCase {
+ private static final int LOW_BMP_WIDTH = 128;
+ private static final int LOW_BMP_HEIGHT = 128;
+ private static final int HIGH_BMP_WIDTH = 3000;
+ private static final int HIGH_BMP_HEIGHT = 4000;
+ private static final int VERY_LOW_BMP_WIDTH = 1;
+ private static final int VERY_LOW_BMP_HEIGHT = 1;
+ private static final int DISPLAY_WIDTH = 1920;
+ private static final int DISPLAY_HEIGHT = 1080;
+
+ private static final int PAGES_LOW = 4;
+ private static final int PAGES_HIGH = 7;
+
+ private static final int MIN_AREAS = 4;
+ private static final int MAX_AREAS = 10;
+
+ private int mMiniBitmapWidth;
+ private int mMiniBitmapHeight;
+
+ @Mock
+ private Executor mBackgroundExecutor;
+
+ private int mColorsProcessed;
+ private int mMiniBitmapUpdatedCount;
+ private int mActivatedCount;
+ private int mDeactivatedCount;
+
+ @Before
+ public void setUp() throws Exception {
+ allowTestableLooperAsMainThread();
+ MockitoAnnotations.initMocks(this);
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mBackgroundExecutor).execute(any(Runnable.class));
+ }
+
+ private void resetCounters() {
+ mColorsProcessed = 0;
+ mMiniBitmapUpdatedCount = 0;
+ mActivatedCount = 0;
+ mDeactivatedCount = 0;
+ }
+
+ private Bitmap getMockBitmap(int width, int height) {
+ Bitmap bitmap = mock(Bitmap.class);
+ when(bitmap.getWidth()).thenReturn(width);
+ when(bitmap.getHeight()).thenReturn(height);
+ return bitmap;
+ }
+
+ private WallpaperColorExtractor getSpyWallpaperColorExtractor() {
+
+ WallpaperColorExtractor wallpaperColorExtractor = new WallpaperColorExtractor(
+ mBackgroundExecutor,
+ new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ @Override
+ public void onColorsProcessed(List<RectF> regions,
+ List<WallpaperColors> colors) {
+ assertThat(regions.size()).isEqualTo(colors.size());
+ mColorsProcessed += regions.size();
+ }
+
+ @Override
+ public void onMiniBitmapUpdated() {
+ mMiniBitmapUpdatedCount++;
+ }
+
+ @Override
+ public void onActivated() {
+ mActivatedCount++;
+ }
+
+ @Override
+ public void onDeactivated() {
+ mDeactivatedCount++;
+ }
+ });
+ WallpaperColorExtractor spyWallpaperColorExtractor = spy(wallpaperColorExtractor);
+
+ doAnswer(invocation -> {
+ mMiniBitmapWidth = invocation.getArgument(1);
+ mMiniBitmapHeight = invocation.getArgument(2);
+ return getMockBitmap(mMiniBitmapWidth, mMiniBitmapHeight);
+ }).when(spyWallpaperColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+
+ doAnswer(invocation -> getMockBitmap(
+ invocation.getArgument(1),
+ invocation.getArgument(2)))
+ .when(spyWallpaperColorExtractor)
+ .createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+ doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
+ .when(spyWallpaperColorExtractor).getLocalWallpaperColors(any(Rect.class));
+
+ return spyWallpaperColorExtractor;
+ }
+
+ private RectF randomArea() {
+ float width = (float) Math.random();
+ float startX = (float) (Math.random() * (1 - width));
+ float height = (float) Math.random();
+ float startY = (float) (Math.random() * (1 - height));
+ return new RectF(startX, startY, startX + width, startY + height);
+ }
+
+ private List<RectF> listOfRandomAreas(int min, int max) {
+ int nAreas = randomBetween(min, max);
+ List<RectF> result = new ArrayList<>();
+ for (int i = 0; i < nAreas; i++) {
+ result.add(randomArea());
+ }
+ return result;
+ }
+
+ private int randomBetween(int minIncluded, int maxIncluded) {
+ return (int) (Math.random() * ((maxIncluded - minIncluded) + 1)) + minIncluded;
+ }
+
+ /**
+ * Test that for bitmaps of random dimensions, the mini bitmap is always created
+ * with either a width <= SMALL_SIDE or a height <= SMALL_SIDE
+ */
+ @Test
+ public void testMiniBitmapCreation() {
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ int width = randomBetween(LOW_BMP_WIDTH, HIGH_BMP_WIDTH);
+ int height = randomBetween(LOW_BMP_HEIGHT, HIGH_BMP_HEIGHT);
+ Bitmap bitmap = getMockBitmap(width, height);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(Math.min(mMiniBitmapWidth, mMiniBitmapHeight))
+ .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ }
+ }
+
+ /**
+ * Test that for bitmaps with both width and height <= SMALL_SIDE,
+ * the mini bitmap is always created with both width and height <= SMALL_SIDE
+ */
+ @Test
+ public void testSmallMiniBitmapCreation() {
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ int width = randomBetween(VERY_LOW_BMP_WIDTH, LOW_BMP_WIDTH);
+ int height = randomBetween(VERY_LOW_BMP_HEIGHT, LOW_BMP_HEIGHT);
+ Bitmap bitmap = getMockBitmap(width, height);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(Math.max(mMiniBitmapWidth, mMiniBitmapHeight))
+ .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ }
+ }
+
+ /**
+ * Test that for a new color extractor with information
+ * (number of pages, display dimensions, wallpaper bitmap) given in random order,
+ * the colors are processed and all the callbacks are properly executed.
+ */
+ @Test
+ public void testNewColorExtraction() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions = listOfRandomAreas(MIN_AREAS, MAX_AREAS);
+ int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+ List<Runnable> tasks = Arrays.asList(
+ () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+ () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+ () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ () -> spyWallpaperColorExtractor.addLocalColorsAreas(
+ regions));
+ Collections.shuffle(tasks);
+ tasks.forEach(Runnable::run);
+
+ assertThat(mActivatedCount).isEqualTo(1);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(mColorsProcessed).isEqualTo(regions.size());
+
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+ }
+
+ /**
+ * Test that the method removeLocalColorAreas behaves properly and does not call
+ * the onDeactivated callback unless all color areas are removed.
+ */
+ @Test
+ public void testRemoveColors() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions = new ArrayList<>();
+ regions.addAll(regions1);
+ regions.addAll(regions2);
+ int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+ List<Runnable> tasks = Arrays.asList(
+ () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+ () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+ () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ () -> spyWallpaperColorExtractor.removeLocalColorAreas(regions1));
+
+ spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ assertThat(mActivatedCount).isEqualTo(1);
+ Collections.shuffle(tasks);
+ tasks.forEach(Runnable::run);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(mDeactivatedCount).isEqualTo(0);
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+ }
+
+ /**
+ * Test that if we change some information (wallpaper bitmap, number of pages),
+ * the colors are correctly recomputed.
+ * Test that if we remove some color areas in the middle of the process,
+ * only the remaining areas are recomputed.
+ */
+ @Test
+ public void testRecomputeColorExtraction() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions = new ArrayList<>();
+ regions.addAll(regions1);
+ regions.addAll(regions2);
+ spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ assertThat(mActivatedCount).isEqualTo(1);
+ int nPages = PAGES_LOW;
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ spyWallpaperColorExtractor.onPageChanged(nPages);
+ spyWallpaperColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+
+ int nSimulations = 20;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+
+ // verify that if we remove some regions, they are not recomputed after other changes
+ if (i == nSimulations / 2) {
+ regions.removeAll(regions2);
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ }
+
+ if (Math.random() >= 0.5) {
+ int nPagesNew = randomBetween(PAGES_LOW, PAGES_HIGH);
+ if (nPagesNew == nPages) continue;
+ nPages = nPagesNew;
+ spyWallpaperColorExtractor.onPageChanged(nPagesNew);
+ } else {
+ Bitmap newBitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ spyWallpaperColorExtractor.onBitmapChanged(newBitmap);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ }
+ assertThat(mColorsProcessed).isEqualTo(regions.size());
+ }
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+
+ @Test
+ public void testCleanUp() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ doNothing().when(bitmap).recycle();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ spyWallpaperColorExtractor.onPageChanged(PAGES_LOW);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ spyWallpaperColorExtractor.cleanUp();
+ spyWallpaperColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
+ assertThat(mColorsProcessed).isEqualTo(0);
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index c83189d..fa3cc99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -84,9 +84,14 @@
initializer.init(true);
mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
Dependency.setInstance(mDependency);
- mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.class),
- mock(Executor.class), mock(DumpManager.class),
- mock(BroadcastDispatcherLogger.class), mock(UserTracker.class));
+ mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(
+ mContext,
+ mContext.getMainExecutor(),
+ mock(Looper.class),
+ mock(Executor.class),
+ mock(DumpManager.class),
+ mock(BroadcastDispatcherLogger.class),
+ mock(UserTracker.class));
mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
Instrumentation inst = spy(mRealInstrumentation);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index bb646f0..52e0c982 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -32,16 +32,18 @@
class FakeBroadcastDispatcher(
context: SysuiTestableContext,
- looper: Looper,
- executor: Executor,
+ mainExecutor: Executor,
+ broadcastRunningLooper: Looper,
+ broadcastRunningExecutor: Executor,
dumpManager: DumpManager,
logger: BroadcastDispatcherLogger,
userTracker: UserTracker
) :
BroadcastDispatcher(
context,
- looper,
- executor,
+ mainExecutor,
+ broadcastRunningLooper,
+ broadcastRunningExecutor,
dumpManager,
logger,
userTracker,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index c56fdb1..5d52be2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -16,6 +16,8 @@
package com.android.systemui.flags
+import java.io.PrintWriter
+
class FakeFeatureFlags : FeatureFlags {
private val booleanFlags = mutableMapOf<Int, Boolean>()
private val stringFlags = mutableMapOf<Int, String>()
@@ -106,6 +108,10 @@
}
}
+ override fun dump(writer: PrintWriter, args: Array<out String>?) {
+ // no-op
+ }
+
private fun flagName(flagId: Int): String {
return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 725b1f4..0c12680 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.StatusBarState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -44,6 +45,9 @@
private val _dozeAmount = MutableStateFlow(0f)
override val dozeAmount: Flow<Float> = _dozeAmount
+ private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
+ override val statusBarState: Flow<StatusBarState> = _statusBarState
+
override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
index 9d5ccbe..1353ad2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
@@ -21,6 +21,14 @@
* condition fulfillment.
*/
public class FakeCondition extends Condition {
+ FakeCondition() {
+ super();
+ }
+
+ FakeCondition(Boolean initialValue, Boolean overriding) {
+ super(initialValue, overriding);
+ }
+
@Override
public void start() {}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 2be67ed..23c7a61 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -70,6 +70,10 @@
}
@Override
+ public void setNewMobileIconSubIds(List<Integer> subIds) {
+ }
+
+ @Override
public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
}
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 a5ec0a4..5a868a4 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.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 @@
*
* 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 @@
@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 402dd84..a1ed178 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 @@
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 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 @@
mainHandler: Handler,
mainExecutor: Executor,
backgroundExecutor: Executor,
- tracingTagPrefix: String
-): UnfoldTransitionProgressProvider =
+ tracingTagPrefix: String,
+ windowManager: IWindowManager,
+): UnfoldSharedComponent =
DaggerUnfoldSharedComponent.factory()
.create(
context,
@@ -57,9 +60,6 @@
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 d54481c..7117aaf 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 @@
*
* 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/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 2ab28c6..043aff6 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -203,6 +203,6 @@
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
private const val DEBUG = true
-private const val SPRING_STIFFNESS = 200.0f
+private const val SPRING_STIFFNESS = 600.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f
private const val FINAL_HINGE_ANGLE_POSITION = 165f
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 19cfc80..07473b3 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.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 @@
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 @@
foldProvider.registerCallback(foldStateListener, mainExecutor)
screenStatusProvider.addCallback(screenListener)
hingeAngleProvider.addCallback(hingeAngleListener)
+ rotationChangeProvider.addCallback(rotationListener)
}
override fun stop() {
@@ -79,6 +83,7 @@
foldProvider.unregisterCallback(foldStateListener)
hingeAngleProvider.removeCallback(hingeAngleListener)
hingeAngleProvider.stop()
+ rotationChangeProvider.removeCallback(rotationListener)
}
override fun addCallback(listener: FoldUpdatesListener) {
@@ -90,14 +95,15 @@
}
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 @@
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 @@
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 @@
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 @@
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 0000000..0cf8224
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold.updates
+
+import android.content.Context
+import android.os.RemoteException
+import android.view.IRotationWatcher
+import android.view.IWindowManager
+import android.view.Surface.Rotation
+import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.util.CallbackController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Allows to subscribe to rotation changes.
+ *
+ * This is needed as rotation updates from [IWindowManager] are received in a binder thread, while
+ * most of the times we want them in the main one. Updates are provided for the display associated
+ * to [context].
+ */
+class RotationChangeProvider
+@Inject
+constructor(
+ private val windowManagerInterface: IWindowManager,
+ private val context: Context,
+ @UnfoldMain private val mainExecutor: Executor,
+) : CallbackController<RotationChangeProvider.RotationListener> {
+
+ private val listeners = mutableListOf<RotationListener>()
+
+ private val rotationWatcher = RotationWatcher()
+
+ override fun addCallback(listener: RotationListener) {
+ mainExecutor.execute {
+ if (listeners.isEmpty()) {
+ subscribeToRotation()
+ }
+ listeners += listener
+ }
+ }
+
+ override fun removeCallback(listener: RotationListener) {
+ mainExecutor.execute {
+ listeners -= listener
+ if (listeners.isEmpty()) {
+ unsubscribeToRotation()
+ }
+ }
+ }
+
+ private fun subscribeToRotation() {
+ try {
+ windowManagerInterface.watchRotation(rotationWatcher, context.displayId)
+ } catch (e: RemoteException) {
+ throw e.rethrowFromSystemServer()
+ }
+ }
+
+ private fun unsubscribeToRotation() {
+ try {
+ windowManagerInterface.removeRotationWatcher(rotationWatcher)
+ } catch (e: RemoteException) {
+ throw e.rethrowFromSystemServer()
+ }
+ }
+
+ /** Gets notified of rotation changes. */
+ fun interface RotationListener {
+ /** Called once rotation changes. */
+ fun onRotationChanged(@Rotation newRotation: Int)
+ }
+
+ private inner class RotationWatcher : IRotationWatcher.Stub() {
+ override fun onRotationChanged(rotation: Int) {
+ mainExecutor.execute { listeners.forEach { it.onRotationChanged(rotation) } }
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6417db0..29194c5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -657,25 +657,27 @@
userState.mBindingServices.removeIf(filter);
userState.mCrashedServices.removeIf(filter);
final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+ boolean anyServiceRemoved = false;
while (it.hasNext()) {
final ComponentName comp = it.next();
final String compPkg = comp.getPackageName();
if (compPkg.equals(packageName)) {
it.remove();
- // Update the enabled services setting.
- persistComponentNamesToSettingLocked(
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- userState.mEnabledServices, userId);
- // Update the touch exploration granted services setting.
userState.mTouchExplorationGrantedServices.remove(comp);
- persistComponentNamesToSettingLocked(
- Settings.Secure.
- TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
- userState.mTouchExplorationGrantedServices, userId);
- onUserStateChangedLocked(userState);
- return;
+ anyServiceRemoved = true;
}
}
+ if (anyServiceRemoved) {
+ // Update the enabled services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices, userId);
+ // Update the touch exploration granted services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ userState.mTouchExplorationGrantedServices, userId);
+ onUserStateChangedLocked(userState);
+ }
}
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index ca7fe0c..14cfce7 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 @@
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/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index f366cec..c235e05 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -519,7 +519,7 @@
final Object curReceiver = r.receivers.get(curIndex);
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
- ActivityManagerService.getShortAction(r.intent.getAction()),
+ r.intent.getAction(),
curReceiver instanceof BroadcastFilter
? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
: BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
@@ -692,7 +692,7 @@
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
callingUid == -1 ? Process.SYSTEM_UID : callingUid,
- ActivityManagerService.getShortAction(intent.getAction()),
+ intent.getAction(),
BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
dispatchDelay, receiveDelay, 0 /* finish_delay */);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 0d08db9..736914a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1365,7 +1365,8 @@
break;
case MSG_II_SET_HEARING_AID_VOLUME:
synchronized (mDeviceStateLock) {
- mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+ mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2,
+ mDeviceInventory.isHearingAidConnected());
}
break;
case MSG_II_SET_LE_AUDIO_OUT_VOLUME: {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ee0d79f..35da73e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -377,7 +377,8 @@
makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice);
} else if (switchToAvailable) {
makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
- streamType, btInfo.mVolume, btInfo.mAudioSystemDevice,
+ streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
+ btInfo.mAudioSystemDevice,
"onSetBtActiveDevice");
}
break;
@@ -1161,6 +1162,22 @@
.record();
}
+ /**
+ * Returns whether a device of type DEVICE_OUT_HEARING_AID is connected.
+ * Visibility by APM plays no role
+ * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise.
+ */
+ boolean isHearingAidConnected() {
+ synchronized (mDevicesLock) {
+ for (DeviceInfo di : mConnectedDevices.values()) {
+ if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
@GuardedBy("mDevicesLock")
private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
int volumeIndex, int device, String eventSource) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0b6b890..745555c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -41,10 +41,12 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
@@ -1185,6 +1187,8 @@
mSafeMediaVolumeIndex = mContext.getResources().getInteger(
com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
@@ -1202,7 +1206,7 @@
mPlaybackMonitor =
new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
device -> onMuteAwaitConnectionTimeout(device));
- mPlaybackMonitor.registerPlaybackCallback(mVoicePlaybackActivityMonitor, true);
+ mPlaybackMonitor.registerPlaybackCallback(mPlaybackActivityMonitor, true);
mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
@@ -1308,6 +1312,7 @@
intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+ intentFilter.addAction(ACTION_CHECK_MUSIC_ACTIVE);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
Context.RECEIVER_EXPORTED);
@@ -1927,13 +1932,7 @@
if (state == AudioService.CONNECTION_STATE_CONNECTED) {
// DEVICE_OUT_HDMI is now connected
if (mSafeMediaVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI)) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ scheduleMusicActiveCheck();
}
if (isPlatformTelevision()) {
@@ -3822,8 +3821,9 @@
}
private AtomicBoolean mVoicePlaybackActive = new AtomicBoolean(false);
+ private AtomicBoolean mMediaPlaybackActive = new AtomicBoolean(false);
- private final IPlaybackConfigDispatcher mVoicePlaybackActivityMonitor =
+ private final IPlaybackConfigDispatcher mPlaybackActivityMonitor =
new IPlaybackConfigDispatcher.Stub() {
@Override
public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
@@ -3836,19 +3836,26 @@
private void onPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
boolean voiceActive = false;
+ boolean mediaActive = false;
for (AudioPlaybackConfiguration config : configs) {
final int usage = config.getAudioAttributes().getUsage();
- if ((usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
- || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
- && config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ if (!config.isActive()) {
+ continue;
+ }
+ if (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+ || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) {
voiceActive = true;
- break;
+ }
+ if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) {
+ mediaActive = true;
}
}
if (mVoicePlaybackActive.getAndSet(voiceActive) != voiceActive) {
updateHearingAidVolumeOnVoiceActivityUpdate();
}
-
+ if (mMediaPlaybackActive.getAndSet(mediaActive) != mediaActive && mediaActive) {
+ scheduleMusicActiveCheck();
+ }
// Update playback active state for all apps in audio mode stack.
// When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
// and request an audio mode update immediately. Upon any other change, queue the message
@@ -4011,7 +4018,7 @@
}
}
- private void setLeAudioVolumeOnModeUpdate(int mode) {
+ private void setLeAudioVolumeOnModeUpdate(int mode, int streamType, int device) {
switch (mode) {
case AudioSystem.MODE_IN_COMMUNICATION:
case AudioSystem.MODE_IN_CALL:
@@ -4025,8 +4032,6 @@
return;
}
- int streamType = getBluetoothContextualVolumeStream(mode);
-
// Currently, DEVICE_OUT_BLE_HEADSET is the only output type for LE_AUDIO profile.
// (See AudioDeviceBroker#createBtDeviceInfo())
int index = mStreamStates[streamType].getIndex(AudioSystem.DEVICE_OUT_BLE_HEADSET);
@@ -4037,6 +4042,7 @@
+ index + " maxIndex=" + maxIndex + " streamType=" + streamType);
}
mDeviceBroker.postSetLeAudioVolumeIndex(index, maxIndex, streamType);
+ mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "setLeAudioVolumeOnModeUpdate");
}
private void setStreamVolume(int streamType, int index, int flags,
@@ -5417,7 +5423,7 @@
// Forcefully set LE audio volume as a workaround, since the value of 'device'
// is not DEVICE_OUT_BLE_* even when BLE is connected.
- setLeAudioVolumeOnModeUpdate(mode);
+ setLeAudioVolumeOnModeUpdate(mode, streamType, device);
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
@@ -6032,30 +6038,52 @@
return mContentResolver;
}
+ private void scheduleMusicActiveCheck() {
+ synchronized (mSafeMediaVolumeStateLock) {
+ cancelMusicActiveCheck();
+ mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_CHECK_MUSIC_ACTIVE,
+ new Intent(ACTION_CHECK_MUSIC_ACTIVE),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime()
+ + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
+ }
+ }
+
+ private void cancelMusicActiveCheck() {
+ synchronized (mSafeMediaVolumeStateLock) {
+ if (mMusicActiveIntent != null) {
+ mAlarmManager.cancel(mMusicActiveIntent);
+ mMusicActiveIntent = null;
+ }
+ }
+ }
private void onCheckMusicActive(String caller) {
synchronized (mSafeMediaVolumeStateLock) {
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-
- if (mSafeMediaVolumeDevices.contains(device)) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ if (mSafeMediaVolumeDevices.contains(device)
+ && mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+ scheduleMusicActiveCheck();
int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
- if (mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
- && (index > safeMediaVolumeIndex(device))) {
+ if (index > safeMediaVolumeIndex(device)) {
// Approximate cumulative active music time
- mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
+ long curTimeMs = SystemClock.elapsedRealtime();
+ if (mLastMusicActiveTimeMs != 0) {
+ mMusicActiveMs += (int) (curTimeMs - mLastMusicActiveTimeMs);
+ }
+ mLastMusicActiveTimeMs = curTimeMs;
+ Log.i(TAG, "onCheckMusicActive() mMusicActiveMs: " + mMusicActiveMs);
if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
setSafeMediaVolumeEnabled(true, caller);
mMusicActiveMs = 0;
}
saveMusicActiveMs();
}
+ } else {
+ cancelMusicActiveCheck();
+ mLastMusicActiveTimeMs = 0;
}
}
}
@@ -6124,6 +6152,7 @@
} else {
// We have existing playback time recorded, already confirmed.
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+ mLastMusicActiveTimeMs = 0;
}
}
} else {
@@ -8633,13 +8662,7 @@
@VisibleForTesting
public void checkMusicActive(int deviceType, String caller) {
if (mSafeMediaVolumeDevices.contains(deviceType)) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ scheduleMusicActiveCheck();
}
}
@@ -8764,6 +8787,8 @@
suspendedPackages[i], suspendedUids[i]);
}
}
+ } else if (action.equals(ACTION_CHECK_MUSIC_ACTIVE)) {
+ onCheckMusicActive(ACTION_CHECK_MUSIC_ACTIVE);
}
}
} // end class AudioServiceBroadcastReceiver
@@ -9709,12 +9734,20 @@
// When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
// automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
private int mMusicActiveMs;
+ private long mLastMusicActiveTimeMs = 0;
+ private PendingIntent mMusicActiveIntent = null;
+ private AlarmManager mAlarmManager;
+
private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval
private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed
// check playback or record activity every 6 seconds for UIDs owning mode IN_COMMUNICATION
private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
+ private static final String ACTION_CHECK_MUSIC_ACTIVE =
+ AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+ private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
+
private int safeMediaVolumeIndex(int device) {
if (!mSafeMediaVolumeDevices.contains(device)) {
return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -9736,14 +9769,9 @@
} else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
mMusicActiveMs = 1; // nonzero = confirmed
+ mLastMusicActiveTimeMs = 0;
saveMusicActiveMs();
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ scheduleMusicActiveCheck();
}
}
}
@@ -9785,7 +9813,9 @@
public void disableSafeMediaVolume(String callingPackage) {
enforceVolumeController("disable the safe media volume");
synchronized (mSafeMediaVolumeStateLock) {
+ final long identity = Binder.clearCallingIdentity();
setSafeMediaVolumeEnabled(false, callingPackage);
+ Binder.restoreCallingIdentity(identity);
if (mPendingVolumeCommand != null) {
onSetStreamVolume(mPendingVolumeCommand.mStreamType,
mPendingVolumeCommand.mIndex,
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index d0f5470..6cd42f8 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -424,7 +424,8 @@
mLeAudio.setVolume(volume);
}
- /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
+ /*package*/ synchronized void setHearingAidVolume(int index, int streamType,
+ boolean isHeadAidConnected) {
if (mHearingAid == null) {
if (AudioService.DEBUG_VOL) {
Log.i(TAG, "setHearingAidVolume: null mHearingAid");
@@ -441,8 +442,11 @@
Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
+ index + " gain=" + gainDB);
}
- AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
- AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+ // do not log when hearing aid is not connected to avoid confusion when reading dumpsys
+ if (isHeadAidConnected) {
+ AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+ }
mHearingAid.setVolume(gainDB);
}
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 62f94ed..1a5f31c 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 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 @@
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 @@
@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 @@
+ 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 d6ca8a6..27a70c5 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 @@
/** {@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 @@
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 02b350e..55fe854 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 @@
+ ", RequireConfirmation: " + requireConfirmation
+ ", State: " + authState
+ ", Latency: " + latency
- + ", Lux: " + mALSProbe.getCurrentLux());
+ + ", Lux: " + mALSProbe.getMostRecentLux());
} else {
Slog.v(TAG, "Authentication latency: " + latency);
}
@@ -231,7 +231,7 @@
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 @@
+ ", 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 @@
}
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 b3f42be..fa75100 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 @@
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/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a7d3729..40e28da 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -592,10 +592,14 @@
}
pw.println();
+ pw.println(" mAmbientBrightnessThresholds=");
mAmbientBrightnessThresholds.dump(pw);
+ pw.println(" mScreenBrightnessThresholds=");
mScreenBrightnessThresholds.dump(pw);
+ pw.println(" mScreenBrightnessThresholdsIdle=");
mScreenBrightnessThresholdsIdle.dump(pw);
- mScreenBrightnessThresholdsIdle.dump(pw);
+ pw.println(" mAmbientBrightnessThresholdsIdle=");
+ mAmbientBrightnessThresholdsIdle.dump(pw);
}
private String configStateToString(int state) {
@@ -860,6 +864,7 @@
Slog.d(TAG, "updateAmbientLux: "
+ ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
+ + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
+ "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
+ "mAmbientLux=" + mAmbientLux);
}
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 17215e5..8f59ffd 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -220,6 +220,11 @@
}
private void backgroundStart(float initialBrightness) {
+ synchronized (mDataCollectionLock) {
+ if (mStarted) {
+ return;
+ }
+ }
if (DEBUG) {
Slog.d(TAG, "Background start");
}
@@ -250,6 +255,11 @@
/** Stop listening for events */
void stop() {
+ synchronized (mDataCollectionLock) {
+ if (!mStarted) {
+ return;
+ }
+ }
if (DEBUG) {
Slog.d(TAG, "Stop");
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4165186..7858516 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -27,6 +27,7 @@
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.MathUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.Spline;
import android.view.DisplayAddress;
@@ -51,7 +52,7 @@
import com.android.server.display.config.SensorDetails;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.config.ThermalThrottling;
-import com.android.server.display.config.Thresholds;
+import com.android.server.display.config.ThresholdPoint;
import com.android.server.display.config.XmlParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -149,7 +150,7 @@
* <quirk>canSetBrightnessViaHwc</quirk>
* </quirks>
*
- * <autoBrightness>
+ * <autoBrightness enable="true">
* <brighteningLightDebounceMillis>
* 2000
* </brighteningLightDebounceMillis>
@@ -188,42 +189,153 @@
* <ambientLightHorizonLong>10001</ambientLightHorizonLong>
* <ambientLightHorizonShort>2001</ambientLightHorizonShort>
*
- * <displayBrightnessChangeThresholds> // Thresholds for screen changes
- * <brighteningThresholds> // Thresholds for active mode brightness changes.
- * <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.002</minimum> // Minimum change needed in screen brightness to darken.
- * </darkeningThresholds>
- * </displayBrightnessChangeThresholds>
- *
- * <ambientBrightnessChangeThresholds> // Thresholds for lux changes
- * <brighteningThresholds> // Thresholds for active mode brightness changes.
- * <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.004</minimum> // Minimum change needed in ambient brightness to darken.
- * </darkeningThresholds>
- * </ambientBrightnessChangeThresholds>
- *
- * <displayBrightnessChangeThresholdsIdle> // Thresholds for screen changes in idle mode
- * <brighteningThresholds> // Thresholds for idle mode brightness changes.
- * <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.002</minimum> // Minimum change needed in screen brightness to darken.
- * </darkeningThresholds>
- * </displayBrightnessChangeThresholdsIdle>
- *
- * <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode
- * <brighteningThresholds> // Thresholds for idle mode brightness changes.
- * <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.004</minimum> // Minimum change needed in ambient brightness to darken.
- * </darkeningThresholds>
- * </ambientBrightnessChangeThresholdsIdle>
- *
+ * <ambientBrightnessChangeThresholds> // Thresholds for lux changes
+ * <brighteningThresholds>
+ * // Minimum change needed in ambient brightness to brighten screen.
+ * <minimum>10</minimum>
+ * // Percentage increase of lux needed to increase the screen brightness at a lux range
+ * // above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>13</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>100</threshold><percentage>14</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>200</threshold><percentage>15</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+* // Minimum change needed in ambient brightness to darken screen.
+ * <minimum>30</minimum>
+ * // Percentage increase of lux needed to decrease the screen brightness at a lux range
+ * // above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>15</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>300</threshold><percentage>16</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>400</threshold><percentage>17</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </ambientBrightnessChangeThresholds>
+ * <displayBrightnessChangeThresholds> // Thresholds for screen brightness changes
+ * <brighteningThresholds>
+ * // Minimum change needed in screen brightness to brighten screen.
+ * <minimum>0.1</minimum>
+ * // Percentage increase of screen brightness needed to increase the screen brightness
+ * // at a lux range above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold>
+ * <percentage>9</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.10</threshold>
+ * <percentage>10</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.20</threshold>
+ * <percentage>11</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * // Minimum change needed in screen brightness to darken screen.
+ * <minimum>0.3</minimum>
+ * // Percentage increase of screen brightness needed to decrease the screen brightness
+ * // at a lux range above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>11</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.11</threshold><percentage>12</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.21</threshold><percentage>13</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </displayBrightnessChangeThresholds>
+ * <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode
+ * <brighteningThresholds>
+ * // Minimum change needed in ambient brightness to brighten screen in idle mode
+ * <minimum>20</minimum>
+ * // Percentage increase of lux needed to increase the screen brightness at a lux range
+ * // above the specified threshold whilst in idle mode.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>21</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>500</threshold><percentage>22</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>600</threshold><percentage>23</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * // Minimum change needed in ambient brightness to darken screen in idle mode
+ * <minimum>40</minimum>
+ * // Percentage increase of lux needed to decrease the screen brightness at a lux range
+ * // above the specified threshold whilst in idle mode.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>23</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>700</threshold><percentage>24</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>800</threshold><percentage>25</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </ambientBrightnessChangeThresholdsIdle>
+ * <displayBrightnessChangeThresholdsIdle> // Thresholds for idle screen brightness changes
+ * <brighteningThresholds>
+ * // Minimum change needed in screen brightness to brighten screen in idle mode
+ * <minimum>0.2</minimum>
+ * // Percentage increase of screen brightness needed to increase the screen brightness
+ * // at a lux range above the specified threshold whilst in idle mode
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>17</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.12</threshold><percentage>18</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.22</threshold><percentage>19</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * // Minimum change needed in screen brightness to darken screen in idle mode
+ * <minimum>0.4</minimum>
+ * // Percentage increase of screen brightness needed to decrease the screen brightness
+ * // at a lux range above the specified threshold whilst in idle mode
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>19</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.13</threshold><percentage>20</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.23</threshold><percentage>21</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </displayBrightnessChangeThresholdsIdle>
* </displayConfiguration>
* }
* </pre>
@@ -247,6 +359,13 @@
private static final String NO_SUFFIX_FORMAT = "%d";
private static final long STABLE_FLAG = 1L << 62;
+ private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
+ private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
+ private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
+ private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
+ private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
+ private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
+
private static final int INTERPOLATION_DEFAULT = 0;
private static final int INTERPOLATION_LINEAR = 1;
@@ -344,6 +463,31 @@
private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f;
private float mAmbientLuxDarkeningMinThreshold = 0.0f;
private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f;
+
+ // Screen brightness thresholds levels & percentages
+ private float[] mScreenBrighteningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenBrighteningPercentages = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
+ private float[] mScreenDarkeningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenDarkeningPercentages = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
+
+ // Screen brightness thresholds levels & percentages for idle mode
+ private float[] mScreenBrighteningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenBrighteningPercentagesIdle = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
+ private float[] mScreenDarkeningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenDarkeningPercentagesIdle = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
+
+ // Ambient brightness thresholds levels & percentages
+ private float[] mAmbientBrighteningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientBrighteningPercentages = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
+ private float[] mAmbientDarkeningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientDarkeningPercentages = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
+ // Ambient brightness thresholds levels & percentages for idle mode
+ private float[] mAmbientBrighteningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientBrighteningPercentagesIdle = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
+ private float[] mAmbientDarkeningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientDarkeningPercentagesIdle = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
private Spline mBrightnessToBacklightSpline;
private Spline mBacklightToBrightnessSpline;
private Spline mBacklightToNitsSpline;
@@ -363,6 +507,11 @@
private long mAutoBrightnessDarkeningLightDebounce =
INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE;
+ // This setting allows non-default displays to have autobrightness enabled.
+ private boolean mAutoBrightnessAvailable = false;
+ // This stores the raw value loaded from the config file - true if not written.
+ private boolean mDdcAutoBrightnessAvailable = true;
+
// Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
// data, which comes from the ddc, and the current one, which may be the DeviceConfig
// overwritten value.
@@ -684,7 +833,7 @@
/**
* The minimum value for the ambient lux increase for a screen brightness change to actually
* occur.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxBrighteningMinThreshold() {
return mAmbientLuxBrighteningMinThreshold;
@@ -693,7 +842,7 @@
/**
* The minimum value for the ambient lux decrease for a screen brightness change to actually
* occur.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxDarkeningMinThreshold() {
return mAmbientLuxDarkeningMinThreshold;
@@ -702,7 +851,7 @@
/**
* The minimum value for the ambient lux increase for a screen brightness change to actually
* occur while in idle screen brightness mode.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxBrighteningMinThresholdIdle() {
return mAmbientLuxBrighteningMinThresholdIdle;
@@ -711,12 +860,262 @@
/**
* The minimum value for the ambient lux decrease for a screen brightness change to actually
* occur while in idle screen brightness mode.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxDarkeningMinThresholdIdle() {
return mAmbientLuxDarkeningMinThresholdIdle;
}
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenBrighteningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenBrighteningPercentages[n]
+ * level[MAX] <= value = mScreenBrighteningPercentages[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenBrighteningPercentages applies
+ */
+ public float[] getScreenBrighteningLevels() {
+ return mScreenBrighteningLevels;
+ }
+
+ /**
+ * The array that describes the screen brightening threshold percentage change at each screen
+ * brightness level described in mScreenBrighteningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness increase required in order for the
+ * screen brightness to change
+ */
+ public float[] getScreenBrighteningPercentages() {
+ return mScreenBrighteningPercentages;
+ }
+
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenDarkeningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenDarkeningPercentages[n]
+ * level[MAX] <= value = mScreenDarkeningPercentages[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenDarkeningPercentages applies
+ */
+ public float[] getScreenDarkeningLevels() {
+ return mScreenDarkeningLevels;
+ }
+
+ /**
+ * The array that describes the screen darkening threshold percentage change at each screen
+ * brightness level described in mScreenDarkeningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness decrease required in order for the
+ * screen brightness to change
+ */
+ public float[] getScreenDarkeningPercentages() {
+ return mScreenDarkeningPercentages;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold
+ * percentage applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientBrighteningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientBrighteningPercentages[n]
+ * level[MAX] <= value = mAmbientBrighteningPercentages[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientBrighteningPercentages applies
+ */
+ public float[] getAmbientBrighteningLevels() {
+ return mAmbientBrighteningLevels;
+ }
+
+ /**
+ * The array that describes the ambient brightening threshold percentage change at each ambient
+ * brightness level described in mAmbientBrighteningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness increase required in order for the
+ * screen brightness to change
+ */
+ public float[] getAmbientBrighteningPercentages() {
+ return mAmbientBrighteningPercentages;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold percentage
+ * applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientDarkeningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientDarkeningPercentages[n]
+ * level[MAX] <= value = mAmbientDarkeningPercentages[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientDarkeningPercentages applies
+ */
+ public float[] getAmbientDarkeningLevels() {
+ return mAmbientDarkeningLevels;
+ }
+
+ /**
+ * The array that describes the ambient darkening threshold percentage change at each ambient
+ * brightness level described in mAmbientDarkeningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness decrease required in order for the
+ * screen brightness to change
+ */
+ public float[] getAmbientDarkeningPercentages() {
+ return mAmbientDarkeningPercentages;
+ }
+
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenBrighteningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenBrighteningPercentagesIdle[n]
+ * level[MAX] <= value = mScreenBrighteningPercentagesIdle[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenBrighteningPercentagesIdle applies
+ */
+ public float[] getScreenBrighteningLevelsIdle() {
+ return mScreenBrighteningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the screen brightening threshold percentage change at each screen
+ * brightness level described in mScreenBrighteningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of brightness increase required in order for the
+ * screen brightness to change while in idle mode.
+ */
+ public float[] getScreenBrighteningPercentagesIdle() {
+ return mScreenBrighteningPercentagesIdle;
+ }
+
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenDarkeningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenDarkeningPercentagesIdle[n]
+ * level[MAX] <= value = mScreenDarkeningPercentagesIdle[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenDarkeningPercentagesIdle applies
+ */
+ public float[] getScreenDarkeningLevelsIdle() {
+ return mScreenDarkeningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the screen darkening threshold percentage change at each screen
+ * brightness level described in mScreenDarkeningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of brightness decrease required in order for the
+ * screen brightness to change while in idle mode.
+ */
+ public float[] getScreenDarkeningPercentagesIdle() {
+ return mScreenDarkeningPercentagesIdle;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientBrighteningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientBrighteningPercentagesIdle[n]
+ * level[MAX] <= value = mAmbientBrighteningPercentagesIdle[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientBrighteningPercentagesIdle applies
+ */
+ public float[] getAmbientBrighteningLevelsIdle() {
+ return mAmbientBrighteningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the ambient brightness threshold percentage change whilst in
+ * idle screen brightness mode at each ambient brightness level described in
+ * mAmbientBrighteningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of ambient brightness increase required in order
+ * for the screen brightness to change
+ */
+ public float[] getAmbientBrighteningPercentagesIdle() {
+ return mAmbientBrighteningPercentagesIdle;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientDarkeningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientDarkeningPercentagesIdle[n]
+ * level[MAX] <= value = mAmbientDarkeningPercentagesIdle[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientDarkeningPercentagesIdle applies
+ */
+ public float[] getAmbientDarkeningLevelsIdle() {
+ return mAmbientDarkeningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the ambient brightness threshold percentage change whilst in
+ * idle screen brightness mode at each ambient brightness level described in
+ * mAmbientDarkeningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of ambient brightness decrease required in order
+ * for the screen brightness to change
+ */
+ public float[] getAmbientDarkeningPercentagesIdle() {
+ return mAmbientDarkeningPercentagesIdle;
+ }
+
SensorData getAmbientLightSensor() {
return mAmbientLightSensor;
}
@@ -725,6 +1124,10 @@
return mProximitySensor;
}
+ boolean isAutoBrightnessAvailable() {
+ return mAutoBrightnessAvailable;
+ }
+
/**
* @param quirkValue The quirk to test.
* @return {@code true} if the specified quirk is present in this configuration, {@code false}
@@ -812,14 +1215,17 @@
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+ ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
+ ", mOriginalBrightnessThrottlingData=" + mOriginalBrightnessThrottlingData
+ + "\n"
+ ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+ ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
+ ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
+ ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease
+ ", mBrightnessRampDecreaseMaxMillis=" + mBrightnessRampDecreaseMaxMillis
+ ", mBrightnessRampIncreaseMaxMillis=" + mBrightnessRampIncreaseMaxMillis
+ + "\n"
+ ", mAmbientHorizonLong=" + mAmbientHorizonLong
+ ", mAmbientHorizonShort=" + mAmbientHorizonShort
+ + "\n"
+ ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
+ ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
+ ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
@@ -829,6 +1235,41 @@
+ ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
+ ", mAmbientLuxBrighteningMinThresholdIdle="
+ mAmbientLuxBrighteningMinThresholdIdle
+ + "\n"
+ + ", mScreenBrighteningLevels=" + Arrays.toString(
+ mScreenBrighteningLevels)
+ + ", mScreenBrighteningPercentages=" + Arrays.toString(
+ mScreenBrighteningPercentages)
+ + ", mScreenDarkeningLevels=" + Arrays.toString(
+ mScreenDarkeningLevels)
+ + ", mScreenDarkeningPercentages=" + Arrays.toString(
+ mScreenDarkeningPercentages)
+ + ", mAmbientBrighteningLevels=" + Arrays.toString(
+ mAmbientBrighteningLevels)
+ + ", mAmbientBrighteningPercentages=" + Arrays.toString(
+ mAmbientBrighteningPercentages)
+ + ", mAmbientDarkeningLevels=" + Arrays.toString(
+ mAmbientDarkeningLevels)
+ + ", mAmbientDarkeningPercentages=" + Arrays.toString(
+ mAmbientDarkeningPercentages)
+ + "\n"
+ + ", mAmbientBrighteningLevelsIdle=" + Arrays.toString(
+ mAmbientBrighteningLevelsIdle)
+ + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString(
+ mAmbientBrighteningPercentagesIdle)
+ + ", mAmbientDarkeningLevelsIdle=" + Arrays.toString(
+ mAmbientDarkeningLevelsIdle)
+ + ", mAmbientDarkeningPercentagesIdle=" + Arrays.toString(
+ mAmbientDarkeningPercentagesIdle)
+ + ", mScreenBrighteningLevelsIdle=" + Arrays.toString(
+ mScreenBrighteningLevelsIdle)
+ + ", mScreenBrighteningPercentagesIdle=" + Arrays.toString(
+ mScreenBrighteningPercentagesIdle)
+ + ", mScreenDarkeningLevelsIdle=" + Arrays.toString(
+ mScreenDarkeningLevelsIdle)
+ + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString(
+ mScreenDarkeningPercentagesIdle)
+ + "\n"
+ ", mAmbientLightSensor=" + mAmbientLightSensor
+ ", mProximitySensor=" + mProximitySensor
+ ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -839,6 +1280,8 @@
+ mAutoBrightnessDarkeningLightDebounce
+ ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
+ ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+ + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ "}";
}
@@ -914,8 +1357,10 @@
loadBrightnessMapFromConfigXml();
loadBrightnessRampsFromConfigXml();
loadAmbientLightSensorFromConfigXml();
+ loadBrightnessChangeThresholdsFromXml();
setProxSensorUnspecified();
loadAutoBrightnessConfigsFromConfigXml();
+ loadAutoBrightnessAvailableFromConfigXml();
mLoadedFrom = "<config.xml>";
}
@@ -934,6 +1379,7 @@
setSimpleMappingStrategyValues();
loadAmbientLightSensorFromConfigXml();
setProxSensorUnspecified();
+ loadAutoBrightnessAvailableFromConfigXml();
}
private void copyUninitializedValuesFromSecondaryConfig(DisplayConfiguration defaultConfig) {
@@ -1126,9 +1572,11 @@
}
private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
- loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
- loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
- loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness());
+ final AutoBrightness autoBrightness = config.getAutoBrightness();
+ loadAutoBrightnessBrighteningLightDebounce(autoBrightness);
+ loadAutoBrightnessDarkeningLightDebounce(autoBrightness);
+ loadAutoBrightnessDisplayBrightnessMapping(autoBrightness);
+ loadEnableAutoBrightness(autoBrightness);
}
/**
@@ -1190,6 +1638,11 @@
}
}
+ private void loadAutoBrightnessAvailableFromConfigXml() {
+ mAutoBrightnessAvailable = mContext.getResources().getBoolean(
+ R.bool.config_automatic_brightness_available);
+ }
+
private void loadBrightnessMapFromConfigXml() {
// Use the config.xml mapping
final Resources res = mContext.getResources();
@@ -1454,91 +1907,287 @@
}
}
+ private void loadBrightnessChangeThresholdsFromXml() {
+ loadBrightnessChangeThresholds(/* config= */ null);
+ }
+
private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
- Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds();
- Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds();
- Thresholds displayBrightnessThresholdsIdle =
- config.getDisplayBrightnessChangeThresholdsIdle();
- Thresholds ambientBrightnessThresholdsIdle =
- config.getAmbientBrightnessChangeThresholdsIdle();
-
- loadDisplayBrightnessThresholds(displayBrightnessThresholds);
- loadAmbientBrightnessThresholds(ambientBrightnessThresholds);
- loadIdleDisplayBrightnessThresholds(displayBrightnessThresholdsIdle);
- loadIdleAmbientBrightnessThresholds(ambientBrightnessThresholdsIdle);
+ loadDisplayBrightnessThresholds(config);
+ loadAmbientBrightnessThresholds(config);
+ loadDisplayBrightnessThresholdsIdle(config);
+ loadAmbientBrightnessThresholdsIdle(config);
}
- private void loadDisplayBrightnessThresholds(Thresholds displayBrightnessThresholds) {
- if (displayBrightnessThresholds != null) {
- BrightnessThresholds brighteningScreen =
- displayBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningScreen =
- displayBrightnessThresholds.getDarkeningThresholds();
+ private void loadDisplayBrightnessThresholds(DisplayConfiguration config) {
+ BrightnessThresholds brighteningScreen = null;
+ BrightnessThresholds darkeningScreen = null;
+ if (config != null && config.getDisplayBrightnessChangeThresholds() != null) {
+ brighteningScreen =
+ config.getDisplayBrightnessChangeThresholds().getBrighteningThresholds();
+ darkeningScreen =
+ config.getDisplayBrightnessChangeThresholds().getDarkeningThresholds();
- if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
- mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
- }
- if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
- mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
- }
+ }
+
+ // Screen bright/darkening threshold levels for active mode
+ Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningScreen,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenBrighteningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+
+ mScreenBrighteningLevels = screenBrighteningPair.first;
+ mScreenBrighteningPercentages = screenBrighteningPair.second;
+
+ Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningScreen,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenDarkeningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+ mScreenDarkeningLevels = screenDarkeningPair.first;
+ mScreenDarkeningPercentages = screenDarkeningPair.second;
+
+ // Screen bright/darkening threshold minimums for active mode
+ if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
+ mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
+ }
+ if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
+ mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
}
}
- private void loadAmbientBrightnessThresholds(Thresholds ambientBrightnessThresholds) {
- if (ambientBrightnessThresholds != null) {
- BrightnessThresholds brighteningAmbientLux =
- ambientBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningAmbientLux =
- ambientBrightnessThresholds.getDarkeningThresholds();
+ private void loadAmbientBrightnessThresholds(DisplayConfiguration config) {
+ // Ambient Brightness Threshold Levels
+ BrightnessThresholds brighteningAmbientLux = null;
+ BrightnessThresholds darkeningAmbientLux = null;
+ if (config != null && config.getAmbientBrightnessChangeThresholds() != null) {
+ brighteningAmbientLux =
+ config.getAmbientBrightnessChangeThresholds().getBrighteningThresholds();
+ darkeningAmbientLux =
+ config.getAmbientBrightnessChangeThresholds().getDarkeningThresholds();
+ }
- final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum();
- final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();
+ // Ambient bright/darkening threshold levels for active mode
+ Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningAmbientLux,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientBrighteningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
+ mAmbientBrighteningLevels = ambientBrighteningPair.first;
+ mAmbientBrighteningPercentages = ambientBrighteningPair.second;
- if (ambientBrighteningThreshold != null) {
- mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue();
- }
- if (ambientDarkeningThreshold != null) {
- mAmbientLuxDarkeningMinThreshold = ambientDarkeningThreshold.floatValue();
- }
+ Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningAmbientLux,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientDarkeningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
+ mAmbientDarkeningLevels = ambientDarkeningPair.first;
+ mAmbientDarkeningPercentages = ambientDarkeningPair.second;
+
+ // Ambient bright/darkening threshold minimums for active/idle mode
+ if (brighteningAmbientLux != null && brighteningAmbientLux.getMinimum() != null) {
+ mAmbientLuxBrighteningMinThreshold =
+ brighteningAmbientLux.getMinimum().floatValue();
+ }
+
+ if (darkeningAmbientLux != null && darkeningAmbientLux.getMinimum() != null) {
+ mAmbientLuxDarkeningMinThreshold = darkeningAmbientLux.getMinimum().floatValue();
}
}
- private void loadIdleDisplayBrightnessThresholds(Thresholds idleDisplayBrightnessThresholds) {
- if (idleDisplayBrightnessThresholds != null) {
- BrightnessThresholds brighteningScreenIdle =
- idleDisplayBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningScreenIdle =
- idleDisplayBrightnessThresholds.getDarkeningThresholds();
+ private void loadDisplayBrightnessThresholdsIdle(DisplayConfiguration config) {
+ BrightnessThresholds brighteningScreenIdle = null;
+ BrightnessThresholds darkeningScreenIdle = null;
+ if (config != null && config.getDisplayBrightnessChangeThresholdsIdle() != null) {
+ brighteningScreenIdle =
+ config.getDisplayBrightnessChangeThresholdsIdle().getBrighteningThresholds();
+ darkeningScreenIdle =
+ config.getDisplayBrightnessChangeThresholdsIdle().getDarkeningThresholds();
+ }
- if (brighteningScreenIdle != null
- && brighteningScreenIdle.getMinimum() != null) {
- mScreenBrighteningMinThresholdIdle =
- brighteningScreenIdle.getMinimum().floatValue();
- }
- if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
- mScreenDarkeningMinThresholdIdle =
- darkeningScreenIdle.getMinimum().floatValue();
- }
+ Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningScreenIdle,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenBrighteningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+ mScreenBrighteningLevelsIdle = screenBrighteningPair.first;
+ mScreenBrighteningPercentagesIdle = screenBrighteningPair.second;
+
+ Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningScreenIdle,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenDarkeningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+ mScreenDarkeningLevelsIdle = screenDarkeningPair.first;
+ mScreenDarkeningPercentagesIdle = screenDarkeningPair.second;
+
+ if (brighteningScreenIdle != null
+ && brighteningScreenIdle.getMinimum() != null) {
+ mScreenBrighteningMinThresholdIdle =
+ brighteningScreenIdle.getMinimum().floatValue();
+ }
+ if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
+ mScreenDarkeningMinThresholdIdle =
+ darkeningScreenIdle.getMinimum().floatValue();
}
}
- private void loadIdleAmbientBrightnessThresholds(Thresholds idleAmbientBrightnessThresholds) {
- if (idleAmbientBrightnessThresholds != null) {
- BrightnessThresholds brighteningAmbientLuxIdle =
- idleAmbientBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningAmbientLuxIdle =
- idleAmbientBrightnessThresholds.getDarkeningThresholds();
+ private void loadAmbientBrightnessThresholdsIdle(DisplayConfiguration config) {
+ BrightnessThresholds brighteningAmbientLuxIdle = null;
+ BrightnessThresholds darkeningAmbientLuxIdle = null;
+ if (config != null && config.getAmbientBrightnessChangeThresholdsIdle() != null) {
+ brighteningAmbientLuxIdle =
+ config.getAmbientBrightnessChangeThresholdsIdle().getBrighteningThresholds();
+ darkeningAmbientLuxIdle =
+ config.getAmbientBrightnessChangeThresholdsIdle().getDarkeningThresholds();
+ }
- if (brighteningAmbientLuxIdle != null
- && brighteningAmbientLuxIdle.getMinimum() != null) {
- mAmbientLuxBrighteningMinThresholdIdle =
- brighteningAmbientLuxIdle.getMinimum().floatValue();
+ Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningAmbientLuxIdle,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientBrighteningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
+ mAmbientBrighteningLevelsIdle = ambientBrighteningPair.first;
+ mAmbientBrighteningPercentagesIdle = ambientBrighteningPair.second;
+
+ Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningAmbientLuxIdle,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientDarkeningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
+ mAmbientDarkeningLevelsIdle = ambientDarkeningPair.first;
+ mAmbientDarkeningPercentagesIdle = ambientDarkeningPair.second;
+
+ if (brighteningAmbientLuxIdle != null
+ && brighteningAmbientLuxIdle.getMinimum() != null) {
+ mAmbientLuxBrighteningMinThresholdIdle =
+ brighteningAmbientLuxIdle.getMinimum().floatValue();
+ }
+
+ if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
+ mAmbientLuxDarkeningMinThresholdIdle =
+ darkeningAmbientLuxIdle.getMinimum().floatValue();
+ }
+ }
+
+ private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
+ int configFallbackThreshold, int configFallbackPercentage, float[] defaultLevels,
+ float[] defaultPercentage) {
+ return getBrightnessLevelAndPercentage(thresholds, configFallbackThreshold,
+ configFallbackPercentage, defaultLevels, defaultPercentage, false);
+ }
+
+ // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
+ // percentages for brightness levels at or above the lux value.
+ // Historically, config.xml would have an array for brightness levels that was 1 shorter than
+ // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
+ // rest of the framework. Values were also defined in different units (permille vs percent).
+ private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
+ int configFallbackThreshold, int configFallbackPermille,
+ float[] defaultLevels, float[] defaultPercentage,
+ boolean potentialOldBrightnessScale) {
+ if (thresholds != null
+ && thresholds.getBrightnessThresholdPoints() != null
+ && thresholds.getBrightnessThresholdPoints()
+ .getBrightnessThresholdPoint().size() != 0) {
+
+ // The level and percentages arrays are equal length in the ddc (new system)
+ List<ThresholdPoint> points =
+ thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
+ final int size = points.size();
+
+ float[] thresholdLevels = new float[size];
+ float[] thresholdPercentages = new float[size];
+
+ int i = 0;
+ for (ThresholdPoint point : points) {
+ thresholdLevels[i] = point.getThreshold().floatValue();
+ thresholdPercentages[i] = point.getPercentage().floatValue();
+ i++;
}
- if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
- mAmbientLuxDarkeningMinThresholdIdle =
- darkeningAmbientLuxIdle.getMinimum().floatValue();
+ return new Pair<>(thresholdLevels, thresholdPercentages);
+ } else {
+ // The level and percentages arrays are unequal length in config.xml (old system)
+ // We prefix the array with a 0 value to ensure they can be handled consistently
+ // with the new system.
+
+ // Load levels array
+ int[] configThresholdArray = mContext.getResources().getIntArray(
+ configFallbackThreshold);
+ int configThresholdsSize;
+ if (configThresholdArray == null || configThresholdArray.length == 0) {
+ configThresholdsSize = 1;
+ } else {
+ configThresholdsSize = configThresholdArray.length + 1;
+ }
+
+
+ // Load percentage array
+ int[] configPermille = mContext.getResources().getIntArray(
+ configFallbackPermille);
+
+ // Ensure lengths match up
+ boolean emptyArray = configPermille == null || configPermille.length == 0;
+ if (emptyArray && configThresholdsSize == 1) {
+ return new Pair<>(defaultLevels, defaultPercentage);
+ }
+ if (emptyArray || configPermille.length != configThresholdsSize) {
+ throw new IllegalArgumentException(
+ "Brightness threshold arrays do not align in length");
+ }
+
+ // Calculate levels array
+ float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
+ // Start at 1, so that 0 index value is 0.0f (default)
+ for (int i = 1; i < configThresholdsSize; i++) {
+ configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
+ }
+ if (potentialOldBrightnessScale) {
+ configThresholdWithZeroPrefixed =
+ constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
+ }
+
+ // Calculate percentages array
+ float[] configPercentage = new float[configThresholdsSize];
+ for (int i = 0; i < configPermille.length; i++) {
+ configPercentage[i] = configPermille[i] / 10.0f;
+ }
+ return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
+ }
+ }
+
+ /**
+ * This check is due to historical reasons, where screen thresholdLevels used to be
+ * integer values in the range of [0-255], but then was changed to be float values from [0,1].
+ * To accommodate both the possibilities, we first check if all the thresholdLevels are in
+ * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
+ */
+ private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
+ if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
+ /* maxValueInclusive= */ 1.0f)) {
+ return thresholdLevels;
+ }
+
+ Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
+ float[] thresholdLevelsScaled = new float[thresholdLevels.length];
+ for (int index = 0; thresholdLevels.length > index; ++index) {
+ thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
+ }
+ return thresholdLevelsScaled;
+ }
+
+ private boolean isAllInRange(float[] configArray, float minValueInclusive,
+ float maxValueInclusive) {
+ for (float v : configArray) {
+ if (v < minValueInclusive || v > maxValueInclusive) {
+ return false;
}
}
+ return true;
}
private boolean thermalStatusIsValid(ThermalStatus value) {
@@ -1634,6 +2283,20 @@
return levels;
}
+ private void loadEnableAutoBrightness(AutoBrightness autobrightness) {
+ // mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the
+ // config.xml values if the autobrightness tag is not defined in the ddc file.
+ // Autobrightness can still be turned off globally via config_automatic_brightness_available
+ mDdcAutoBrightnessAvailable = true;
+ if (autobrightness != null) {
+ mDdcAutoBrightnessAvailable = autobrightness.getEnabled();
+ }
+
+ mAutoBrightnessAvailable = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_automatic_brightness_available)
+ && mDdcAutoBrightnessAvailable;
+ }
+
static class SensorData {
public String type;
public String name;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b153c1b..a1490e5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1759,9 +1759,13 @@
if (displayDevice == null) {
return;
}
- mPersistentDataStore.setUserPreferredResolution(
- displayDevice, resolutionWidth, resolutionHeight);
- mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate);
+ try {
+ mPersistentDataStore.setUserPreferredResolution(
+ displayDevice, resolutionWidth, resolutionHeight);
+ mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate);
+ } finally {
+ mPersistentDataStore.saveIfNeeded();
+ }
}
private void setUserPreferredModeForDisplayLocked(int displayId, Display.Mode mode) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index efd2e34..95dc23f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -558,13 +558,6 @@
mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
- // Check the setting, but also verify that it is the default display. Only the default
- // display has an automatic brightness controller running.
- // TODO: b/179021925 - Fix to work with multiple displays
- mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
- com.android.internal.R.bool.config_automatic_brightness_available)
- && mDisplayId == Display.DEFAULT_DISPLAY;
-
mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
@@ -938,6 +931,8 @@
}
private void setUpAutoBrightness(Resources resources, Handler handler) {
+ mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
+
if (!mUseSoftwareAutoBrightnessConfig) {
return;
}
@@ -956,53 +951,77 @@
com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
1, 1);
- int[] ambientBrighteningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_ambientBrighteningThresholds);
- int[] ambientDarkeningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_ambientDarkeningThresholds);
- int[] ambientThresholdLevels = resources.getIntArray(
- com.android.internal.R.array.config_ambientThresholdLevels);
+ // Ambient Lux - Active Mode Brightness Thresholds
+ float[] ambientBrighteningThresholds =
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages();
+ float[] ambientDarkeningThresholds =
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages();
+ float[] ambientBrighteningLevels =
+ mDisplayDeviceConfig.getAmbientBrighteningLevels();
+ float[] ambientDarkeningLevels =
+ mDisplayDeviceConfig.getAmbientDarkeningLevels();
float ambientDarkeningMinThreshold =
mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
float ambientBrighteningMinThreshold =
mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
ambientBrighteningThresholds, ambientDarkeningThresholds,
- ambientThresholdLevels, ambientDarkeningMinThreshold,
+ ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
ambientBrighteningMinThreshold);
- int[] screenBrighteningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_screenBrighteningThresholds);
- int[] screenDarkeningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_screenDarkeningThresholds);
- float[] screenThresholdLevels = BrightnessMappingStrategy.getFloatArray(resources
- .obtainTypedArray(com.android.internal.R.array.config_screenThresholdLevels));
+ // Display - Active Mode Brightness Thresholds
+ float[] screenBrighteningThresholds =
+ mDisplayDeviceConfig.getScreenBrighteningPercentages();
+ float[] screenDarkeningThresholds =
+ mDisplayDeviceConfig.getScreenDarkeningPercentages();
+ float[] screenBrighteningLevels =
+ mDisplayDeviceConfig.getScreenBrighteningLevels();
+ float[] screenDarkeningLevels =
+ mDisplayDeviceConfig.getScreenDarkeningLevels();
float screenDarkeningMinThreshold =
mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
float screenBrighteningMinThreshold =
mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
- screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
- screenDarkeningMinThreshold, screenBrighteningMinThreshold);
+ screenBrighteningThresholds, screenDarkeningThresholds,
+ screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
+ screenBrighteningMinThreshold);
- // Idle screen thresholds
- float screenDarkeningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
- float screenBrighteningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
- HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
- screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
- screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
-
- // Idle ambient thresholds
+ // Ambient Lux - Idle Screen Brightness Thresholds
float ambientDarkeningMinThresholdIdle =
mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
float ambientBrighteningMinThresholdIdle =
mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+ float[] ambientBrighteningThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
+ float[] ambientDarkeningThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
+ float[] ambientBrighteningLevelsIdle =
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
+ float[] ambientDarkeningLevelsIdle =
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
- ambientBrighteningThresholds, ambientDarkeningThresholds,
- ambientThresholdLevels, ambientDarkeningMinThresholdIdle,
- ambientBrighteningMinThresholdIdle);
+ ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
+ ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
+ ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
+
+ // Display - Idle Screen Brightness Thresholds
+ float screenDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+ float screenBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+ float[] screenBrighteningThresholdsIdle =
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
+ float[] screenDarkeningThresholdsIdle =
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
+ float[] screenBrighteningLevelsIdle =
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
+ float[] screenDarkeningLevelsIdle =
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
+ HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+ screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
+ screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
+ screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
long brighteningLightDebounce = mDisplayDeviceConfig
.getAutoBrightnessBrighteningLightDebounce();
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index abf8fe3..faa4c3d 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -29,52 +29,38 @@
private static final boolean DEBUG = false;
- private final float[] mBrighteningThresholds;
- private final float[] mDarkeningThresholds;
- private final float[] mThresholdLevels;
+ private final float[] mBrighteningThresholdsPercentages;
+ private final float[] mDarkeningThresholdsPercentages;
+ private final float[] mBrighteningThresholdLevels;
+ private final float[] mDarkeningThresholdLevels;
private final float mMinDarkening;
private final float mMinBrightening;
/**
- * Creates a {@code HysteresisLevels} object for ambient brightness.
- * @param brighteningThresholds an array of brightening hysteresis constraint constants.
- * @param darkeningThresholds an array of darkening hysteresis constraint constants.
- * @param thresholdLevels a monotonically increasing array of threshold levels.
- * @param minBrighteningThreshold the minimum value for which the brightening value needs to
- * return.
- * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
- */
- HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
- int[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
- if (brighteningThresholds.length != darkeningThresholds.length
- || darkeningThresholds.length != thresholdLevels.length + 1) {
- throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
- }
- mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
- mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
- mThresholdLevels = setArrayFormat(thresholdLevels, 1.0f);
- mMinDarkening = minDarkeningThreshold;
- mMinBrightening = minBrighteningThreshold;
- }
-
- /**
- * Creates a {@code HysteresisLevels} object for screen brightness.
- * @param brighteningThresholds an array of brightening hysteresis constraint constants.
- * @param darkeningThresholds an array of darkening hysteresis constraint constants.
- * @param thresholdLevels a monotonically increasing array of threshold levels.
+ * Creates a {@code HysteresisLevels} object with the given equal-length
+ * float arrays.
+ * @param brighteningThresholdsPercentages 0-100 of thresholds
+ * @param darkeningThresholdsPercentages 0-100 of thresholds
+ * @param brighteningThresholdLevels float array of brightness values in the relevant units
+ * @param darkeningThresholdLevels float array of brightness values in the relevant units
* @param minBrighteningThreshold the minimum value for which the brightening value needs to
* return.
* @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
*/
- HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
- float[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
- if (brighteningThresholds.length != darkeningThresholds.length
- || darkeningThresholds.length != thresholdLevels.length + 1) {
+ HysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages,
+ float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
+ float minDarkeningThreshold, float minBrighteningThreshold) {
+ if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
+ || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
}
- mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
- mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
- mThresholdLevels = constraintInRangeIfNeeded(thresholdLevels);
+ mBrighteningThresholdsPercentages =
+ setArrayFormat(brighteningThresholdsPercentages, 100.0f);
+ mDarkeningThresholdsPercentages =
+ setArrayFormat(darkeningThresholdsPercentages, 100.0f);
+ mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
+ mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
mMinDarkening = minDarkeningThreshold;
mMinBrightening = minBrighteningThreshold;
}
@@ -83,7 +69,9 @@
* Return the brightening hysteresis threshold for the given value level.
*/
public float getBrighteningThreshold(float value) {
- final float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
+ final float brightConstant = getReferenceLevel(value,
+ mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
+
float brightThreshold = value * (1.0f + brightConstant);
if (DEBUG) {
Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
@@ -98,7 +86,8 @@
* Return the darkening hysteresis threshold for the given value level.
*/
public float getDarkeningThreshold(float value) {
- final float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
+ final float darkConstant = getReferenceLevel(value,
+ mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
float darkThreshold = value * (1.0f - darkConstant);
if (DEBUG) {
Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
@@ -111,60 +100,39 @@
/**
* Return the hysteresis constant for the closest threshold value from the given array.
*/
- private float getReferenceLevel(float value, float[] referenceLevels) {
- int index = 0;
- while (mThresholdLevels.length > index && value >= mThresholdLevels[index]) {
- ++index;
+ private float getReferenceLevel(float value, float[] thresholdLevels,
+ float[] thresholdPercentages) {
+ if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
+ return 0.0f;
}
- return referenceLevels[index];
+ int index = 0;
+ while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
+ index++;
+ }
+ return thresholdPercentages[index];
}
/**
* Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
*/
- private float[] setArrayFormat(int[] configArray, float divideFactor) {
+ private float[] setArrayFormat(float[] configArray, float divideFactor) {
float[] levelArray = new float[configArray.length];
for (int index = 0; levelArray.length > index; ++index) {
- levelArray[index] = (float) configArray[index] / divideFactor;
+ levelArray[index] = configArray[index] / divideFactor;
}
return levelArray;
}
- /**
- * This check is due to historical reasons, where screen thresholdLevels used to be
- * integer values in the range of [0-255], but then was changed to be float values from [0,1].
- * To accommodate both the possibilities, we first check if all the thresholdLevels are in [0,
- * 1], and if not, we divide all the levels with 255 to bring them down to the same scale.
- */
- private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
- if (isAllInRange(thresholdLevels, /* minValueInclusive = */ 0.0f, /* maxValueInclusive = */
- 1.0f)) {
- return thresholdLevels;
- }
-
- Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
- float[] thresholdLevelsScaled = new float[thresholdLevels.length];
- for (int index = 0; thresholdLevels.length > index; ++index) {
- thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
- }
- return thresholdLevelsScaled;
- }
-
- private boolean isAllInRange(float[] configArray, float minValueInclusive,
- float maxValueInclusive) {
- int configArraySize = configArray.length;
- for (int index = 0; configArraySize > index; ++index) {
- if (configArray[index] < minValueInclusive || configArray[index] > maxValueInclusive) {
- return false;
- }
- }
- return true;
- }
void dump(PrintWriter pw) {
pw.println("HysteresisLevels");
- pw.println(" mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds));
- pw.println(" mDarkeningThresholds=" + Arrays.toString(mDarkeningThresholds));
- pw.println(" mThresholdLevels=" + Arrays.toString(mThresholdLevels));
+ pw.println(" mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels));
+ pw.println(" mBrighteningThresholdsPercentages="
+ + Arrays.toString(mBrighteningThresholdsPercentages));
+ pw.println(" mMinBrightening=" + mMinBrightening);
+ pw.println(" mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels));
+ pw.println(" mDarkeningThresholdsPercentages="
+ + Arrays.toString(mDarkeningThresholdsPercentages));
+ pw.println(" mMinDarkening=" + mMinDarkening);
}
}
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index b9a0738..a11f172 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -75,6 +75,11 @@
* </brightness-curve>
* </brightness-configuration>
* </brightness-configurations>
+ * <display-mode>0<
+ * <resolution-width>1080</resolution-width>
+ * <resolution-height>1920</resolution-height>
+ * <refresh-rate>60</refresh-rate>
+ * </display-mode>
* </display>
* </display-states>
* <stable-device-values>
@@ -121,6 +126,10 @@
private static final String ATTR_PACKAGE_NAME = "package-name";
private static final String ATTR_TIME_STAMP = "timestamp";
+ private static final String TAG_RESOLUTION_WIDTH = "resolution-width";
+ private static final String TAG_RESOLUTION_HEIGHT = "resolution-height";
+ private static final String TAG_REFRESH_RATE = "refresh-rate";
+
// Remembered Wifi display devices.
private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
@@ -696,6 +705,18 @@
case TAG_BRIGHTNESS_CONFIGURATIONS:
mDisplayBrightnessConfigurations.loadFromXml(parser);
break;
+ case TAG_RESOLUTION_WIDTH:
+ String width = parser.nextText();
+ mWidth = Integer.parseInt(width);
+ break;
+ case TAG_RESOLUTION_HEIGHT:
+ String height = parser.nextText();
+ mHeight = Integer.parseInt(height);
+ break;
+ case TAG_REFRESH_RATE:
+ String refreshRate = parser.nextText();
+ mRefreshRate = Float.parseFloat(refreshRate);
+ break;
}
}
}
@@ -712,6 +733,18 @@
serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
mDisplayBrightnessConfigurations.saveToXml(serializer);
serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
+
+ serializer.startTag(null, TAG_RESOLUTION_WIDTH);
+ serializer.text(Integer.toString(mWidth));
+ serializer.endTag(null, TAG_RESOLUTION_WIDTH);
+
+ serializer.startTag(null, TAG_RESOLUTION_HEIGHT);
+ serializer.text(Integer.toString(mHeight));
+ serializer.endTag(null, TAG_RESOLUTION_HEIGHT);
+
+ serializer.startTag(null, TAG_REFRESH_RATE);
+ serializer.text(Float.toString(mRefreshRate));
+ serializer.endTag(null, TAG_REFRESH_RATE);
}
public void dump(final PrintWriter pw, final String prefix) {
@@ -719,6 +752,8 @@
pw.println(prefix + "BrightnessValue=" + mBrightness);
pw.println(prefix + "DisplayBrightnessConfigurations: ");
mDisplayBrightnessConfigurations.dump(pw, prefix);
+ pw.println(prefix + "Resolution=" + mWidth + " " + mHeight);
+ pw.println(prefix + "RefreshRate=" + mRefreshRate);
}
}
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 4e4f454..b8af1bf 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -118,7 +118,7 @@
public void startDream(Binder token, ComponentName name,
boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock,
- ComponentName overlayComponentName) {
+ ComponentName overlayComponentName, String reason) {
stopDream(true /*immediate*/, "starting new dream");
Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
@@ -128,7 +128,7 @@
Slog.i(TAG, "Starting dream: name=" + name
+ ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze
- + ", userId=" + userId);
+ + ", userId=" + userId + ", reason='" + reason + "'");
mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock);
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index fee1f5c..c9557d6 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -60,6 +60,7 @@
import android.view.Display;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.util.DumpUtils;
@@ -84,6 +85,9 @@
private static final boolean DEBUG = false;
private static final String TAG = "DreamManagerService";
+ private static final String DOZE_WAKE_LOCK_TAG = "dream:doze";
+ private static final String DREAM_WAKE_LOCK_TAG = "dream:dream";
+
private final Object mLock = new Object();
private final Context mContext;
@@ -98,17 +102,11 @@
private final ComponentName mAmbientDisplayComponent;
private final boolean mDismissDreamOnActivityStart;
- private Binder mCurrentDreamToken;
- private ComponentName mCurrentDreamName;
- private int mCurrentDreamUserId;
- private boolean mCurrentDreamIsPreview;
- private boolean mCurrentDreamCanDoze;
- private boolean mCurrentDreamIsDozing;
- private boolean mCurrentDreamIsWaking;
+ @GuardedBy("mLock")
+ private DreamRecord mCurrentDream;
+
private boolean mForceAmbientDisplayEnabled;
- private boolean mDreamsOnlyEnabledForSystemUser;
- private int mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
- private int mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+ private final boolean mDreamsOnlyEnabledForSystemUser;
// A temporary dream component that, when present, takes precedence over user configured dream
// component.
@@ -116,7 +114,7 @@
private ComponentName mDreamOverlayServiceName;
- private AmbientDisplayConfiguration mDozeConfig;
+ private final AmbientDisplayConfiguration mDozeConfig;
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
@Nullable
@@ -132,8 +130,14 @@
final boolean activityAllowed = activityType == ACTIVITY_TYPE_HOME
|| activityType == ACTIVITY_TYPE_DREAM
|| activityType == ACTIVITY_TYPE_ASSISTANT;
- if (mCurrentDreamToken != null && !mCurrentDreamIsWaking
- && !mCurrentDreamIsDozing && !activityAllowed) {
+
+ boolean shouldRequestAwaken;
+ synchronized (mLock) {
+ shouldRequestAwaken = mCurrentDream != null && !mCurrentDream.isWaking
+ && !mCurrentDream.isDozing && !activityAllowed;
+ }
+
+ if (shouldRequestAwaken) {
requestAwakenInternal(
"stopping dream due to activity start: " + activityInfo.name);
}
@@ -149,7 +153,7 @@
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
mAtmInternal = getLocalService(ActivityTaskManagerInternal.class);
- mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG);
+ mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG);
mDozeConfig = new AmbientDisplayConfiguration(mContext);
mUiEventLogger = new UiEventLoggerImpl();
mDreamUiEventLogger = new DreamUiEventLoggerImpl(
@@ -197,43 +201,38 @@
}
private void dumpInternal(PrintWriter pw) {
- pw.println("DREAM MANAGER (dumpsys dreams)");
- pw.println();
- pw.println("mCurrentDreamToken=" + mCurrentDreamToken);
- pw.println("mCurrentDreamName=" + mCurrentDreamName);
- pw.println("mCurrentDreamUserId=" + mCurrentDreamUserId);
- pw.println("mCurrentDreamIsPreview=" + mCurrentDreamIsPreview);
- pw.println("mCurrentDreamCanDoze=" + mCurrentDreamCanDoze);
- pw.println("mCurrentDreamIsDozing=" + mCurrentDreamIsDozing);
- pw.println("mCurrentDreamIsWaking=" + mCurrentDreamIsWaking);
- pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
- pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
- pw.println("mCurrentDreamDozeScreenState="
- + Display.stateToString(mCurrentDreamDozeScreenState));
- pw.println("mCurrentDreamDozeScreenBrightness=" + mCurrentDreamDozeScreenBrightness);
- pw.println("getDozeComponent()=" + getDozeComponent());
- pw.println();
+ synchronized (mLock) {
+ pw.println("DREAM MANAGER (dumpsys dreams)");
+ pw.println();
+ pw.println("mCurrentDream=" + mCurrentDream);
+ pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
+ pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+ pw.println("getDozeComponent()=" + getDozeComponent());
+ pw.println();
- DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() {
- @Override
- public void dump(PrintWriter pw, String prefix) {
- mController.dump(pw);
- }
- }, pw, "", 200);
+ DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> mController.dump(pw1), pw, "", 200);
+ }
}
/** Whether a real dream is occurring. */
private boolean isDreamingInternal() {
synchronized (mLock) {
- return mCurrentDreamToken != null && !mCurrentDreamIsPreview
- && !mCurrentDreamIsWaking;
+ return mCurrentDream != null && !mCurrentDream.isPreview
+ && !mCurrentDream.isWaking;
+ }
+ }
+
+ /** Whether a doze is occurring. */
+ private boolean isDozingInternal() {
+ synchronized (mLock) {
+ return mCurrentDream != null && mCurrentDream.isDozing;
}
}
/** Whether a real dream, or a dream preview is occurring. */
private boolean isDreamingOrInPreviewInternal() {
synchronized (mLock) {
- return mCurrentDreamToken != null && !mCurrentDreamIsWaking;
+ return mCurrentDream != null && !mCurrentDream.isWaking;
}
}
@@ -247,8 +246,8 @@
// Because napping could cause the screen to turn off immediately if the dream
// cannot be started, we keep one eye open and gently poke user activity.
long time = SystemClock.uptimeMillis();
- mPowerManager.userActivity(time, true /*noChangeLights*/);
- mPowerManager.nap(time);
+ mPowerManager.userActivity(time, /* noChangeLights= */ true);
+ mPowerManagerInternal.nap(time, /* allowWake= */ true);
}
private void requestAwakenInternal(String reason) {
@@ -273,7 +272,7 @@
// locks are held and the user activity timeout has expired then the
// device may simply go to sleep.
synchronized (mLock) {
- if (mCurrentDreamToken == token) {
+ if (mCurrentDream != null && mCurrentDream.token == token) {
stopDreamLocked(immediate, "finished self");
}
}
@@ -281,16 +280,17 @@
private void testDreamInternal(ComponentName dream, int userId) {
synchronized (mLock) {
- startDreamLocked(dream, true /*isPreviewMode*/, false /*canDoze*/, userId);
+ startDreamLocked(dream, true /*isPreviewMode*/, false /*canDoze*/, userId,
+ "test dream" /*reason*/);
}
}
- private void startDreamInternal(boolean doze) {
+ private void startDreamInternal(boolean doze, String reason) {
final int userId = ActivityManager.getCurrentUser();
final ComponentName dream = chooseDreamForUser(doze, userId);
if (dream != null) {
synchronized (mLock) {
- startDreamLocked(dream, false /*isPreviewMode*/, doze, userId);
+ startDreamLocked(dream, false /*isPreviewMode*/, doze, userId, reason);
}
}
}
@@ -314,13 +314,13 @@
}
synchronized (mLock) {
- if (mCurrentDreamToken == token && mCurrentDreamCanDoze) {
- mCurrentDreamDozeScreenState = screenState;
- mCurrentDreamDozeScreenBrightness = screenBrightness;
+ if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.canDoze) {
+ mCurrentDream.dozeScreenState = screenState;
+ mCurrentDream.dozeScreenBrightness = screenBrightness;
mPowerManagerInternal.setDozeOverrideFromDreamManager(
screenState, screenBrightness);
- if (!mCurrentDreamIsDozing) {
- mCurrentDreamIsDozing = true;
+ if (!mCurrentDream.isDozing) {
+ mCurrentDream.isDozing = true;
mDozeWakeLock.acquire();
}
}
@@ -333,8 +333,8 @@
}
synchronized (mLock) {
- if (mCurrentDreamToken == token && mCurrentDreamIsDozing) {
- mCurrentDreamIsDozing = false;
+ if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.isDozing) {
+ mCurrentDream.isDozing = false;
mDozeWakeLock.release();
mPowerManagerInternal.setDozeOverrideFromDreamManager(
Display.STATE_UNKNOWN, PowerManager.BRIGHTNESS_DEFAULT);
@@ -403,7 +403,7 @@
ComponentName[] components = componentsFromString(names);
// first, ensure components point to valid services
- List<ComponentName> validComponents = new ArrayList<ComponentName>();
+ List<ComponentName> validComponents = new ArrayList<>();
if (components != null) {
for (ComponentName component : components) {
if (validateDream(component)) {
@@ -439,8 +439,9 @@
mSystemDreamComponent = componentName;
// Switch dream if currently dreaming and not dozing.
- if (isDreamingInternal() && !mCurrentDreamIsDozing) {
- startDreamInternal(false);
+ if (isDreamingInternal() && !isDozingInternal()) {
+ startDreamInternal(false /*doze*/, (mSystemDreamComponent == null ? "clear" : "set")
+ + " system dream component" /*reason*/);
}
}
}
@@ -478,13 +479,16 @@
}
}
+ @GuardedBy("mLock")
private void startDreamLocked(final ComponentName name,
- final boolean isPreviewMode, final boolean canDoze, final int userId) {
- if (!mCurrentDreamIsWaking
- && Objects.equals(mCurrentDreamName, name)
- && mCurrentDreamIsPreview == isPreviewMode
- && mCurrentDreamCanDoze == canDoze
- && mCurrentDreamUserId == userId) {
+ final boolean isPreviewMode, final boolean canDoze, final int userId,
+ final String reason) {
+ if (mCurrentDream != null
+ && !mCurrentDream.isWaking
+ && Objects.equals(mCurrentDream.name, name)
+ && mCurrentDream.isPreview == isPreviewMode
+ && mCurrentDream.canDoze == canDoze
+ && mCurrentDream.userId == userId) {
Slog.i(TAG, "Already in target dream.");
return;
}
@@ -493,73 +497,60 @@
Slog.i(TAG, "Entering dreamland.");
- final Binder newToken = new Binder();
- mCurrentDreamToken = newToken;
- mCurrentDreamName = name;
- mCurrentDreamIsPreview = isPreviewMode;
- mCurrentDreamCanDoze = canDoze;
- mCurrentDreamUserId = userId;
+ mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze);
- if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
+ if (!mCurrentDream.name.equals(mAmbientDisplayComponent)) {
// TODO(b/213906448): Remove when metrics based on new atom are fully rolled out.
mUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_START);
mDreamUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_START,
- mCurrentDreamName.flattenToString());
+ mCurrentDream.name.flattenToString());
}
PowerManager.WakeLock wakeLock = mPowerManager
- .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream");
+ .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, DREAM_WAKE_LOCK_TAG);
+ final Binder dreamToken = mCurrentDream.token;
mHandler.post(wakeLock.wrap(() -> {
mAtmInternal.notifyDreamStateChanged(true);
- mController.startDream(newToken, name, isPreviewMode, canDoze, userId, wakeLock,
- mDreamOverlayServiceName);
+ mController.startDream(dreamToken, name, isPreviewMode, canDoze, userId, wakeLock,
+ mDreamOverlayServiceName, reason);
}));
}
+ @GuardedBy("mLock")
private void stopDreamLocked(final boolean immediate, String reason) {
- if (mCurrentDreamToken != null) {
+ if (mCurrentDream != null) {
if (immediate) {
Slog.i(TAG, "Leaving dreamland.");
cleanupDreamLocked();
- } else if (mCurrentDreamIsWaking) {
+ } else if (mCurrentDream.isWaking) {
return; // already waking
} else {
Slog.i(TAG, "Gently waking up from dream.");
- mCurrentDreamIsWaking = true;
+ mCurrentDream.isWaking = true;
}
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Slog.i(TAG, "Performing gentle wake from dream.");
- mController.stopDream(immediate, reason);
- }
- });
+ mHandler.post(() -> mController.stopDream(immediate, reason));
}
}
+ @GuardedBy("mLock")
private void cleanupDreamLocked() {
- if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
+ mHandler.post(() -> mAtmInternal.notifyDreamStateChanged(false /*dreaming*/));
+
+ if (mCurrentDream == null) {
+ return;
+ }
+
+ if (!mCurrentDream.name.equals(mAmbientDisplayComponent)) {
// TODO(b/213906448): Remove when metrics based on new atom are fully rolled out.
mUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_STOP);
mDreamUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_STOP,
- mCurrentDreamName.flattenToString());
+ mCurrentDream.name.flattenToString());
}
- mCurrentDreamToken = null;
- mCurrentDreamName = null;
- mCurrentDreamIsPreview = false;
- mCurrentDreamCanDoze = false;
- mCurrentDreamUserId = 0;
- mCurrentDreamIsWaking = false;
- if (mCurrentDreamIsDozing) {
- mCurrentDreamIsDozing = false;
+ if (mCurrentDream.isDozing) {
mDozeWakeLock.release();
}
- mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
- mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
- mHandler.post(() -> {
- mAtmInternal.notifyDreamStateChanged(false);
- });
+ mCurrentDream = null;
}
private void checkPermission(String permission) {
@@ -606,7 +597,7 @@
@Override
public void onDreamStopped(Binder token) {
synchronized (mLock) {
- if (mCurrentDreamToken == token) {
+ if (mCurrentDream != null && mCurrentDream.token == token) {
cleanupDreamLocked();
}
}
@@ -624,7 +615,7 @@
* Handler for asynchronous operations performed by the dream manager.
* Ensures operations to {@link DreamController} are single-threaded.
*/
- private final class DreamHandler extends Handler {
+ private static final class DreamHandler extends Handler {
public DreamHandler(Looper looper) {
super(looper, null, true /*async*/);
}
@@ -646,7 +637,7 @@
@Nullable FileDescriptor err,
@NonNull String[] args, @Nullable ShellCallback callback,
@NonNull ResultReceiver resultReceiver) throws RemoteException {
- new DreamShellCommand(DreamManagerService.this, mPowerManager)
+ new DreamShellCommand(DreamManagerService.this)
.exec(this, in, out, err, args, callback, resultReceiver);
}
@@ -865,13 +856,13 @@
private final class LocalService extends DreamManagerInternal {
@Override
- public void startDream(boolean doze) {
- startDreamInternal(doze);
+ public void startDream(boolean doze, String reason) {
+ startDreamInternal(doze, reason);
}
@Override
- public void stopDream(boolean immediate) {
- stopDreamInternal(immediate, "requested stopDream");
+ public void stopDream(boolean immediate, String reason) {
+ stopDreamInternal(immediate, reason);
}
@Override
@@ -890,13 +881,47 @@
}
}
+ private static final class DreamRecord {
+ public final Binder token = new Binder();
+ public final ComponentName name;
+ public final int userId;
+ public final boolean isPreview;
+ public final boolean canDoze;
+ public boolean isDozing = false;
+ public boolean isWaking = false;
+ public int dozeScreenState = Display.STATE_UNKNOWN;
+ public int dozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+
+ DreamRecord(ComponentName name, int userId, boolean isPreview, boolean canDoze) {
+ this.name = name;
+ this.userId = userId;
+ this.isPreview = isPreview;
+ this.canDoze = canDoze;
+ }
+
+ @Override
+ public String toString() {
+ return "DreamRecord{"
+ + "token=" + token
+ + ", name=" + name
+ + ", userId=" + userId
+ + ", isPreview=" + isPreview
+ + ", canDoze=" + canDoze
+ + ", isDozing=" + isDozing
+ + ", isWaking=" + isWaking
+ + ", dozeScreenState=" + dozeScreenState
+ + ", dozeScreenBrightness=" + dozeScreenBrightness
+ + '}';
+ }
+ }
+
private final Runnable mSystemPropertiesChanged = new Runnable() {
@Override
public void run() {
if (DEBUG) Slog.d(TAG, "System properties changed");
synchronized (mLock) {
- if (mCurrentDreamName != null && mCurrentDreamCanDoze
- && !mCurrentDreamName.equals(getDozeComponent())) {
+ if (mCurrentDream != null && mCurrentDream.name != null && mCurrentDream.canDoze
+ && !mCurrentDream.name.equals(getDozeComponent())) {
// May have updated the doze component, wake up
mPowerManager.wakeUp(SystemClock.uptimeMillis(),
"android.server.dreams:SYSPROP");
diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java
index eae7e80..ab84ae4 100644
--- a/services/core/java/com/android/server/dreams/DreamShellCommand.java
+++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java
@@ -18,10 +18,8 @@
import android.annotation.NonNull;
import android.os.Binder;
-import android.os.PowerManager;
import android.os.Process;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Slog;
@@ -34,11 +32,9 @@
private static final boolean DEBUG = true;
private static final String TAG = "DreamShellCommand";
private final @NonNull DreamManagerService mService;
- private final @NonNull PowerManager mPowerManager;
- DreamShellCommand(@NonNull DreamManagerService service, @NonNull PowerManager powerManager) {
+ DreamShellCommand(@NonNull DreamManagerService service) {
mService = service;
- mPowerManager = powerManager;
}
@Override
@@ -67,8 +63,6 @@
}
private int startDreaming() {
- mPowerManager.wakeUp(SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_PLUGGED_IN, "shell:cmd:android.service.dreams:DREAM");
mService.requestStartDreamFromShell();
return 0;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f11801f..fd1fdce 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4868,6 +4868,13 @@
}
@Override
+ public int getHintsFromListenerNoToken() {
+ synchronized (mNotificationLock) {
+ return mListenerHints;
+ }
+ }
+
+ @Override
public void requestInterruptionFilterFromListener(INotificationListener token,
int interruptionFilter) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 9a89efa..232a69b 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -177,11 +177,13 @@
NOTIFICATION_CANCEL_USER_PEEK(190),
@UiEvent(doc = "Notification was canceled due to user dismissal from the always-on display")
NOTIFICATION_CANCEL_USER_AOD(191),
+ @UiEvent(doc = "Notification was canceled due to user dismissal from a bubble")
+ NOTIFICATION_CANCEL_USER_BUBBLE(1228),
+ @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen")
+ NOTIFICATION_CANCEL_USER_LOCKSCREEN(193),
@UiEvent(doc = "Notification was canceled due to user dismissal from the notification"
+ " shade.")
NOTIFICATION_CANCEL_USER_SHADE(192),
- @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen")
- NOTIFICATION_CANCEL_USER_LOCKSCREEN(193),
@UiEvent(doc = "Notification was canceled due to an assistant adjustment update.")
NOTIFICATION_CANCEL_ASSISTANT(906);
@@ -232,6 +234,10 @@
return NOTIFICATION_CANCEL_USER_AOD;
case NotificationStats.DISMISSAL_SHADE:
return NOTIFICATION_CANCEL_USER_SHADE;
+ case NotificationStats.DISMISSAL_BUBBLE:
+ return NOTIFICATION_CANCEL_USER_BUBBLE;
+ case NotificationStats.DISMISSAL_LOCKSCREEN:
+ return NOTIFICATION_CANCEL_USER_LOCKSCREEN;
default:
if (NotificationManagerService.DBG) {
throw new IllegalArgumentException("Unexpected surface for user-dismiss "
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 05cb429..32ef014 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -63,7 +63,6 @@
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
-import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
@@ -1597,7 +1596,7 @@
// If there's a dream running then use home to escape the dream
// but don't actually go home.
if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
- mDreamManagerInternal.stopDream(false /*immediate*/);
+ mDreamManagerInternal.stopDream(false /*immediate*/, "short press on home" /*reason*/);
return;
}
@@ -2816,9 +2815,8 @@
break;
case KeyEvent.KEYCODE_S:
if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
- int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
- : TAKE_SCREENSHOT_FULLSCREEN;
- interceptScreenshotChord(type, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+ interceptScreenshotChord(
+ TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
return key_consumed;
}
break;
diff --git a/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java b/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java
index aad7b14..7440fc7 100644
--- a/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java
+++ b/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java
@@ -40,13 +40,24 @@
public class AmbientDisplaySuppressionController {
private static final String TAG = "AmbientDisplaySuppressionController";
- private final Context mContext;
private final Set<Pair<String, Integer>> mSuppressionTokens;
+ private final AmbientDisplaySuppressionChangedCallback mCallback;
private IStatusBarService mStatusBarService;
- AmbientDisplaySuppressionController(Context context) {
- mContext = requireNonNull(context);
+ /** Interface to get a list of available logical devices. */
+ interface AmbientDisplaySuppressionChangedCallback {
+ /**
+ * Called when the suppression state changes.
+ *
+ * @param isSuppressed Whether ambient is suppressed.
+ */
+ void onSuppressionChanged(boolean isSuppressed);
+ }
+
+ AmbientDisplaySuppressionController(
+ @NonNull AmbientDisplaySuppressionChangedCallback callback) {
mSuppressionTokens = Collections.synchronizedSet(new ArraySet<>());
+ mCallback = requireNonNull(callback);
}
/**
@@ -58,6 +69,7 @@
*/
public void suppress(@NonNull String token, int callingUid, boolean suppress) {
Pair<String, Integer> suppressionToken = Pair.create(requireNonNull(token), callingUid);
+ final boolean wasSuppressed = isSuppressed();
if (suppress) {
mSuppressionTokens.add(suppressionToken);
@@ -65,9 +77,14 @@
mSuppressionTokens.remove(suppressionToken);
}
+ final boolean isSuppressed = isSuppressed();
+ if (isSuppressed != wasSuppressed) {
+ mCallback.onSuppressionChanged(isSuppressed);
+ }
+
try {
synchronized (mSuppressionTokens) {
- getStatusBar().suppressAmbientDisplay(isSuppressed());
+ getStatusBar().suppressAmbientDisplay(isSuppressed);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to suppress ambient display", e);
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 5a2fb18..dad9584 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -571,7 +571,8 @@
/**
* Called when there has been user activity.
*/
- public void onUserActivity(int displayGroupId, int event, int uid) {
+ public void onUserActivity(int displayGroupId, @PowerManager.UserActivityEvent int event,
+ int uid) {
if (DEBUG) {
Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index fec61ac..431cf38 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -74,6 +74,8 @@
private long mLastPowerOnTime;
private long mLastUserActivityTime;
private long mLastUserActivityTimeNoChangeLights;
+ @PowerManager.UserActivityEvent
+ private int mLastUserActivityEvent;
/** Timestamp (milliseconds since boot) of the last time the power group was awoken.*/
private long mLastWakeTime;
/** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
@@ -227,8 +229,8 @@
}
}
- boolean dreamLocked(long eventTime, int uid) {
- if (eventTime < mLastWakeTime || mWakefulness != WAKEFULNESS_AWAKE) {
+ boolean dreamLocked(long eventTime, int uid, boolean allowWake) {
+ if (eventTime < mLastWakeTime || (!allowWake && mWakefulness != WAKEFULNESS_AWAKE)) {
return false;
}
@@ -244,7 +246,7 @@
return true;
}
- boolean dozeLocked(long eventTime, int uid, int reason) {
+ boolean dozeLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
if (eventTime < getLastWakeTimeLocked() || !isInteractive(mWakefulness)) {
return false;
}
@@ -253,9 +255,14 @@
try {
reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
+ long millisSinceLastUserActivity = eventTime - Math.max(
+ mLastUserActivityTimeNoChangeLights, mLastUserActivityTime);
Slog.i(TAG, "Powering off display group due to "
- + PowerManager.sleepReasonToString(reason) + " (groupId= " + getGroupId()
- + ", uid= " + uid + ")...");
+ + PowerManager.sleepReasonToString(reason)
+ + " (groupId= " + getGroupId() + ", uid= " + uid
+ + ", millisSinceLastUserActivity=" + millisSinceLastUserActivity
+ + ", lastUserActivityEvent=" + PowerManager.userActivityEventToString(
+ mLastUserActivityEvent) + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0,
@@ -266,14 +273,16 @@
return true;
}
- boolean sleepLocked(long eventTime, int uid, int reason) {
+ boolean sleepLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
return false;
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "sleepPowerGroup");
try {
- Slog.i(TAG, "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ")...");
+ Slog.i(TAG,
+ "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ", reason="
+ + PowerManager.sleepReasonToString(reason) + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_ASLEEP, eventTime, uid, reason, /* opUid= */0,
/* opPackageName= */ null, /* details= */ null);
@@ -287,16 +296,20 @@
return mLastUserActivityTime;
}
- void setLastUserActivityTimeLocked(long lastUserActivityTime) {
+ void setLastUserActivityTimeLocked(long lastUserActivityTime,
+ @PowerManager.UserActivityEvent int event) {
mLastUserActivityTime = lastUserActivityTime;
+ mLastUserActivityEvent = event;
}
public long getLastUserActivityTimeNoChangeLightsLocked() {
return mLastUserActivityTimeNoChangeLights;
}
- public void setLastUserActivityTimeNoChangeLightsLocked(long time) {
+ public void setLastUserActivityTimeNoChangeLightsLocked(long time,
+ @PowerManager.UserActivityEvent int event) {
mLastUserActivityTimeNoChangeLights = time;
+ mLastUserActivityEvent = event;
}
public int getUserActivitySummaryLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dbf05f1..4ec92ec 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -127,6 +127,7 @@
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.power.AmbientDisplaySuppressionController.AmbientDisplaySuppressionChangedCallback;
import com.android.server.power.batterysaver.BatterySaverController;
import com.android.server.power.batterysaver.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySaverStateMachine;
@@ -467,6 +468,9 @@
// True if the device should wake up when plugged or unplugged.
private boolean mWakeUpWhenPluggedOrUnpluggedConfig;
+ // True if the device should keep dreaming when undocked.
+ private boolean mKeepDreamingWhenUndockingConfig;
+
// True if the device should wake up when plugged or unplugged in theater mode.
private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig;
@@ -504,6 +508,9 @@
// effectively and terminate the dream. Use -1 to disable this safety feature.
private int mDreamsBatteryLevelDrainCutoffConfig;
+ // Whether dreams should be disabled when ambient mode is suppressed.
+ private boolean mDreamsDisabledByAmbientModeSuppressionConfig;
+
// True if dreams are enabled by the user.
private boolean mDreamsEnabledSetting;
@@ -961,8 +968,8 @@
}
AmbientDisplaySuppressionController createAmbientDisplaySuppressionController(
- Context context) {
- return new AmbientDisplaySuppressionController(context);
+ @NonNull AmbientDisplaySuppressionChangedCallback callback) {
+ return new AmbientDisplaySuppressionController(callback);
}
InattentiveSleepWarningController createInattentiveSleepWarningController() {
@@ -1041,7 +1048,8 @@
mConstants = new Constants(mHandler);
mAmbientDisplayConfiguration = mInjector.createAmbientDisplayConfiguration(context);
mAmbientDisplaySuppressionController =
- mInjector.createAmbientDisplaySuppressionController(context);
+ mInjector.createAmbientDisplaySuppressionController(
+ mAmbientSuppressionChangedCallback);
mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
mFaceDownDetector = new FaceDownDetector(this::onFlip);
mScreenUndimDetector = new ScreenUndimDetector();
@@ -1169,6 +1177,7 @@
return;
}
+ Slog.i(TAG, "onFlip(): Face " + (isFaceDown ? "down." : "up."));
mIsFaceDown = isFaceDown;
if (isFaceDown) {
final long currentTime = mClock.uptimeMillis();
@@ -1373,6 +1382,8 @@
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(
@@ -1397,6 +1408,8 @@
com.android.internal.R.integer.config_dreamsBatteryLevelMinimumWhenNotPowered);
mDreamsBatteryLevelDrainCutoffConfig = resources.getInteger(
com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff);
+ mDreamsDisabledByAmbientModeSuppressionConfig = resources.getBoolean(
+ com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
mDozeAfterScreenOff = resources.getBoolean(
com.android.internal.R.bool.config_dozeAfterScreenOffByDefault);
mMinimumScreenOffTimeoutConfig = resources.getInteger(
@@ -1888,12 +1901,13 @@
// Called from native code.
@SuppressWarnings("unused")
- private void userActivityFromNative(long eventTime, int event, int displayId, int flags) {
+ private void userActivityFromNative(long eventTime, @PowerManager.UserActivityEvent int event,
+ int displayId, int flags) {
userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID);
}
- private void userActivityInternal(int displayId, long eventTime, int event, int flags,
- int uid) {
+ private void userActivityInternal(int displayId, long eventTime,
+ @PowerManager.UserActivityEvent int event, int flags, int uid) {
synchronized (mLock) {
if (displayId == Display.INVALID_DISPLAY) {
if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
@@ -1917,6 +1931,13 @@
}
}
+ private void napInternal(long eventTime, int uid, boolean allowWake) {
+ synchronized (mLock) {
+ dreamPowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
+ eventTime, uid, allowWake);
+ }
+ }
+
private void onUserAttention() {
synchronized (mLock) {
if (userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
@@ -1944,11 +1965,12 @@
@GuardedBy("mLock")
private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
- int event, int flags, int uid) {
+ @PowerManager.UserActivityEvent int event, int flags, int uid) {
final int groupId = powerGroup.getGroupId();
if (DEBUG_SPEW) {
Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
- + ", eventTime=" + eventTime + ", event=" + event
+ + ", eventTime=" + eventTime
+ + ", event=" + PowerManager.userActivityEventToString(event)
+ ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
}
@@ -1983,7 +2005,7 @@
if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
if (eventTime > powerGroup.getLastUserActivityTimeNoChangeLightsLocked()
&& eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime);
+ powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime, event);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -1993,7 +2015,7 @@
}
} else {
if (eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeLocked(eventTime);
+ powerGroup.setLastUserActivityTimeLocked(eventTime, event);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -2020,7 +2042,8 @@
@WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "wakePowerGroupLocked: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId() + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid);
}
if (mForceSuspendActive || !mSystemReady) {
return;
@@ -2030,7 +2053,8 @@
}
@GuardedBy("mLock")
- private boolean dreamPowerGroupLocked(PowerGroup powerGroup, long eventTime, int uid) {
+ private boolean dreamPowerGroupLocked(PowerGroup powerGroup, long eventTime, int uid,
+ boolean allowWake) {
if (DEBUG_SPEW) {
Slog.d(TAG, "dreamPowerGroup: groupId=" + powerGroup.getGroupId() + ", eventTime="
+ eventTime + ", uid=" + uid);
@@ -2038,16 +2062,16 @@
if (!mBootCompleted || !mSystemReady) {
return false;
}
- return powerGroup.dreamLocked(eventTime, uid);
+ return powerGroup.dreamLocked(eventTime, uid, allowWake);
}
@GuardedBy("mLock")
private boolean dozePowerGroupLocked(final PowerGroup powerGroup, long eventTime,
- int reason, int uid) {
+ @GoToSleepReason int reason, int uid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "dozePowerGroup: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason
- + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
}
if (!mSystemReady || !mBootCompleted) {
@@ -2058,10 +2082,12 @@
}
@GuardedBy("mLock")
- private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, int reason,
- int uid) {
+ private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime,
+ @GoToSleepReason int reason, int uid) {
if (DEBUG_SPEW) {
- Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + ", uid=" + uid);
+ Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
}
if (!mBootCompleted || !mSystemReady) {
return false;
@@ -2122,8 +2148,10 @@
case WAKEFULNESS_DOZING:
traceMethodName = "goToSleep";
Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
- + " (uid " + uid + ")...");
-
+ + " (uid " + uid + ", screenOffTimeout=" + mScreenOffTimeoutSetting
+ + ", activityTimeoutWM=" + mUserActivityTimeoutOverrideFromWindowManager
+ + ", maxDimRatio=" + mMaximumScreenDimRatioConfig
+ + ", maxDimDur=" + mMaximumScreenDimDurationConfig + ")...");
mLastGlobalSleepTime = eventTime;
mLastGlobalSleepReason = reason;
mDozeStartInProgress = true;
@@ -2479,6 +2507,14 @@
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
@@ -3078,7 +3114,8 @@
changed = sleepPowerGroupLocked(powerGroup, time,
PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, Process.SYSTEM_UID);
} else if (shouldNapAtBedTimeLocked()) {
- changed = dreamPowerGroupLocked(powerGroup, time, Process.SYSTEM_UID);
+ changed = dreamPowerGroupLocked(powerGroup, time,
+ Process.SYSTEM_UID, /* allowWake= */ false);
} else {
changed = dozePowerGroupLocked(powerGroup, time,
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, Process.SYSTEM_UID);
@@ -3218,8 +3255,10 @@
if (mDreamManager != null) {
// Restart the dream whenever the sandman is summoned.
if (startDreaming) {
- mDreamManager.stopDream(/* immediate= */ false);
- mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING);
+ mDreamManager.stopDream(/* immediate= */ false,
+ "power manager request before starting dream" /*reason*/);
+ mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING,
+ "power manager request" /*reason*/);
}
isDreaming = mDreamManager.isDreaming();
} else {
@@ -3297,23 +3336,43 @@
}
// Doze has ended or will be stopped. Update the power state.
- sleepPowerGroupLocked(powerGroup, now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
+ sleepPowerGroupLocked(powerGroup, now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
Process.SYSTEM_UID);
}
}
// Stop dream.
if (isDreaming) {
- mDreamManager.stopDream(/* immediate= */ false);
+ mDreamManager.stopDream(/* immediate= */ false, "power manager request" /*reason*/);
}
}
+ @GuardedBy("mLock")
+ private void onDreamSuppressionChangedLocked(final boolean isSuppressed) {
+ if (!mDreamsDisabledByAmbientModeSuppressionConfig) {
+ return;
+ }
+ if (!isSuppressed && mIsPowered && mDreamsSupportedConfig && mDreamsEnabledSetting
+ && shouldNapAtBedTimeLocked() && isItBedTimeYetLocked(
+ mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP))) {
+ napInternal(SystemClock.uptimeMillis(), Process.SYSTEM_UID, /* allowWake= */ true);
+ } else if (isSuppressed) {
+ mDirty |= DIRTY_SETTINGS;
+ updatePowerStateLocked();
+ }
+ }
+
+
/**
* Returns true if the {@code groupId} is allowed to dream in its current state.
*/
@GuardedBy("mLock")
private boolean canDreamLocked(final PowerGroup powerGroup) {
+ final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppressionConfig
+ && mAmbientDisplaySuppressionController.isSuppressed();
+
if (!mBootCompleted
+ || dreamsSuppressed
|| getGlobalWakefulnessLocked() != WAKEFULNESS_DREAMING
|| !mDreamsSupportedConfig
|| !mDreamsEnabledSetting
@@ -4207,7 +4266,7 @@
void onUserActivity() {
synchronized (mLock) {
mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).setLastUserActivityTimeLocked(
- mClock.uptimeMillis());
+ mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER);
}
}
@@ -4398,6 +4457,8 @@
+ mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig);
pw.println(" mTheaterModeEnabled="
+ mTheaterModeEnabled);
+ pw.println(" mKeepDreamingWhenUndockingConfig="
+ + mKeepDreamingWhenUndockingConfig);
pw.println(" mSuspendWhenScreenOffDueToProximityConfig="
+ mSuspendWhenScreenOffDueToProximityConfig);
pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig);
@@ -5003,6 +5064,16 @@
}
};
+ private final AmbientDisplaySuppressionChangedCallback mAmbientSuppressionChangedCallback =
+ new AmbientDisplaySuppressionChangedCallback() {
+ @Override
+ public void onSuppressionChanged(boolean isSuppressed) {
+ synchronized (mLock) {
+ onDreamSuppressionChangedLocked(isSuppressed);
+ }
+ }
+ };
+
/**
* Callback for asynchronous operations performed by the power manager.
*/
@@ -5590,7 +5661,8 @@
}
@Override // Binder call
- public void userActivity(int displayId, long eventTime, int event, int flags) {
+ public void userActivity(int displayId, long eventTime,
+ @PowerManager.UserActivityEvent int event, int flags) {
final long now = mClock.uptimeMillis();
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
!= PackageManager.PERMISSION_GRANTED
@@ -5690,10 +5762,7 @@
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- dreamPowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
- eventTime, uid);
- }
+ napInternal(eventTime, uid, /* allowWake= */ false);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6621,6 +6690,11 @@
public boolean interceptPowerKeyDown(KeyEvent event) {
return interceptPowerKeyDownInternal(event);
}
+
+ @Override
+ public void nap(long eventTime, boolean allowWake) {
+ napInternal(eventTime, Process.SYSTEM_UID, allowWake);
+ }
}
/**
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 8b30995..d8e6c26 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -53,7 +53,7 @@
private static class DataElement {
private static final int LENGTH_FIELD_WIDTH = 4;
- private static final int MAX_DATA_ELEMENT_SIZE = 1000;
+ private static final int MAX_DATA_ELEMENT_SIZE = 32768;
private byte[] mData;
diff --git a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
index fd6ec06..f744d00 100644
--- a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
+++ b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
@@ -46,6 +46,8 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener,
SensorEventListener {
@@ -275,7 +277,7 @@
public void onSensorChanged(SensorEvent event) {
// Using log space to represent human sensation (Fechner's Law) instead of lux
// because lux values causes bright flashes to skew the average very high.
- addElement(event.timestamp, Math.max(0,
+ addElement(TimeUnit.NANOSECONDS.toMillis(event.timestamp), Math.max(0,
(int) (Math.log(event.values[0]) * LIGHT_VALUE_MULTIPLIER)));
updateLightSession();
mHandler.removeCallbacksAndMessages(mDelayedUpdateToken);
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index f888ff6..2888b9a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -602,9 +602,12 @@
synchronized (mUserTrustState) {
wasTrusted = (mUserTrustState.get(userId) == TrustState.TRUSTED);
wasTrustable = (mUserTrustState.get(userId) == TrustState.TRUSTABLE);
+ boolean isAutomotive = getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE);
boolean renewingTrust = wasTrustable && (
(flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0);
- boolean canMoveToTrusted = alreadyUnlocked || isFromUnlock || renewingTrust;
+ boolean canMoveToTrusted =
+ alreadyUnlocked || isFromUnlock || renewingTrust || isAutomotive;
boolean upgradingTrustForCurrentUser = (userId == mCurrentUser);
if (trustedByAtLeastOneAgent && wasTrusted) {
@@ -687,7 +690,7 @@
*/
public void lockUser(int userId) {
mLockPatternUtils.requireStrongAuth(
- StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId);
+ StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
try {
WindowManagerGlobal.getWindowManagerService().lockNow(null);
} catch (RemoteException e) {
@@ -2084,7 +2087,7 @@
if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
mLockPatternUtils.requireStrongAuth(
- mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
+ mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId);
}
maybeLockScreen(mUserId);
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f25929c..189b86f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1166,7 +1166,7 @@
try {
connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
wpdData.mWidth, wpdData.mHeight,
- wpdData.mPadding, mDisplayId);
+ wpdData.mPadding, mDisplayId, FLAG_SYSTEM | FLAG_LOCK);
} catch (RemoteException e) {
Slog.w(TAG, "Failed attaching wallpaper on display", e);
if (wallpaper != null && !wallpaper.wallpaperUpdating
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index df9ae63..eca2e74 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -27,6 +27,8 @@
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
@@ -707,7 +709,26 @@
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- return r != null && r.setOccludesParent(true);
+ // Create a transition if the activity is playing in case the below activity didn't
+ // commit invisible. That's because if any activity below this one has changed its
+ // visibility while playing transition, there won't able to commit visibility until
+ // the running transition finish.
+ final Transition transition = r != null
+ && r.mTransitionController.inPlayingTransition(r)
+ ? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null;
+ if (transition != null) {
+ r.mTransitionController.requestStartTransition(transition, null /*startTask */,
+ null /* remoteTransition */, null /* displayChange */);
+ }
+ final boolean changed = r != null && r.setOccludesParent(true);
+ if (transition != null) {
+ if (changed) {
+ r.mTransitionController.setReady(r.getDisplayContent());
+ } else {
+ transition.abort();
+ }
+ }
+ return changed;
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -728,7 +749,25 @@
if (under != null) {
under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
}
- return r.setOccludesParent(false);
+ // Create a transition if the activity is playing in case the current activity
+ // didn't commit invisible. That's because if this activity has changed its
+ // visibility while playing transition, there won't able to commit visibility until
+ // the running transition finish.
+ final Transition transition = r.mTransitionController.inPlayingTransition(r)
+ ? r.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
+ if (transition != null) {
+ r.mTransitionController.requestStartTransition(transition, null /*startTask */,
+ null /* remoteTransition */, null /* displayChange */);
+ }
+ final boolean changed = r.setOccludesParent(false);
+ if (transition != null) {
+ if (changed) {
+ r.mTransitionController.setReady(r.getDisplayContent());
+ } else {
+ transition.abort();
+ }
+ }
+ return changed;
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b8486e7..2eb2cf6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -120,6 +120,8 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
+import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
@@ -658,7 +660,7 @@
private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
new WindowState.UpdateReportedVisibilityResults();
- boolean mUseTransferredAnimation;
+ int mTransitionChangeFlags;
/** Whether we need to setup the animation to animate only within the letterbox. */
private boolean mNeedsLetterboxedAnimation;
@@ -4074,7 +4076,11 @@
// 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) {
@@ -4395,10 +4401,10 @@
// When transferring an animation, we no longer need to apply an animation to
// the token we transfer the animation over. Thus, set this flag to indicate
// we've transferred the animation.
- mUseTransferredAnimation = true;
+ mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
} else if (mTransitionController.getTransitionPlayer() != null) {
// In the new transit system, just set this every time we transfer the window
- mUseTransferredAnimation = true;
+ mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
}
// Post cleanup after the visibility and animation are transferred.
fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow);
@@ -5247,6 +5253,10 @@
// If in a transition, defer commits for activities that are going invisible
if (!visible && inTransition()) {
+ if (mTransitionController.inPlayingTransition(this)
+ && mTransitionController.isCollecting(this)) {
+ mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+ }
return;
}
// If we are preparing an app transition, then delay changing
@@ -5297,7 +5307,7 @@
@Override
boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
- if (mUseTransferredAnimation) {
+ if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
return false;
}
// If it was set to true, reset the last request to force the transition.
@@ -5370,7 +5380,7 @@
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
- mUseTransferredAnimation = false;
+ mTransitionChangeFlags = 0;
postApplyAnimation(visible, fromTransition);
}
@@ -5812,8 +5822,8 @@
// in untrusted mode. Traverse bottom to top with boundary so that it will only check
// activities above this activity.
final ActivityRecord differentUidOverlayActivity = getTask().getActivity(
- a -> a.getUid() != getUid(), this /* boundary */, false /* includeBoundary */,
- false /* traverseTopToBottom */);
+ a -> !a.finishing && a.getUid() != getUid(), this /* boundary */,
+ false /* includeBoundary */, false /* traverseTopToBottom */);
return differentUidOverlayActivity != null;
}
@@ -7628,6 +7638,31 @@
ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
}
+ /**
+ * Returns the requested {@link Configuration.Orientation} for the current activity.
+ *
+ * <p>When The current orientation is set to {@link SCREEN_ORIENTATION_BEHIND} it returns the
+ * requested orientation for the activity below which is the first activity with an explicit
+ * (different from {@link SCREEN_ORIENTATION_UNSET}) orientation which is not {@link
+ * SCREEN_ORIENTATION_BEHIND}.
+ */
+ @Configuration.Orientation
+ @Override
+ int getRequestedConfigurationOrientation(boolean forDisplay) {
+ if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) {
+ // We use Task here because we want to be consistent with what happens in
+ // multi-window mode where other tasks orientations are ignored.
+ final ActivityRecord belowCandidate = task.getActivity(
+ a -> a.mOrientation != SCREEN_ORIENTATION_UNSET && !a.finishing
+ && a.mOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND, this,
+ false /* includeBoundary */, true /* traverseTopToBottom */);
+ if (belowCandidate != null) {
+ return belowCandidate.getRequestedConfigurationOrientation(forDisplay);
+ }
+ }
+ return super.getRequestedConfigurationOrientation(forDisplay);
+ }
+
@Override
void onCancelFixedRotationTransform(int originalDisplayRotation) {
if (this != mDisplayContent.getLastOrientationSource()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 0707b81..9f502ab 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -50,6 +50,7 @@
import android.util.SparseArray;
import android.view.RemoteAnimationAdapter;
import android.view.WindowManager;
+import android.window.RemoteTransition;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -388,9 +389,9 @@
SafeActivityOptions bottomOptions = null;
if (options != null) {
// To ensure the first N-1 activities (N == total # of activities) are also launched
- // into the correct display, use a copy of the passed-in options (keeping only
- // display-related info) for these activities.
- bottomOptions = options.selectiveCloneDisplayOptions();
+ // into the correct display and root task, use a copy of the passed-in options (keeping
+ // only display-related and launch-root-task information) for these activities.
+ bottomOptions = options.selectiveCloneLaunchOptions();
}
try {
intents = ArrayUtils.filterNotNull(intents, Intent[]::new);
@@ -563,14 +564,39 @@
return false;
}
mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
- final ActivityMetricsLogger.LaunchingState launchingState =
- mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
- final Task task = r.getTask();
- mService.deferWindowLayout();
- try {
+ final RemoteTransition remote = options.getRemoteTransition();
+ if (remote != null && rootTask.mTransitionController.isCollecting()) {
+ final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
+ 0 /* flags */, rootTask.mTransitionController,
+ mService.mWindowManager.mSyncEngine);
+ // Special case: we are entering recents while an existing transition is running. In
+ // this case, we know it's safe to "defer" the activity launch, so lets do so now so
+ // that it can get its own transition and thus update launcher correctly.
+ mService.mWindowManager.mSyncEngine.queueSyncSet(
+ () -> rootTask.mTransitionController.moveToCollecting(transition),
+ () -> {
+ final Task task = r.getTask();
+ task.mTransitionController.requestStartTransition(transition,
+ task, remote, null /* displayChange */);
+ task.mTransitionController.collect(task);
+ startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+ });
+ } else {
+ final Task task = r.getTask();
task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT,
0 /* flags */, task, task /* readyGroupRef */,
options.getRemoteTransition(), null /* displayChange */);
+ startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+ }
+ return true;
+ }
+
+ void startExistingRecentsIfPossibleInner(Intent intent, ActivityOptions options,
+ ActivityRecord r, Task task, Task rootTask) {
+ final ActivityMetricsLogger.LaunchingState launchingState =
+ mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+ mService.deferWindowLayout();
+ try {
r.mTransitionController.setTransientLaunch(r,
TaskDisplayArea.getRootTaskAbove(rootTask));
task.moveToFront("startExistingRecents");
@@ -582,7 +608,6 @@
task.mInResumeTopActivity = false;
mService.continueWindowLayout();
}
- return true;
}
void registerRemoteAnimationForNextActivityStart(String packageName,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index a870b8a..e3916cb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1481,7 +1481,7 @@
handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
mRootWindowContainer.getDefaultTaskDisplayArea(), currentRootTask,
forceNonResizeable);
- if (r != null) {
+ if (r != null && (options == null || !options.getDisableStartingWindow())) {
// Use a starting window to reduce the transition latency for reshowing the task.
// Note that with shell transition, this should be executed before requesting
// transition to avoid delaying the starting window.
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
new file mode 100644
index 0000000..5e44d6c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
+
+/**
+ * The class that defines default launch params for tasks in desktop mode
+ */
+public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
+
+ private static final String TAG =
+ TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
+ private static final boolean DEBUG = false;
+
+ // Desktop mode feature flag.
+ static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode", false);
+ // Override default freeform task width when desktop mode is enabled. In dips.
+ private static final int DESKTOP_MODE_DEFAULT_WIDTH_DP = SystemProperties.getInt(
+ "persist.wm.debug.desktop_mode.default_width", 840);
+ // Override default freeform task height when desktop mode is enabled. In dips.
+ private static final int DESKTOP_MODE_DEFAULT_HEIGHT_DP = SystemProperties.getInt(
+ "persist.wm.debug.desktop_mode.default_height", 630);
+
+ private StringBuilder mLogBuilder;
+
+ @Override
+ public int onCalculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,
+ @Nullable ActivityRecord activity, @Nullable ActivityRecord source,
+ @Nullable ActivityOptions options, @Nullable ActivityStarter.Request request, int phase,
+ LaunchParamsController.LaunchParams currentParams,
+ LaunchParamsController.LaunchParams outParams) {
+
+ initLogBuilder(task, activity);
+ int result = calculate(task, layout, activity, source, options, request, phase,
+ currentParams, outParams);
+ outputLog();
+ return result;
+ }
+
+ private int calculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,
+ @Nullable ActivityRecord activity, @Nullable ActivityRecord source,
+ @Nullable ActivityOptions options, @Nullable ActivityStarter.Request request, int phase,
+ LaunchParamsController.LaunchParams currentParams,
+ LaunchParamsController.LaunchParams outParams) {
+
+ if (task == null) {
+ appendLog("task null, skipping");
+ return RESULT_SKIP;
+ }
+ if (phase != PHASE_BOUNDS) {
+ appendLog("not in bounds phase, skipping");
+ return RESULT_SKIP;
+ }
+ if (!task.inFreeformWindowingMode()) {
+ appendLog("not a freeform task, skipping");
+ return RESULT_SKIP;
+ }
+ if (!currentParams.mBounds.isEmpty()) {
+ appendLog("currentParams has bounds set, not overriding");
+ return RESULT_SKIP;
+ }
+
+ // Copy over any values
+ outParams.set(currentParams);
+
+ // Update width and height with default desktop mode values
+ float density = (float) task.getConfiguration().densityDpi / DENSITY_DEFAULT;
+ final int width = (int) (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f);
+ final int height = (int) (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f);
+ outParams.mBounds.right = width;
+ outParams.mBounds.bottom = height;
+
+ // Center the task in window bounds
+ Rect windowBounds = task.getWindowConfiguration().getBounds();
+ outParams.mBounds.offset(windowBounds.centerX() - outParams.mBounds.centerX(),
+ windowBounds.centerY() - outParams.mBounds.centerY());
+
+ appendLog("setting desktop mode task bounds to %s", outParams.mBounds);
+
+ return RESULT_DONE;
+ }
+
+ private void initLogBuilder(Task task, ActivityRecord activity) {
+ if (DEBUG) {
+ mLogBuilder = new StringBuilder(
+ "DesktopModeLaunchParamsModifier: task=" + task + " activity=" + activity);
+ }
+ }
+
+ private void appendLog(String format, Object... args) {
+ if (DEBUG) mLogBuilder.append(" ").append(String.format(format, args));
+ }
+
+ private void outputLog() {
+ if (DEBUG) Slog.d(TAG, mLogBuilder.toString());
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
new file mode 100644
index 0000000..a6f8557
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.function.Consumer;
+
+/**
+ * Class that registers callbacks with the {@link DeviceStateManager} and
+ * responds to fold state changes by forwarding such events to a delegate.
+ */
+final class DeviceStateController {
+ private final DeviceStateManager mDeviceStateManager;
+ private final Context mContext;
+
+ private FoldStateListener mDeviceStateListener;
+
+ public enum FoldState {
+ UNKNOWN, OPEN, FOLDED, HALF_FOLDED
+ }
+
+ DeviceStateController(Context context, Handler handler, Consumer<FoldState> delegate) {
+ mContext = context;
+ mDeviceStateManager = mContext.getSystemService(DeviceStateManager.class);
+ if (mDeviceStateManager != null) {
+ mDeviceStateListener = new FoldStateListener(mContext, delegate);
+ mDeviceStateManager
+ .registerCallback(new HandlerExecutor(handler),
+ mDeviceStateListener);
+ }
+ }
+
+ void unregisterFromDeviceStateManager() {
+ if (mDeviceStateListener != null) {
+ mDeviceStateManager.unregisterCallback(mDeviceStateListener);
+ }
+ }
+
+ /**
+ * A listener for half-fold device state events that dispatches state changes to a delegate.
+ */
+ static final class FoldStateListener implements DeviceStateManager.DeviceStateCallback {
+
+ private final int[] mHalfFoldedDeviceStates;
+ private final int[] mFoldedDeviceStates;
+
+ @Nullable
+ private FoldState mLastResult;
+ private final Consumer<FoldState> mDelegate;
+
+ FoldStateListener(Context context, Consumer<FoldState> delegate) {
+ mFoldedDeviceStates = context.getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
+ mHalfFoldedDeviceStates = context.getResources().getIntArray(
+ com.android.internal.R.array.config_halfFoldedDeviceStates);
+ mDelegate = delegate;
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ final boolean halfFolded = ArrayUtils.contains(mHalfFoldedDeviceStates, state);
+ FoldState result;
+ if (halfFolded) {
+ result = FoldState.HALF_FOLDED;
+ } else {
+ final boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
+ result = folded ? FoldState.FOLDED : FoldState.OPEN;
+ }
+ if (mLastResult == null || !mLastResult.equals(result)) {
+ mLastResult = result;
+ mDelegate.accept(result);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 71c80fb..38f6a53 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -564,6 +564,7 @@
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
+ private final DeviceStateController mDeviceStateController;
private final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
final RemoteDisplayChangeController mRemoteDisplayChangeController;
@@ -1119,6 +1120,13 @@
mDisplayPolicy = new DisplayPolicy(mWmService, this);
mDisplayRotation = new DisplayRotation(mWmService, this);
+
+ mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH,
+ newFoldState -> {
+ mDisplaySwitchTransitionLauncher.foldStateChanged(newFoldState);
+ mDisplayRotation.foldStateChanged(newFoldState);
+ });
+
mCloseToSquareMaxAspectRatio = mWmService.mContext.getResources().getFloat(
R.dimen.config_closeToSquareDisplayMaxAspectRatio);
if (isDefaultDisplay) {
@@ -3218,7 +3226,7 @@
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
handleAnimatingStoppedAndTransition();
mWmService.stopFreezingDisplayLocked();
- mDisplaySwitchTransitionLauncher.destroy();
+ mDeviceStateController.unregisterFromDeviceStateManager();
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
mPointerEventDispatcher.dispose();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 4c69f87..42a3ec6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2710,7 +2710,7 @@
*
* @param screenshotType The type of screenshot, for example either
* {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or
- * {@link WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
+ * {@link WindowManager#TAKE_SCREENSHOT_PROVIDED_IMAGE}
* @param source Where the screenshot originated from (see WindowManager.ScreenshotSource)
*/
public void takeScreenshot(int screenshotType, int source) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 97609a7..a8d13c5 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -40,6 +40,7 @@
import android.annotation.AnimRes;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -108,6 +109,8 @@
private OrientationListener mOrientationListener;
private StatusBarManagerInternal mStatusBarManagerInternal;
private SettingsObserver mSettingsObserver;
+ @Nullable
+ private FoldController mFoldController;
@ScreenOrientation
private int mCurrentAppOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
@@ -238,6 +241,10 @@
mOrientationListener.setCurrentRotation(mRotation);
mSettingsObserver = new SettingsObserver(uiHandler);
mSettingsObserver.observe();
+ if (mSupportAutoRotation && mContext.getResources().getBoolean(
+ R.bool.config_windowManagerHalfFoldAutoRotateOverride)) {
+ mFoldController = new FoldController();
+ }
}
}
@@ -436,7 +443,17 @@
final int oldRotation = mRotation;
final int lastOrientation = mLastOrientation;
- final int rotation = rotationForOrientation(lastOrientation, oldRotation);
+ int rotation = rotationForOrientation(lastOrientation, oldRotation);
+ // Use the saved rotation for tabletop mode, if set.
+ if (mFoldController != null && mFoldController.shouldRevertOverriddenRotation()) {
+ int prevRotation = rotation;
+ rotation = mFoldController.revertOverriddenRotation();
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Reverting orientation. Rotating to %s from %s rather than %s.",
+ Surface.rotationToString(rotation),
+ Surface.rotationToString(oldRotation),
+ Surface.rotationToString(prevRotation));
+ }
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "
+ "oldRotation=%s (%d)",
@@ -1138,7 +1155,8 @@
// If we don't support auto-rotation then bail out here and ignore
// the sensor and any rotation lock settings.
preferredRotation = -1;
- } else if ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
+ } else if (((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
+ || isTabletopAutoRotateOverrideEnabled())
&& (orientation == ActivityInfo.SCREEN_ORIENTATION_USER
|| orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|| orientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
@@ -1292,10 +1310,17 @@
return false;
}
+ private boolean isTabletopAutoRotateOverrideEnabled() {
+ return mFoldController != null && mFoldController.overrideFrozenRotation();
+ }
+
private boolean isRotationChoicePossible(int orientation) {
// Rotation choice is only shown when the user is in locked mode.
if (mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) return false;
+ // Don't show rotation choice if we are in tabletop or book modes.
+ if (isTabletopAutoRotateOverrideEnabled()) return false;
+
// We should only enable rotation choice if the rotation isn't forced by the lid, dock,
// demo, hdmi, vr, etc mode.
@@ -1496,6 +1521,74 @@
proto.end(token);
}
+ /**
+ * Called by the DeviceStateManager callback when the device state changes.
+ */
+ void foldStateChanged(DeviceStateController.FoldState foldState) {
+ if (mFoldController != null) {
+ synchronized (mLock) {
+ mFoldController.foldStateChanged(foldState);
+ }
+ }
+ }
+
+ private class FoldController {
+ @Surface.Rotation
+ private int mHalfFoldSavedRotation = -1; // No saved rotation
+ private DeviceStateController.FoldState mFoldState =
+ DeviceStateController.FoldState.UNKNOWN;
+
+ boolean overrideFrozenRotation() {
+ return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
+ }
+
+ boolean shouldRevertOverriddenRotation() {
+ return mFoldState == DeviceStateController.FoldState.OPEN // When transitioning to open.
+ && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
+ && mUserRotationMode
+ == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
+ }
+
+ int revertOverriddenRotation() {
+ int savedRotation = mHalfFoldSavedRotation;
+ mHalfFoldSavedRotation = -1;
+ return savedRotation;
+ }
+
+ void foldStateChanged(DeviceStateController.FoldState newState) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "foldStateChanged: displayId %d, halfFoldStateChanged %s, "
+ + "saved rotation: %d, mUserRotation: %d, mLastSensorRotation: %d, "
+ + "mLastOrientation: %d, mRotation: %d",
+ mDisplayContent.getDisplayId(), newState.name(), mHalfFoldSavedRotation,
+ mUserRotation, mLastSensorRotation, mLastOrientation, mRotation);
+ if (mFoldState == DeviceStateController.FoldState.UNKNOWN) {
+ mFoldState = newState;
+ return;
+ }
+ if (newState == DeviceStateController.FoldState.HALF_FOLDED
+ && mFoldState != DeviceStateController.FoldState.HALF_FOLDED) {
+ // The device has transitioned to HALF_FOLDED state: save the current rotation and
+ // update the device rotation.
+ mHalfFoldSavedRotation = mRotation;
+ mFoldState = newState;
+ // Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will
+ // return true, so rotation is unlocked.
+ mService.updateRotation(false /* alwaysSendConfiguration */,
+ false /* forceRelayout */);
+ } else {
+ // Revert the rotation to our saved value if we transition from HALF_FOLDED.
+ mRotation = mHalfFoldSavedRotation;
+ // Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
+ // so we will override USER_ROTATION_LOCKED and allow a rotation).
+ mService.updateRotation(false /* alwaysSendConfiguration */,
+ false /* forceRelayout */);
+ // Once we are rotated, set mFoldstate, effectively removing the lock override.
+ mFoldState = newState;
+ }
+ }
+ }
+
private class OrientationListener extends WindowOrientationListener implements Runnable {
transient boolean mEnabled;
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 7bd2a4a..e74e5787 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -64,6 +64,10 @@
void registerDefaultModifiers(ActivityTaskSupervisor supervisor) {
// {@link TaskLaunchParamsModifier} handles window layout preferences.
registerModifier(new TaskLaunchParamsModifier(supervisor));
+ if (DesktopModeLaunchParamsModifier.DESKTOP_MODE_SUPPORTED) {
+ // {@link DesktopModeLaunchParamsModifier} handles default task size changes
+ registerModifier(new DesktopModeLaunchParamsModifier());
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index a89894d..30bdc34 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -24,10 +24,7 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.graphics.Rect;
-import android.hardware.devicestate.DeviceStateManager;
-import android.os.HandlerExecutor;
import android.window.DisplayAreaInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
@@ -36,11 +33,8 @@
private final DisplayContent mDisplayContent;
private final WindowManagerService mService;
- private final DeviceStateManager mDeviceStateManager;
private final TransitionController mTransitionController;
- private DeviceStateListener mDeviceStateListener;
-
/**
* If on a foldable device represents whether the device is folded or not
*/
@@ -52,21 +46,15 @@
mDisplayContent = displayContent;
mService = displayContent.mWmService;
mTransitionController = transitionController;
-
- mDeviceStateManager = mService.mContext.getSystemService(DeviceStateManager.class);
-
- if (mDeviceStateManager != null) {
- mDeviceStateListener = new DeviceStateListener(mService.mContext);
- mDeviceStateManager
- .registerCallback(new HandlerExecutor(mDisplayContent.mWmService.mH),
- mDeviceStateListener);
- }
}
- public void destroy() {
- if (mDeviceStateManager != null) {
- mDeviceStateManager.unregisterCallback(mDeviceStateListener);
- }
+ /**
+ * Called by the DeviceStateManager callback when the state changes.
+ */
+ void foldStateChanged(DeviceStateController.FoldState newFoldState) {
+ // Ignore transitions to/from half-folded.
+ if (newFoldState == DeviceStateController.FoldState.HALF_FOLDED) return;
+ mIsFolded = newFoldState == DeviceStateController.FoldState.FOLDED;
}
/**
@@ -143,10 +131,4 @@
mTransition = null;
}
- class DeviceStateListener extends DeviceStateManager.FoldStateListener {
-
- DeviceStateListener(Context context) {
- super(context, newIsFolded -> mIsFolded = newIsFolded);
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 7f22242..2866f42 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2021,7 +2021,12 @@
// non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore
// to its previous freeform bounds.
rootTask.setLastNonFullscreenBounds(task.mLastNonFullscreenBounds);
- rootTask.setBounds(task.getBounds());
+ // When creating a new Task for PiP, set its initial bounds as the TaskFragment in
+ // case the activity is embedded, so that it can be animated to PiP window from the
+ // current bounds.
+ // Use Task#setBoundsUnchecked to skip checking windowing mode as the windowing mode
+ // will be updated later after this is collected in transition.
+ rootTask.setBoundsUnchecked(r.getTaskFragment().getBounds());
// Move the last recents animation transaction from original task to the new one.
if (task.mLastRecentsAnimationTransaction != null) {
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 8bacacd..6d1b5fa 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -117,13 +117,13 @@
/**
* To ensure that two activities, one using this object, and the other using the
- * SafeActivityOptions returned from this function, are launched into the same display through
- * ActivityStartController#startActivities, all display-related information, i.e.
- * displayAreaToken, launchDisplayId and callerDisplayId, are cloned.
+ * SafeActivityOptions returned from this function, are launched into the same display/root task
+ * through ActivityStartController#startActivities, all display-related information, i.e.
+ * displayAreaToken, launchDisplayId, callerDisplayId and the launch root task are cloned.
*/
- @Nullable SafeActivityOptions selectiveCloneDisplayOptions() {
- final ActivityOptions options = cloneLaunchingDisplayOptions(mOriginalOptions);
- final ActivityOptions callerOptions = cloneLaunchingDisplayOptions(mCallerOptions);
+ @Nullable SafeActivityOptions selectiveCloneLaunchOptions() {
+ final ActivityOptions options = cloneLaunchingOptions(mOriginalOptions);
+ final ActivityOptions callerOptions = cloneLaunchingOptions(mCallerOptions);
if (options == null && callerOptions == null) {
return null;
}
@@ -136,11 +136,12 @@
return safeOptions;
}
- private ActivityOptions cloneLaunchingDisplayOptions(ActivityOptions options) {
+ private ActivityOptions cloneLaunchingOptions(ActivityOptions options) {
return options == null ? null : ActivityOptions.makeBasic()
.setLaunchTaskDisplayArea(options.getLaunchTaskDisplayArea())
.setLaunchDisplayId(options.getLaunchDisplayId())
- .setCallerDisplayId((options.getCallerDisplayId()));
+ .setCallerDisplayId(options.getCallerDisplayId())
+ .setLaunchRootTask(options.getLaunchRootTask());
}
/**
@@ -263,7 +264,7 @@
ActivityOptions options, int callingPid, int callingUid) {
// If a launch task id is specified, then ensure that the caller is the recents
// component or has the START_TASKS_FROM_RECENTS permission
- if (options.getLaunchTaskId() != INVALID_TASK_ID
+ if ((options.getLaunchTaskId() != INVALID_TASK_ID || options.getDisableStartingWindow())
&& !supervisor.mRecentTasks.isCallerRecents(callingUid)) {
final int startInTaskPerm = ActivityTaskManagerService.checkPermission(
START_TASKS_FROM_RECENTS, callingPid, callingUid);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b67d2ef..731754e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2618,6 +2618,13 @@
return boundsChange;
}
+ /** Sets the requested bounds regardless of the windowing mode. */
+ int setBoundsUnchecked(@NonNull Rect bounds) {
+ final int boundsChange = super.setBounds(bounds);
+ updateSurfaceBounds();
+ return boundsChange;
+ }
+
@Override
public boolean isCompatible(int windowingMode, int activityType) {
// TODO: Should we just move this to ConfigurationContainer?
@@ -3546,12 +3553,16 @@
* {@link android.window.TaskFragmentOrganizer}
*/
TaskFragmentParentInfo getTaskFragmentParentInfo() {
- return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), isVisibleRequested());
+ return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(),
+ shouldBeVisible(null /* starting */));
}
@Override
void onActivityVisibleRequestedChanged() {
- if (mVisibleRequested != isVisibleRequested()) {
+ final boolean prevVisibleRequested = mVisibleRequested;
+ // mVisibleRequested is updated in super method.
+ super.onActivityVisibleRequestedChanged();
+ if (prevVisibleRequested != mVisibleRequested) {
sendTaskFragmentParentInfoChangedIfNeeded();
}
}
@@ -5070,6 +5081,9 @@
== ActivityOptions.ANIM_SCENE_TRANSITION) {
doShow = false;
}
+ if (options != null && options.getDisableStartingWindow()) {
+ doShow = false;
+ }
if (r.mLaunchTaskBehind) {
// Don't do a starting window for mLaunchTaskBehind. More importantly make sure we
// tell WindowManager that r is visible even though it is at the back of the root
@@ -5913,10 +5927,7 @@
return BOUNDS_CHANGE_NONE;
}
- final int result = super.setBounds(!inMultiWindowMode() ? null : bounds);
-
- updateSurfaceBounds();
- return result;
+ return setBoundsUnchecked(!inMultiWindowMode() ? null : bounds);
}
@Override
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 058a066..d178676 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2690,12 +2690,26 @@
return;
}
mVisibleRequested = isVisibleRequested;
- final TaskFragment parentTf = getParent().asTaskFragment();
+ final WindowContainer<?> parent = getParent();
+ if (parent == null) {
+ return;
+ }
+ final TaskFragment parentTf = parent.asTaskFragment();
if (parentTf != null) {
parentTf.onActivityVisibleRequestedChanged();
}
}
+ @Nullable
+ @Override
+ TaskFragment getTaskFragment(Predicate<TaskFragment> callback) {
+ final TaskFragment taskFragment = super.getTaskFragment(callback);
+ if (taskFragment != null) {
+ return taskFragment;
+ }
+ return callback.test(this) ? this : null;
+ }
+
String toFullString() {
final StringBuilder sb = new StringBuilder(128);
sb.append(this);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 2d5c989..867833a 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -920,6 +920,7 @@
for (int i = 0, n = pendingEvents.size(); i < n; i++) {
final PendingTaskFragmentEvent event = pendingEvents.get(i);
final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
+ // TODO(b/251132298): move visibility check to the client side.
if (task != null && (task.lastActiveTime <= event.mDeferTime
|| !(isTaskVisible(task, visibleTasks, invisibleTasks)
|| shouldSendEventWhenTaskInvisible(event)))) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 96ad9ab..46253c1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -55,7 +55,6 @@
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
-import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
@@ -1596,6 +1595,10 @@
if (info.mEndParent != null) {
change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
}
+ if (info.mStartParent != null && info.mStartParent.mRemoteToken != null
+ && target.getParent() != info.mStartParent) {
+ change.setLastParent(info.mStartParent.mRemoteToken.toWindowContainerToken());
+ }
change.setMode(info.getTransitMode(target));
change.setStartAbsBounds(info.mAbsoluteBounds);
change.setFlags(info.getChangeFlags(target));
@@ -1875,26 +1878,24 @@
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();
if (record != null) {
parentTask = record.getTask();
- if (record.mUseTransferredAnimation) {
- flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
- }
if (record.mVoiceInteraction) {
flags |= FLAG_IS_VOICE_INTERACTION;
}
+ flags |= record.mTransitionChangeFlags;
}
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && task == null) {
@@ -1913,20 +1914,26 @@
// 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 e8682f7..26ce4ae 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 @@
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 @@
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 949fa96..32a110e 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 @@
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/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f61bccf..c161a9b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3869,8 +3869,8 @@
// configuration update when the window has requested to be hidden. Doing so can lead to
// the client erroneously accepting a configuration that would have otherwise caused an
// activity restart. We instead hand back the last reported {@link MergedConfiguration}.
- if (useLatestConfig || (relayoutVisible && (!shouldCheckTokenVisibleRequested()
- || mToken.isVisibleRequested()))) {
+ if (useLatestConfig || (relayoutVisible && (mActivityRecord == null
+ || mActivityRecord.mVisibleRequested))) {
final Configuration globalConfig = getProcessGlobalConfiguration();
final Configuration overrideConfig = getMergedOverrideConfiguration();
outMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 267cff6..f53a1cf 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -351,9 +351,39 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <xs:sequence>
+ <!-- Thresholds as tenths of percent of current brightness level, at each level of
+ brightness -->
+ <xs:element name="brightnessThresholdPoints" type="thresholdPoints" maxOccurs="1" minOccurs="0">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="thresholdPoints">
+ <xs:sequence>
+ <xs:element type="thresholdPoint" name="brightnessThresholdPoint" maxOccurs="unbounded" minOccurs="1">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="thresholdPoint">
+ <xs:sequence>
+ <xs:element type="nonNegativeDecimal" name="threshold">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element type="nonNegativeDecimal" name="percentage">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
</xs:complexType>
<xs:complexType name="autoBrightness">
+ <xs:attribute name="enabled" type="xs:boolean" use="optional" default="true"/>
<xs:sequence>
<!-- Sets the debounce for autoBrightness brightening in millis-->
<xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index f8bff75..d89bd7c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -6,14 +6,18 @@
method public final java.math.BigInteger getBrighteningLightDebounceMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
+ method public boolean getEnabled();
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
+ method public void setEnabled(boolean);
}
public class BrightnessThresholds {
ctor public BrightnessThresholds();
+ method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
method @NonNull public final java.math.BigDecimal getMinimum();
+ method public final void setBrightnessThresholdPoints(com.android.server.display.config.ThresholdPoints);
method public final void setMinimum(@NonNull java.math.BigDecimal);
}
@@ -204,6 +208,19 @@
method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap);
}
+ public class ThresholdPoint {
+ ctor public ThresholdPoint();
+ method @NonNull public final java.math.BigDecimal getPercentage();
+ method @NonNull public final java.math.BigDecimal getThreshold();
+ method public final void setPercentage(@NonNull java.math.BigDecimal);
+ method public final void setThreshold(@NonNull java.math.BigDecimal);
+ }
+
+ public class ThresholdPoints {
+ ctor public ThresholdPoints();
+ method @NonNull public final java.util.List<com.android.server.display.config.ThresholdPoint> getBrightnessThresholdPoint();
+ }
+
public class Thresholds {
ctor public Thresholds();
method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds();
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
index 45e0d09..4ef6875 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
@@ -628,6 +628,7 @@
CodePath.SAME, CodePath.DIFFERENT ->
throw AssertionError("secondDataPath cannot be a data path")
CodePath.SYSTEM -> assertThat(codePaths[1]).isEqualTo(stubFile.parent.toString())
+ else -> {}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index b9d6b2c..994df22 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -392,7 +392,7 @@
whenever(rule.mocks().appsFilter.getVisibilityAllowList(
any(PackageDataSnapshot::class.java),
argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java),
- any() as ArrayMap<String, out PackageStateInternal>
+ any<ArrayMap<String, out PackageStateInternal>>()
))
.thenReturn(list)
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index bccd8a0b..9ae8922 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 @@
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 @@
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 @@
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 @@
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 @@
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 10f0a5c..68c9ce4 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.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.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 @@
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 @@
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 @@
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 @@
verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
verifyNoMoreInteractions(mSensorManager);
- assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
}
@Test
@@ -166,7 +174,148 @@
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 60dc2eb..88a9646 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 @@
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 dea4d4f..a5c181d 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.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 @@
@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 @@
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/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 8280fc6..4f2b613 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -422,13 +422,13 @@
@Test
public void testHysteresisLevels() {
- int[] ambientBrighteningThresholds = {100, 200};
- int[] ambientDarkeningThresholds = {400, 500};
- int[] ambientThresholdLevels = {500};
+ float[] ambientBrighteningThresholds = {50, 100};
+ float[] ambientDarkeningThresholds = {10, 20};
+ float[] ambientThresholdLevels = {0, 500};
float ambientDarkeningMinChangeThreshold = 3.0f;
float ambientBrighteningMinChangeThreshold = 1.5f;
HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
- ambientDarkeningThresholds, ambientThresholdLevels,
+ ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels,
ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
// test low, activate minimum change thresholds.
@@ -437,16 +437,17 @@
assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
// test max
- assertEquals(12000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON);
- assertEquals(5000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+ // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
+ assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2);
+ assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
// test just below threshold
- assertEquals(548.9f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
- assertEquals(299.4f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+ assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+ assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
// test at (considered above) threshold
- assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
- assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+ assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+ assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 356600d..0642228 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -885,6 +886,29 @@
assertNull(mInjector.mLightSensor);
}
+ @Test
+ public void testOnlyOneReceiverRegistered() {
+ assertNull(mInjector.mLightSensor);
+ assertNull(mInjector.mSensorListener);
+ startTracker(mTracker, 0.3f, false);
+
+ assertNotNull(mInjector.mLightSensor);
+ assertNotNull(mInjector.mSensorListener);
+ Sensor registeredLightSensor = mInjector.mLightSensor;
+ SensorEventListener registeredSensorListener = mInjector.mSensorListener;
+
+ mTracker.start(0.3f);
+ assertSame(registeredLightSensor, mInjector.mLightSensor);
+ assertSame(registeredSensorListener, mInjector.mSensorListener);
+
+ mTracker.stop();
+ assertNull(mInjector.mLightSensor);
+ assertNull(mInjector.mSensorListener);
+
+ // mInjector asserts that we aren't removing a null receiver
+ mTracker.stop();
+ }
+
private InputStream getInputStream(String data) {
return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 66420ad..a719f52 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -32,6 +32,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,6 +50,9 @@
@RunWith(AndroidJUnit4.class)
public final class DisplayDeviceConfigTest {
private DisplayDeviceConfig mDisplayDeviceConfig;
+ private static final float ZERO_DELTA = 0.0f;
+ private static final float SMALL_DELTA = 0.0001f;
+
@Mock
private Context mContext;
@@ -69,26 +74,74 @@
assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
- assertEquals(mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), 10.0f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), 2.0f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
- 0.0f);
- assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f}, 0.0f);
+ ZERO_DELTA);
+ assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f},
+ ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
- 0.0f);
- assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);
- assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
+ ZERO_DELTA);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
- float[]{0.0f, 50.0f, 80.0f}, 0.0f);
+ float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
- float[]{45.32f, 75.43f}, 0.0f);
+ float[]{45.32f, 75.43f}, ZERO_DELTA);
+
+ // Test thresholds
+ assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
+ ZERO_DELTA);
+ assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+ ZERO_DELTA);
+ assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+ assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+ assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 0.10f, 0.20f},
+ mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{9, 10, 11},
+ mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 0.11f, 0.21f},
+ mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{11, 12, 13},
+ mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 100, 200},
+ mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{13, 14, 15},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 300, 400},
+ mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{15, 16, 17},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 0.12f, 0.22f},
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{17, 18, 19},
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 0.13f, 0.23f},
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{19, 20, 21},
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 500, 600},
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{21, 22, 23},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 700, 800},
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{23, 24, 25},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+
+
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@@ -97,9 +150,60 @@
public void testConfigValuesFromConfigResource() {
setupDisplayDeviceConfigFromConfigResourceFile();
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
- float[]{2.0f, 200.0f, 600.0f}, 0.0f);
+ float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
- float[]{0.0f, 0.0f, 110.0f, 500.0f}, 0.0f);
+ float[]{0.0f, 0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+
+ // Test thresholds
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+ ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ // screen levels will be considered "old screen brightness scale"
+ // and therefore will divide by 255
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
+ assertArrayEquals(new float[]{35, 36, 37},
+ mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
+ assertArrayEquals(new float[]{37, 38, 39},
+ mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{27, 28, 29},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{29, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
+ assertArrayEquals(new float[]{35, 36, 37},
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
+ assertArrayEquals(new float[]{37, 38, 39},
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{27, 28, 29},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{29, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@@ -154,11 +258,126 @@
+ "<ambientBrightnessChangeThresholds>\n"
+ "<brighteningThresholds>\n"
+ "<minimum>10</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>13</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>100</threshold><percentage>14</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>200</threshold><percentage>15</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ "</brighteningThresholds>\n"
+ "<darkeningThresholds>\n"
- + "<minimum>2</minimum>\n"
+ + "<minimum>30</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>15</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>300</threshold><percentage>16</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>400</threshold><percentage>17</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ "</darkeningThresholds>\n"
+ "</ambientBrightnessChangeThresholds>\n"
+ + "<displayBrightnessChangeThresholds>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>0.1</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold>\n"
+ + "<percentage>9</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.10</threshold>\n"
+ + "<percentage>10</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.20</threshold>\n"
+ + "<percentage>11</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>0.3</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>11</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.11</threshold><percentage>12</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.21</threshold><percentage>13</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</darkeningThresholds>\n"
+ + "</displayBrightnessChangeThresholds>\n"
+ + "<ambientBrightnessChangeThresholdsIdle>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>20</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>21</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>500</threshold><percentage>22</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>600</threshold><percentage>23</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>40</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>23</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>700</threshold><percentage>24</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>800</threshold><percentage>25</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</darkeningThresholds>\n"
+ + "</ambientBrightnessChangeThresholdsIdle>\n"
+ + "<displayBrightnessChangeThresholdsIdle>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>0.2</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>17</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.12</threshold><percentage>18</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.22</threshold><percentage>19</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>0.4</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>19</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.13</threshold><percentage>20</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.23</threshold><percentage>21</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</darkeningThresholds>\n"
+ + "</displayBrightnessChangeThresholdsIdle>\n"
+ "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
+ "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease> "
+ "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
@@ -171,18 +390,6 @@
+ "</screenBrightnessRampDecreaseMaxMillis>"
+ "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
+ "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
- + "<displayBrightnessChangeThresholds>"
- + "<brighteningThresholds>"
- + "<minimum>"
- + "0.001"
- + "</minimum>"
- + "</brighteningThresholds>"
- + "<darkeningThresholds>"
- + "<minimum>"
- + "0.002"
- + "</minimum>"
- + "</darkeningThresholds>"
- + "</displayBrightnessChangeThresholds>"
+ "<screenBrightnessRampIncreaseMaxMillis>"
+ "2000"
+ "</screenBrightnessRampIncreaseMaxMillis>\n"
@@ -241,8 +448,24 @@
com.android.internal.R.array.config_autoBrightnessLevels))
.thenReturn(screenBrightnessLevelLux);
- mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+ // Thresholds
+ // Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
+ // of length N+1
+ when(mResources.getIntArray(com.android.internal.R.array.config_ambientThresholdLevels))
+ .thenReturn(new int[]{30, 31});
+ when(mResources.getIntArray(com.android.internal.R.array.config_screenThresholdLevels))
+ .thenReturn(new int[]{42, 43});
+ when(mResources.getIntArray(
+ com.android.internal.R.array.config_ambientBrighteningThresholds))
+ .thenReturn(new int[]{270, 280, 290});
+ when(mResources.getIntArray(com.android.internal.R.array.config_ambientDarkeningThresholds))
+ .thenReturn(new int[]{290, 300, 310});
+ when(mResources.getIntArray(R.array.config_screenBrighteningThresholds))
+ .thenReturn(new int[]{350, 360, 370});
+ when(mResources.getIntArray(R.array.config_screenDarkeningThresholds))
+ .thenReturn(new int[]{370, 380, 390});
+ mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
}
private TypedArray createFloatTypedArray(float[] vals) {
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 9fe8609c..3b0a22f 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -275,6 +275,75 @@
assertNull(mDataStore.getBrightnessConfiguration(userSerial));
}
+ @Test
+ public void testStoreAndRestoreResolution() {
+ final String uniqueDisplayId = "test:123";
+ DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+ @Override
+ public boolean hasStableUniqueId() {
+ return true;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return null;
+ }
+ };
+ int width = 35;
+ int height = 45;
+ mDataStore.loadIfNeeded();
+ mDataStore.setUserPreferredResolution(testDisplayDevice, width, height);
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mInjector.setWriteStream(baos);
+ mDataStore.saveIfNeeded();
+ mTestLooper.dispatchAll();
+ assertTrue(mInjector.wasWriteSuccessful());
+ TestInjector newInjector = new TestInjector();
+ PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ newInjector.setReadStream(bais);
+ newDataStore.loadIfNeeded();
+ assertNotNull(newDataStore.getUserPreferredResolution(testDisplayDevice));
+ assertEquals(35, newDataStore.getUserPreferredResolution(testDisplayDevice).x);
+ assertEquals(35, mDataStore.getUserPreferredResolution(testDisplayDevice).x);
+ assertEquals(45, newDataStore.getUserPreferredResolution(testDisplayDevice).y);
+ assertEquals(45, mDataStore.getUserPreferredResolution(testDisplayDevice).y);
+ }
+
+ @Test
+ public void testStoreAndRestoreRefreshRate() {
+ final String uniqueDisplayId = "test:123";
+ DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+ @Override
+ public boolean hasStableUniqueId() {
+ return true;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return null;
+ }
+ };
+ float refreshRate = 85.3f;
+ mDataStore.loadIfNeeded();
+ mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mInjector.setWriteStream(baos);
+ mDataStore.saveIfNeeded();
+ mTestLooper.dispatchAll();
+ assertTrue(mInjector.wasWriteSuccessful());
+ TestInjector newInjector = new TestInjector();
+ PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ newInjector.setReadStream(bais);
+ newDataStore.loadIfNeeded();
+ assertNotNull(newDataStore.getUserPreferredRefreshRate(testDisplayDevice));
+ assertEquals(85.3f, mDataStore.getUserPreferredRefreshRate(testDisplayDevice), 01.f);
+ assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
+ }
+
public class TestInjector extends PersistentDataStore.Injector {
private InputStream mReadStream;
private OutputStream mWriteStream;
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
index d8c9c34..e3ca170 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -116,7 +116,7 @@
@Test
public void testDreamPowerGroup() {
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
- mPowerGroup.dreamLocked(TIMESTAMP1, UID);
+ mPowerGroup.dreamLocked(TIMESTAMP1, UID, /* allowWake= */ false);
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
assertThat(mPowerGroup.isSandmanSummonedLocked()).isTrue();
verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
@@ -172,7 +172,7 @@
eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
/* details= */ isNull());
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
- assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID)).isFalse();
+ assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID, /* allowWake= */ false)).isFalse();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
verify(mWakefulnessCallbackMock, never()).onWakefulnessChangedLocked(
eq(GROUP_ID), /* wakefulness= */ eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP2),
@@ -181,6 +181,22 @@
}
@Test
+ public void testDreamPowerGroupWhenNotAwakeShouldWake() {
+ mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
+ verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+ eq(WAKEFULNESS_DOZING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_TIMEOUT),
+ eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
+ /* details= */ isNull());
+ assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+ assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID, /* allowWake= */ true)).isTrue();
+ assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(
+ eq(GROUP_ID), /* wakefulness= */ eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP2),
+ /* reason= */ anyInt(), eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ any(),
+ /* details= */ any());
+ }
+
+ @Test
public void testLastWakeAndSleepTimeIsUpdated() {
assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
@@ -514,7 +530,7 @@
.setBatterySaverEnabled(batterySaverEnabled)
.setBrightnessFactor(brightnessFactor)
.build();
- mPowerGroup.dreamLocked(TIMESTAMP1, UID);
+ mPowerGroup.dreamLocked(TIMESTAMP1, UID, /* allowWake= */ false);
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 9ff7d69..f5ed41a 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -393,6 +393,12 @@
.thenReturn(minimumScreenOffTimeoutConfigMillis);
}
+ private void setDreamsDisabledByAmbientModeSuppressionConfig(boolean disable) {
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig))
+ .thenReturn(disable);
+ }
+
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
mTestLooper.dispatchAll();
@@ -612,6 +618,31 @@
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();
@@ -731,7 +762,7 @@
doAnswer(inv -> {
when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
return null;
- }).when(mDreamManagerInternalMock).startDream(anyBoolean());
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
setMinimumScreenOffTimeoutConfig(5);
createService();
@@ -753,7 +784,7 @@
doAnswer(inv -> {
when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
return null;
- }).when(mDreamManagerInternalMock).startDream(anyBoolean());
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
setMinimumScreenOffTimeoutConfig(5);
createService();
@@ -765,6 +796,91 @@
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+ setDreamsDisabledByAmbientModeSuppressionConfig(true);
+ setMinimumScreenOffTimeoutConfig(10000);
+ createService();
+ startSystem();
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ setPluggedIn(true);
+ // Allow asynchronous sandman calls to execute.
+ advanceTime(10000);
+
+ forceDream();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+ advanceTime(50);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testAmbientSuppressionDisabled_shouldNotWakeDevice() {
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+ setDreamsDisabledByAmbientModeSuppressionConfig(false);
+ setMinimumScreenOffTimeoutConfig(10000);
+ createService();
+ startSystem();
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ setPluggedIn(true);
+ // Allow asynchronous sandman calls to execute.
+ advanceTime(10000);
+
+ forceDream();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+ advanceTime(50);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ }
+
+ @Test
+ public void testAmbientSuppression_doesNotAffectDreamForcing() {
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+ setDreamsDisabledByAmbientModeSuppressionConfig(true);
+ setMinimumScreenOffTimeoutConfig(10000);
+ createService();
+ startSystem();
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+ setPluggedIn(true);
+ // Allow asynchronous sandman calls to execute.
+ advanceTime(10000);
+
+ // Verify that forcing dream still works even though ambient display is suppressed
+ forceDream();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ }
+
@Test
public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
final String suspendBlockerName = "PowerManagerService.Display";
@@ -1168,7 +1284,7 @@
doAnswer(inv -> {
when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
return null;
- }).when(mDreamManagerInternalMock).startDream(anyBoolean());
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
final String pkg = mContextSpy.getOpPackageName();
final Binder token = new Binder();
@@ -1662,7 +1778,7 @@
forceDozing();
// Allow handleSandman() to be called asynchronously
advanceTime(500);
- verify(mDreamManagerInternalMock).startDream(eq(true));
+ verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
}
@Test
@@ -1700,7 +1816,7 @@
// Allow handleSandman() to be called asynchronously
advanceTime(500);
- verify(mDreamManagerInternalMock).startDream(eq(true));
+ verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
}
@Test
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 d544744..462957a 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 @@
assertTrue(activity1.getTask().getTaskInfo().launchCookies.contains(launchCookie));
}
+ @Test
+ public void testOrientationForScreenOrientationBehind() {
+ final Task task = createTask(mDisplayContent);
+ // Activity below
+ new ActivityBuilder(mAtm)
+ .setTask(task)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .build();
+ final ActivityRecord activityTop = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .setScreenOrientation(SCREEN_ORIENTATION_BEHIND)
+ .build();
+ final int topOrientation = activityTop.getRequestedConfigurationOrientation();
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, topOrientation);
+ }
+
private void verifyProcessInfoUpdate(ActivityRecord activity, State state,
boolean shouldUpdate, boolean activityChange) {
reset(activity.app);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
new file mode 100644
index 0000000..7830e90
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.LaunchParamsController.LaunchParamsModifier.Result;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for desktop mode task bounds.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DesktopModeLaunchParamsModifierTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
+
+ private ActivityRecord mActivity;
+
+ private DesktopModeLaunchParamsModifier mTarget;
+
+ private LaunchParamsController.LaunchParams mCurrent;
+ private LaunchParamsController.LaunchParams mResult;
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = new ActivityBuilder(mAtm).build();
+ mTarget = new DesktopModeLaunchParamsModifier();
+ mCurrent = new LaunchParamsController.LaunchParams();
+ mCurrent.reset();
+ mResult = new LaunchParamsController.LaunchParams();
+ mResult.reset();
+ }
+
+ @Test
+ public void testReturnsSkipIfTaskIsNull() {
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
+ }
+
+ @Test
+ public void testReturnsSkipIfNotBoundsPhase() {
+ final Task task = new TaskBuilder(mSupervisor).build();
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).setPhase(
+ PHASE_DISPLAY).calculate());
+ }
+
+ @Test
+ public void testReturnsSkipIfTaskNotInFreeform() {
+ final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_FULLSCREEN).build();
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).calculate());
+ }
+
+ @Test
+ public void testReturnsSkipIfCurrentParamsHasBounds() {
+ final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_FREEFORM).build();
+ mCurrent.mBounds.set(/* left */ 0, /* top */ 0, /* right */ 100, /* bottom */ 100);
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).calculate());
+ }
+
+ @Test
+ public void testUsesDefaultBounds() {
+ final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_FREEFORM).build();
+ assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(dpiToPx(task, 840), mResult.mBounds.width());
+ assertEquals(dpiToPx(task, 630), mResult.mBounds.height());
+ }
+
+ @Test
+ public void testUsesDisplayAreaAndWindowingModeFromSource() {
+ final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_FREEFORM).build();
+ TaskDisplayArea mockTaskDisplayArea = mock(TaskDisplayArea.class);
+ mCurrent.mPreferredTaskDisplayArea = mockTaskDisplayArea;
+ mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
+
+ assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(mockTaskDisplayArea, mResult.mPreferredTaskDisplayArea);
+ assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
+ }
+
+ private int dpiToPx(Task task, int dpi) {
+ float density = (float) task.getConfiguration().densityDpi / DENSITY_DEFAULT;
+ return (int) (dpi * density + 0.5f);
+ }
+
+ private class CalculateRequestBuilder {
+ private Task mTask;
+ private int mPhase = PHASE_BOUNDS;
+ private final ActivityRecord mActivity =
+ DesktopModeLaunchParamsModifierTests.this.mActivity;
+ private final LaunchParamsController.LaunchParams mCurrentParams = mCurrent;
+ private final LaunchParamsController.LaunchParams mOutParams = mResult;
+
+ private CalculateRequestBuilder setTask(Task task) {
+ mTask = task;
+ return this;
+ }
+
+ private CalculateRequestBuilder setPhase(int phase) {
+ mPhase = phase;
+ return this;
+ }
+
+ @Result
+ private int calculate() {
+ return mTarget.onCalculate(mTask, /* layout*/ null, mActivity, /* source */
+ null, /* options */ null, /* request */ null, mPhase, mCurrentParams,
+ mOutParams);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
new file mode 100644
index 0000000..86732c9
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link DeviceStateController}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DeviceStateControllerTests
+ */
+@SmallTest
+@Presubmit
+public class DeviceStateControllerTests {
+
+ private DeviceStateController.FoldStateListener mFoldStateListener;
+ private DeviceStateController mTarget;
+ private DeviceStateControllerBuilder mBuilder;
+
+ private Context mMockContext;
+ private Handler mMockHandler;
+ private Resources mMockRes;
+ private DeviceStateManager mMockDeviceStateManager;
+
+ private Consumer<DeviceStateController.FoldState> mDelegate;
+ private DeviceStateController.FoldState mCurrentState = DeviceStateController.FoldState.UNKNOWN;
+
+ @Before
+ public void setUp() {
+ mBuilder = new DeviceStateControllerBuilder();
+ mCurrentState = DeviceStateController.FoldState.UNKNOWN;
+ }
+
+ private void initialize(boolean supportFold, boolean supportHalfFold) throws Exception {
+ mBuilder.setSupportFold(supportFold, supportHalfFold);
+ mDelegate = (newFoldState) -> {
+ mCurrentState = newFoldState;
+ };
+ mBuilder.setDelegate(mDelegate);
+ mBuilder.build();
+ verifyFoldStateListenerRegistration(1);
+ }
+
+ @Test
+ public void testInitialization() throws Exception {
+ initialize(true /* supportFold */, true /* supportHalfFolded */);
+ mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
+ assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
+ }
+
+ @Test
+ public void testInitializationWithNoFoldSupport() throws Exception {
+ initialize(false /* supportFold */, false /* supportHalfFolded */);
+ mFoldStateListener.onStateChanged(mFoldedStates[0]);
+ // Note that the folded state is ignored.
+ assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
+ }
+
+ @Test
+ public void testWithFoldSupported() throws Exception {
+ initialize(true /* supportFold */, false /* supportHalfFolded */);
+ mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
+ assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
+ mFoldStateListener.onStateChanged(mFoldedStates[0]);
+ assertEquals(mCurrentState, DeviceStateController.FoldState.FOLDED);
+ mFoldStateListener.onStateChanged(mHalfFoldedStates[0]);
+ assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN); // Ignored
+ }
+
+ @Test
+ public void testWithHalfFoldSupported() throws Exception {
+ initialize(true /* supportFold */, true /* supportHalfFolded */);
+ mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
+ assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
+ mFoldStateListener.onStateChanged(mFoldedStates[0]);
+ assertEquals(mCurrentState, DeviceStateController.FoldState.FOLDED);
+ mFoldStateListener.onStateChanged(mHalfFoldedStates[0]);
+ assertEquals(mCurrentState, DeviceStateController.FoldState.HALF_FOLDED);
+ }
+
+
+ private final int[] mFoldedStates = {0};
+ private final int[] mUnfoldedStates = {1};
+ private final int[] mHalfFoldedStates = {2};
+
+
+ private void verifyFoldStateListenerRegistration(int numOfInvocation) {
+ final ArgumentCaptor<DeviceStateController.FoldStateListener> listenerCaptor =
+ ArgumentCaptor.forClass(DeviceStateController.FoldStateListener.class);
+ verify(mMockDeviceStateManager, times(numOfInvocation)).registerCallback(
+ any(),
+ listenerCaptor.capture());
+ if (numOfInvocation > 0) {
+ mFoldStateListener = listenerCaptor.getValue();
+ }
+ }
+
+ private class DeviceStateControllerBuilder {
+ private boolean mSupportFold = false;
+ private boolean mSupportHalfFold = false;
+ private Consumer<DeviceStateController.FoldState> mDelegate;
+
+ DeviceStateControllerBuilder setSupportFold(
+ boolean supportFold, boolean supportHalfFold) {
+ mSupportFold = supportFold;
+ mSupportHalfFold = supportHalfFold;
+ return this;
+ }
+
+ DeviceStateControllerBuilder setDelegate(
+ Consumer<DeviceStateController.FoldState> delegate) {
+ mDelegate = delegate;
+ return this;
+ }
+
+ private void mockFold(boolean enableFold, boolean enableHalfFold) {
+ if (enableFold) {
+ when(mMockContext.getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates))
+ .thenReturn(mFoldedStates);
+ }
+ if (enableHalfFold) {
+ when(mMockContext.getResources().getIntArray(
+ com.android.internal.R.array.config_halfFoldedDeviceStates))
+ .thenReturn(mHalfFoldedStates);
+ }
+ }
+
+ private void build() throws Exception {
+ mMockContext = mock(Context.class);
+ mMockRes = mock(Resources.class);
+ when(mMockContext.getResources()).thenReturn((mMockRes));
+ mMockDeviceStateManager = mock(DeviceStateManager.class);
+ when(mMockContext.getSystemService(DeviceStateManager.class))
+ .thenReturn(mMockDeviceStateManager);
+ mockFold(mSupportFold, mSupportHalfFold);
+ mMockHandler = mock(Handler.class);
+ mTarget = new DeviceStateController(mMockContext, mMockHandler, mDelegate);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 89f7111..b45c37f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -28,6 +28,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atMost;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
@@ -103,7 +104,7 @@
private Context mMockContext;
private Resources mMockRes;
private SensorManager mMockSensorManager;
- private Sensor mFakeSensor;
+ private Sensor mFakeOrientationSensor;
private DisplayWindowSettings mMockDisplayWindowSettings;
private ContentResolver mMockResolver;
private FakeSettingsProvider mFakeSettingsProvider;
@@ -323,7 +324,7 @@
waitForUiHandler();
verify(mMockSensorManager, times(numOfInvocation)).registerListener(
listenerCaptor.capture(),
- same(mFakeSensor),
+ same(mFakeOrientationSensor),
anyInt(),
any());
if (numOfInvocation > 0) {
@@ -460,7 +461,7 @@
SensorEvent.class.getDeclaredConstructor(int.class);
constructor.setAccessible(true);
final SensorEvent event = constructor.newInstance(1);
- event.sensor = mFakeSensor;
+ event.sensor = mFakeOrientationSensor;
event.values[0] = rotation;
event.timestamp = SystemClock.elapsedRealtimeNanos();
return event;
@@ -691,6 +692,43 @@
SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
}
+ // ====================================================
+ // Tests for half-fold auto-rotate override of rotation
+ // ====================================================
+ @Test
+ public void testUpdatesRotationWhenSensorUpdates_RotationLocked_HalfFolded() throws Exception {
+ mBuilder.setSupportHalfFoldAutoRotateOverride(true);
+ mBuilder.build();
+ configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+ enableOrientationSensor();
+
+ mTarget.foldStateChanged(DeviceStateController.FoldState.OPEN);
+ freezeRotation(Surface.ROTATION_270);
+
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_0));
+ assertTrue(waitForUiHandler());
+ // No rotation...
+ assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+
+ // ... until half-fold
+ mTarget.foldStateChanged(DeviceStateController.FoldState.HALF_FOLDED);
+ assertTrue(waitForUiHandler());
+ verify(sMockWm).updateRotation(false, false);
+ assertTrue(waitForUiHandler());
+ assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+
+ // ... then transition back to flat
+ mTarget.foldStateChanged(DeviceStateController.FoldState.OPEN);
+ assertTrue(waitForUiHandler());
+ verify(sMockWm, atLeast(1)).updateRotation(false, false);
+ assertTrue(waitForUiHandler());
+ assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+ }
+
// =================================
// Tests for Policy based Rotation
// =================================
@@ -884,6 +922,7 @@
private class DisplayRotationBuilder {
private boolean mIsDefaultDisplay = true;
private boolean mSupportAutoRotation = true;
+ private boolean mSupportHalfFoldAutoRotateOverride = false;
private int mLidOpenRotation = WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
private int mCarDockRotation;
@@ -920,6 +959,12 @@
return this;
}
+ private DisplayRotationBuilder setSupportHalfFoldAutoRotateOverride(
+ boolean supportHalfFoldAutoRotateOverride) {
+ mSupportHalfFoldAutoRotateOverride = supportHalfFoldAutoRotateOverride;
+ return this;
+ }
+
private void captureObservers() {
ArgumentCaptor<ContentObserver> captor = ArgumentCaptor.forClass(
ContentObserver.class);
@@ -1032,9 +1077,13 @@
mMockSensorManager = mock(SensorManager.class);
when(mMockContext.getSystemService(Context.SENSOR_SERVICE))
.thenReturn(mMockSensorManager);
- mFakeSensor = createSensor(Sensor.TYPE_DEVICE_ORIENTATION);
+ mFakeOrientationSensor = createSensor(Sensor.TYPE_DEVICE_ORIENTATION);
when(mMockSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION)).thenReturn(
- Collections.singletonList(mFakeSensor));
+ Collections.singletonList(mFakeOrientationSensor));
+
+ when(mMockContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_windowManagerHalfFoldAutoRotateOverride))
+ .thenReturn(mSupportHalfFoldAutoRotateOverride);
mMockResolver = mock(ContentResolver.class);
when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 601cf15..64c1e05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -72,6 +72,7 @@
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Rect;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.MergedConfiguration;
@@ -377,6 +378,33 @@
assertEquals(WINDOWING_MODE_FULLSCREEN, fullscreenTask.getWindowingMode());
}
+ @Test
+ public void testMovingEmbeddedActivityToPip() {
+ final Rect taskBounds = new Rect(0, 0, 800, 1000);
+ final Rect taskFragmentBounds = new Rect(0, 0, 400, 1000);
+ final Task task = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
+ WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ task.setBounds(taskBounds);
+ assertEquals(taskBounds, task.getBounds());
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(2)
+ .setBounds(taskFragmentBounds)
+ .build();
+ assertEquals(taskFragmentBounds, taskFragment.getBounds());
+ final ActivityRecord topActivity = taskFragment.getTopMostActivity();
+
+ // Move the top activity to pinned root task.
+ mRootWindowContainer.moveActivityToPinnedRootTask(topActivity,
+ null /* launchIntoPipHostActivity */, "test");
+
+ final Task pinnedRootTask = task.getDisplayArea().getRootPinnedTask();
+
+ // Ensure the initial bounds of the PiP Task is the same as the TaskFragment.
+ ensureTaskPlacement(pinnedRootTask, topActivity);
+ assertEquals(taskFragmentBounds, pinnedRootTask.getBounds());
+ }
+
private static void ensureTaskPlacement(Task task, ActivityRecord... activities) {
final ArrayList<ActivityRecord> taskActivities = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index e57ad5d..24e932f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -57,10 +57,20 @@
.setLaunchTaskDisplayArea(token)
.setLaunchDisplayId(launchDisplayId)
.setCallerDisplayId(callerDisplayId))
- .selectiveCloneDisplayOptions();
+ .selectiveCloneLaunchOptions();
assertSame(clone.getOriginalOptions().getLaunchTaskDisplayArea(), token);
assertEquals(clone.getOriginalOptions().getLaunchDisplayId(), launchDisplayId);
assertEquals(clone.getOriginalOptions().getCallerDisplayId(), callerDisplayId);
}
+
+ @Test
+ public void test_selectiveCloneLunchRootTask() {
+ final WindowContainerToken token = mock(WindowContainerToken.class);
+ final SafeActivityOptions clone = new SafeActivityOptions(ActivityOptions.makeBasic()
+ .setLaunchRootTask(token))
+ .selectiveCloneLaunchOptions();
+
+ assertSame(clone.getOriginalOptions().getLaunchRootTask(), token);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 1404de2..0b23359 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -195,6 +195,7 @@
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
+ assertTaskFragmentParentInfoChangedTransaction(mTask);
assertTaskFragmentAppearedTransaction();
}
@@ -365,6 +366,7 @@
mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
+ assertTaskFragmentParentInfoChangedTransaction(task);
assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
}
@@ -1205,7 +1207,8 @@
/**
* Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
- * {@link WindowOrganizerController#applyTransaction} to apply the transaction,
+ * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the
+ * transaction,
*/
private void createTaskFragmentFromOrganizer(WindowContainerTransaction wct,
ActivityRecord ownerActivity, IBinder fragmentToken) {
@@ -1239,8 +1242,8 @@
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- // Appeared will come with parent info changed.
- final TaskFragmentTransaction.Change change = changes.get(changes.size() - 1);
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_APPEARED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
@@ -1253,8 +1256,8 @@
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- // InfoChanged may come with parent info changed.
- final TaskFragmentTransaction.Change change = changes.get(changes.size() - 1);
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
@@ -1266,7 +1269,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_VANISHED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
@@ -1278,7 +1283,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, change.getType());
assertEquals(task.mTaskId, change.getTaskId());
assertEquals(task.getTaskFragmentParentInfo(), change.getTaskFragmentParentInfo());
@@ -1290,7 +1297,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_ERROR, change.getType());
assertEquals(mErrorToken, change.getErrorCallbackToken());
final Bundle errorBundle = change.getErrorBundle();
@@ -1306,7 +1315,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
assertEquals(taskId, change.getTaskId());
assertEquals(intent, change.getActivityIntent());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 76cd19b..68ac1d6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1454,6 +1454,21 @@
verify(tfBehind, never()).resumeTopActivity(any(), any(), anyBoolean());
}
+ @Test
+ public void testGetTaskFragment() {
+ final Task parentTask = createTask(mDisplayContent);
+ final TaskFragment tf0 = createTaskFragmentWithParentTask(parentTask);
+ final TaskFragment tf1 = createTaskFragmentWithParentTask(parentTask);
+
+ assertNull("Could not find it because there's no organized TaskFragment",
+ parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
+
+ doReturn(true).when(tf0).isOrganizedTaskFragment();
+
+ assertEquals("tf0 must be return because it's the organized TaskFragment.",
+ tf0, parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
+ }
+
private Task getTestTask() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
return task.getBottomMostTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index e976fd4..9fd0850 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -19,7 +19,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
@@ -1229,10 +1228,8 @@
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
- // Set to multi-windowing mode in order to set bounds.
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
- task.setBounds(taskBounds);
+ task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(
@@ -1274,10 +1271,8 @@
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
- // Set to multi-windowing mode in order to set bounds.
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
- task.setBounds(taskBounds);
+ task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: set bounds to make sure the start bounds is ignored if it is not visible.
activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
@@ -1305,10 +1300,8 @@
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
- // Set to multi-windowing mode in order to set bounds.
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
- task.setBounds(taskBounds);
+ task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: fills Task without override.
activity.mVisibleRequested = true;
@@ -1329,6 +1322,35 @@
}
@Test
+ public void testReparentChangeLastParent() {
+ final Transition transition = createTestTransition(TRANSIT_CHANGE);
+ final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ // Reparent activity in transition.
+ final Task lastParent = createTask(mDisplayContent);
+ final Task newParent = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(lastParent);
+ activity.mVisibleRequested = true;
+ // Skip manipulate the SurfaceControl.
+ doNothing().when(activity).setDropInputMode(anyInt());
+ changes.put(activity, new Transition.ChangeInfo(activity));
+ activity.reparent(newParent, POSITION_TOP);
+ activity.mVisibleRequested = false;
+
+ participants.add(activity);
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ participants, changes);
+ final TransitionInfo info = Transition.calculateTransitionInfo(
+ transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+ // Change contains last parent info.
+ assertEquals(1, info.getChanges().size());
+ assertEquals(lastParent.mRemoteToken.toWindowContainerToken(),
+ info.getChanges().get(0).getLastParent());
+ }
+
+ @Test
public void testIncludeEmbeddedActivityReparent() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 5c1c193..b0d7ed6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -215,6 +215,12 @@
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
assertEquals(0, outConfig.getMergedConfiguration().densityDpi);
+ // Non activity window can still get the last config.
+ win.mActivityRecord = null;
+ win.fillClientWindowFramesAndConfiguration(outFrames, outConfig,
+ false /* useLatestConfig */, true /* relayoutVisible */);
+ assertEquals(win.getConfiguration().densityDpi,
+ outConfig.getMergedConfiguration().densityDpi);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 139e440..b99fd16 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -718,6 +718,10 @@
activity.mVisibleRequested = true;
}
+ static TaskFragment createTaskFragmentWithParentTask(@NonNull Task parentTask) {
+ return createTaskFragmentWithParentTask(parentTask, false /* createEmbeddedTask */);
+ }
+
/**
* Creates a {@link TaskFragment} and attach it to the {@code parentTask}.
*
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index eafcef2..1e74451 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 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 @@
// 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 @@
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 @@
packageName, translatedAppUid);
return;
}
- mActiveTranslations.put(activityToken,
+ mActiveTranslations.put(shareableActivityToken,
new ActiveTranslation(sourceSpec, targetSpec, translatedAppUid,
packageName));
}
@@ -317,7 +314,7 @@
case STATE_UI_TRANSLATION_FINISHED: {
if (activeTranslation != null) {
- mActiveTranslations.remove(activityToken);
+ mActiveTranslations.remove(shareableActivityToken);
}
break;
}
@@ -332,12 +329,12 @@
@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 @@
}
}
- 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 @@
pw.println(waitingFinishCallbackSize);
for (IBinder activityToken : mWaitingFinishedCallbackActivities) {
pw.print(prefix);
- pw.print("activityToken: ");
+ pw.print("shareableActivityToken: ");
pw.println(activityToken);
}
}
@@ -458,7 +447,14 @@
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 @@
@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;
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
new file mode 100644
index 0000000..efb92f2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.provider.Settings
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.Assert.assertNotNull
+
+class AssistantAppHelper @JvmOverloads constructor(
+ val instr: Instrumentation,
+ val component: ComponentName = ActivityOptions.ASSISTANT_SERVICE_COMPONENT_NAME,
+) {
+ protected val uiDevice: UiDevice = UiDevice.getInstance(instr)
+ protected val defaultAssistant: String? = Settings.Secure.getString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.ASSISTANT)
+ protected val defaultVoiceInteractionService: String? = Settings.Secure.getString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE)
+
+ fun setDefaultAssistant() {
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE,
+ component.flattenToString())
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.ASSISTANT,
+ component.flattenToString())
+ }
+
+ fun resetDefaultAssistant() {
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE,
+ defaultVoiceInteractionService)
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.ASSISTANT,
+ defaultAssistant)
+ }
+
+ /**
+ * Open Assistance UI.
+ *
+ * @param longpress open the UI by long pressing power button.
+ * Otherwise open the UI through vioceinteraction shell command directly.
+ */
+ @JvmOverloads
+ fun openUI(longpress: Boolean = false) {
+ if (longpress) {
+ uiDevice.executeShellCommand("input keyevent --longpress KEYCODE_POWER")
+ } else {
+ uiDevice.executeShellCommand("cmd voiceinteraction show")
+ }
+ val ui = uiDevice.wait(
+ Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")),
+ FIND_TIMEOUT)
+ assertNotNull("Can't find Assistant UI after long pressing power button.", ui)
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 45a4730..e90eed1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -80,20 +80,21 @@
public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp";
public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp";
public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".NotificationActivity");
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".NotificationActivity");
public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity";
- public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName(
- FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
+ public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp";
public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".GameActivity");
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".GameActivity");
+
+ public static final ComponentName ASSISTANT_SERVICE_COMPONENT_NAME =
+ new ComponentName(
+ FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".AssistantInteractionService");
}
diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt
index b2c5f4a..8e62388 100644
--- a/tools/xmlpersistence/src/main/kotlin/Generator.kt
+++ b/tools/xmlpersistence/src/main/kotlin/Generator.kt
@@ -149,6 +149,7 @@
when (field) {
is ClassFieldInfo -> this += field.allClassFields
is ListFieldInfo -> this += field.element.allClassFields
+ else -> {}
}
}
}