diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 7393bcde13b62940c9e6f165946c913bf1222286..21ed1eb6bef8e8a309f8e5affbef3117f7c6d957 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -37,7 +37,9 @@ import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.text.TextUtils;
import android.util.Log;
@@ -91,6 +93,14 @@ import java.util.concurrent.Executor;
public class AlarmManager {
private static final String TAG = "AlarmManager";
+ /**
+ * Prefix used by {{@link #makeTag(long, WorkSource)}} to make a tag on behalf of the caller
+ * when the {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)} API is
+ * used. This prefix is a unique sequence of characters to differentiate with other tags that
+ * apps may provide to other APIs that accept a listener callback.
+ */
+ private static final String GENERATED_TAG_PREFIX = "$android.alarm.generated";
+
/** @hide */
@IntDef(prefix = { "RTC", "ELAPSED" }, value = {
RTC_WAKEUP,
@@ -860,6 +870,24 @@ public class AlarmManager {
targetHandler, workSource, null);
}
+ /**
+ * This is only used to make an identifying tag for the deprecated
+ * {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)} API which doesn't
+ * accept a tag. For all other APIs, the tag provided by the app is used, even if it is
+ * {@code null}.
+ */
+ private static String makeTag(long triggerMillis, WorkSource ws) {
+ final StringBuilder tagBuilder = new StringBuilder(GENERATED_TAG_PREFIX);
+
+ tagBuilder.append(":");
+ final int attributionUid =
+ (ws == null || ws.isEmpty()) ? Process.myUid() : ws.getAttributionUid();
+ tagBuilder.append(UserHandle.formatUid(attributionUid));
+ tagBuilder.append(":");
+ tagBuilder.append(triggerMillis);
+ return tagBuilder.toString();
+ }
+
/**
* Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
* Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
@@ -875,8 +903,8 @@ public class AlarmManager {
public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
long intervalMillis, OnAlarmListener listener, Handler targetHandler,
WorkSource workSource) {
- setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
- targetHandler, workSource, null);
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener,
+ makeTag(triggerAtMillis, workSource), targetHandler, workSource, null);
}
/**
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 9b64edf53d8cf3660541f1cd75d11d0aaaca8381..f50a9024803000b6b544c81eb591eaea86db704d 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -225,6 +225,8 @@ public interface AppStandbyInternal {
void setActiveAdminApps(Set adminPkgs, int userId);
+ void setAdminProtectedPackages(Set packageNames, int userId);
+
/**
* @return {@code true} if the given package is an active device admin app.
*/
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index af6334cbbd8688aadf89f9eb3e19fe5d128cffcd..342465138ac73f87536f723a78600b02120ddb9c 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4739,8 +4739,14 @@ public class AlarmManagerService extends SystemService {
}
final ArraySet> triggerPackages =
new ArraySet<>();
+ final SparseIntArray countsPerUid = new SparseIntArray();
+ final SparseIntArray wakeupCountsPerUid = new SparseIntArray();
for (int i = 0; i < triggerList.size(); i++) {
final Alarm a = triggerList.get(i);
+ increment(countsPerUid, a.uid);
+ if (a.wakeup) {
+ increment(wakeupCountsPerUid, a.uid);
+ }
if (mConstants.USE_TARE_POLICY) {
if (!isExemptFromTare(a)) {
triggerPackages.add(Pair.create(
@@ -4761,7 +4767,8 @@ public class AlarmManagerService extends SystemService {
}
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
- MetricsHelper.pushAlarmBatchDelivered(triggerList.size(), wakeUps);
+ logAlarmBatchDelivered(
+ triggerList.size(), wakeUps, countsPerUid, wakeupCountsPerUid);
}
}
@@ -4776,6 +4783,32 @@ public class AlarmManagerService extends SystemService {
}
}
+ private static void increment(SparseIntArray array, int key) {
+ final int index = array.indexOfKey(key);
+ if (index >= 0) {
+ array.setValueAt(index, array.valueAt(index) + 1);
+ } else {
+ array.put(key, 1);
+ }
+ }
+
+ private void logAlarmBatchDelivered(
+ int alarms,
+ int wakeups,
+ SparseIntArray countsPerUid,
+ SparseIntArray wakeupCountsPerUid) {
+ final int[] uids = new int[countsPerUid.size()];
+ final int[] countsArray = new int[countsPerUid.size()];
+ final int[] wakeupCountsArray = new int[countsPerUid.size()];
+ for (int i = 0; i < countsPerUid.size(); i++) {
+ uids[i] = countsPerUid.keyAt(i);
+ countsArray[i] = countsPerUid.valueAt(i);
+ wakeupCountsArray[i] = wakeupCountsPerUid.get(uids[i], 0);
+ }
+ MetricsHelper.pushAlarmBatchDelivered(
+ alarms, wakeups, uids, countsArray, wakeupCountsArray);
+ }
+
/**
* Attribute blame for a WakeLock.
*
@@ -5695,12 +5728,7 @@ public class AlarmManagerService extends SystemService {
}
private void incrementAlarmCount(int uid) {
- final int uidIndex = mAlarmsPerUid.indexOfKey(uid);
- if (uidIndex >= 0) {
- mAlarmsPerUid.setValueAt(uidIndex, mAlarmsPerUid.valueAt(uidIndex) + 1);
- } else {
- mAlarmsPerUid.put(uid, 1);
- }
+ increment(mAlarmsPerUid, uid);
}
/**
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
index 75ed616e2d96467009f6da9aacb051d78cc36c7f..2923cfd8e22cc2b6055f0193fd27011f9a4ab641 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
@@ -111,10 +111,14 @@ class MetricsHelper {
ActivityManager.processStateAmToProto(callerProcState));
}
- static void pushAlarmBatchDelivered(int numAlarms, int wakeups) {
+ static void pushAlarmBatchDelivered(
+ int numAlarms, int wakeups, int[] uids, int[] alarmsPerUid, int[] wakeupAlarmsPerUid) {
FrameworkStatsLog.write(
FrameworkStatsLog.ALARM_BATCH_DELIVERED,
numAlarms,
- wakeups);
+ wakeups,
+ uids,
+ alarmsPerUid,
+ wakeupAlarmsPerUid);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index a6f47d4e4908180fb7164bb795b13af605333064..b27ff411dd584b6abd68f2a6d65f29cc8cd74157 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -264,6 +264,10 @@ public class AppStandbyController
@GuardedBy("mActiveAdminApps")
private final SparseArray> mActiveAdminApps = new SparseArray<>();
+ /** List of admin protected packages. Can contain {@link android.os.UserHandle#USER_ALL}. */
+ @GuardedBy("mAdminProtectedPackages")
+ private final SparseArray> mAdminProtectedPackages = new SparseArray<>();
+
/**
* Set of system apps that are headless (don't have any "front door" activities, enabled or
* disabled). Presence in this map indicates that the app is a headless system app.
@@ -1335,6 +1339,9 @@ public class AppStandbyController
synchronized (mActiveAdminApps) {
mActiveAdminApps.remove(userId);
}
+ synchronized (mAdminProtectedPackages) {
+ mAdminProtectedPackages.remove(userId);
+ }
}
}
@@ -1424,6 +1431,10 @@ public class AppStandbyController
return STANDBY_BUCKET_EXEMPTED;
}
+ if (isAdminProtectedPackages(packageName, userId)) {
+ return STANDBY_BUCKET_EXEMPTED;
+ }
+
if (isActiveNetworkScorer(packageName)) {
return STANDBY_BUCKET_EXEMPTED;
}
@@ -1871,6 +1882,17 @@ public class AppStandbyController
}
}
+ private boolean isAdminProtectedPackages(String packageName, int userId) {
+ synchronized (mAdminProtectedPackages) {
+ if (mAdminProtectedPackages.contains(UserHandle.USER_ALL)
+ && mAdminProtectedPackages.get(UserHandle.USER_ALL).contains(packageName)) {
+ return true;
+ }
+ return mAdminProtectedPackages.contains(userId)
+ && mAdminProtectedPackages.get(userId).contains(packageName);
+ }
+ }
+
@Override
public void addActiveDeviceAdmin(String adminPkg, int userId) {
synchronized (mActiveAdminApps) {
@@ -1894,6 +1916,17 @@ public class AppStandbyController
}
}
+ @Override
+ public void setAdminProtectedPackages(Set packageNames, int userId) {
+ synchronized (mAdminProtectedPackages) {
+ if (packageNames == null || packageNames.isEmpty()) {
+ mAdminProtectedPackages.remove(userId);
+ } else {
+ mAdminProtectedPackages.put(userId, packageNames);
+ }
+ }
+ }
+
@Override
public void onAdminDataAvailable() {
mAdminDataAvailableLatch.countDown();
@@ -1916,6 +1949,13 @@ public class AppStandbyController
}
}
+ @VisibleForTesting
+ Set getAdminProtectedPackagesForTest(int userId) {
+ synchronized (mAdminProtectedPackages) {
+ return mAdminProtectedPackages.get(userId);
+ }
+ }
+
/**
* Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
* returns {@code false}.
diff --git a/config/preloaded-classes b/config/preloaded-classes
index a6c0c3d7eb15fbcc0d165fb79dce933e754aac44..6de1461cc0df9c185f1a7b1edc5d6ab5367eac33 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -14414,6 +14414,7 @@ java.util.ImmutableCollections$Set0
java.util.ImmutableCollections$Set1
java.util.ImmutableCollections$Set2
java.util.ImmutableCollections$SetN
+java.util.ImmutableCollections
java.util.InputMismatchException
java.util.Iterator
java.util.JumboEnumSet$EnumSetIterator
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6e61569a835255daaf4940197124a395979ccddb..04b48ceadf967743fe703136e7c05486cd01a9d7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -809,6 +809,7 @@ package android.content.pm {
field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
+ field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -1151,7 +1152,6 @@ package android.hardware.camera2 {
public final class CameraManager {
method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
- field public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L; // 0xef10e60L
}
public abstract static class CameraManager.AvailabilityCallback {
@@ -2442,6 +2442,7 @@ package android.service.dreams {
public abstract class DreamOverlayService extends android.app.Service {
ctor public DreamOverlayService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public void onEndDream();
method public abstract void onStartDream(@NonNull android.view.WindowManager.LayoutParams);
method public final void requestExit();
method public final boolean shouldShowComplications();
@@ -2947,7 +2948,9 @@ package android.view {
}
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+ method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
method public android.view.View getTooltipView();
+ method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1807fb66da8f600d80a57e0317eaf63fa3ef77d3..cf11c5c7d431f6a90be164109240411f1b3a9bff 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -997,6 +997,8 @@ public class Activity extends ContextThemeWrapper
private ComponentCallbacksController mCallbacksController;
+ @Nullable private IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
/**
@@ -1606,18 +1608,17 @@ public class Activity extends ContextThemeWrapper
private void notifyVoiceInteractionManagerServiceActivityEvent(
@VoiceInteractionSession.VoiceInteractionActivityEventType int type) {
-
- final IVoiceInteractionManagerService service =
- IVoiceInteractionManagerService.Stub.asInterface(
- ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
- if (service == null) {
- Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
- + "VoiceInteractionManagerService");
- return;
+ if (mVoiceInteractionManagerService == null) {
+ mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ if (mVoiceInteractionManagerService == null) {
+ Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
+ + "VoiceInteractionManagerService");
+ return;
+ }
}
-
try {
- service.notifyActivityEventChanged(mToken, type);
+ mVoiceInteractionManagerService.notifyActivityEventChanged(mToken, type);
} catch (RemoteException e) {
// Empty
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 9328664683143ded89e903bdee15ff33b2a6616c..fec522b7ff7cff948ec187ec81826dcc7f84f3a7 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4061,6 +4061,9 @@ public class ActivityManager {
* processes to reclaim memory; the system will take care of restarting
* these processes in the future as needed.
*
+ *
Third party applications can only use this API to kill their own processes.
+ *
+ *
* @param packageName The name of the package whose processes are to
* be killed.
*/
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index e00329385ca554b56c20f0729eb1f7746d2e0a32..53e0a05b27b1ca7ecf33703a0d1183b39acb173b 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -326,6 +326,20 @@ public class ActivityOptions extends ComponentOptions {
private static final String KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES =
"android:activity.applyActivityFlagsForBubbles";
+ /**
+ * Indicates to apply {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the launching shortcut.
+ * @hide
+ */
+ private static final String KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT =
+ "android:activity.applyMultipleTaskFlagForShortcut";
+
+ /**
+ * Indicates to apply {@link Intent#FLAG_ACTIVITY_NO_USER_ACTION} to the launching shortcut.
+ * @hide
+ */
+ private static final String KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT =
+ "android:activity.applyNoUserActionFlagForShortcut";
+
/**
* For Activity transitions, the calling Activity's TransitionListener used to
* notify the called Activity when the shared element and the exit transitions
@@ -459,6 +473,8 @@ public class ActivityOptions extends ComponentOptions {
private boolean mLockTaskMode = false;
private boolean mDisallowEnterPictureInPictureWhileLaunching;
private boolean mApplyActivityFlagsForBubbles;
+ private boolean mApplyMultipleTaskFlagForShortcut;
+ private boolean mApplyNoUserActionFlagForShortcut;
private boolean mTaskAlwaysOnTop;
private boolean mTaskOverlay;
private boolean mTaskOverlayCanResume;
@@ -1258,6 +1274,10 @@ public class ActivityOptions extends ComponentOptions {
KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
mApplyActivityFlagsForBubbles = opts.getBoolean(
KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, false);
+ mApplyMultipleTaskFlagForShortcut = opts.getBoolean(
+ KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT, false);
+ mApplyNoUserActionFlagForShortcut = opts.getBoolean(
+ KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT, false);
if (opts.containsKey(KEY_ANIM_SPECS)) {
Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
mAnimSpecs = new AppTransitionAnimationSpec[specs.length];
@@ -1844,6 +1864,26 @@ public class ActivityOptions extends ComponentOptions {
return mApplyActivityFlagsForBubbles;
}
+ /** @hide */
+ public void setApplyMultipleTaskFlagForShortcut(boolean apply) {
+ mApplyMultipleTaskFlagForShortcut = apply;
+ }
+
+ /** @hide */
+ public boolean isApplyMultipleTaskFlagForShortcut() {
+ return mApplyMultipleTaskFlagForShortcut;
+ }
+
+ /** @hide */
+ public void setApplyNoUserActionFlagForShortcut(boolean apply) {
+ mApplyNoUserActionFlagForShortcut = apply;
+ }
+
+ /** @hide */
+ public boolean isApplyNoUserActionFlagForShortcut() {
+ return mApplyNoUserActionFlagForShortcut;
+ }
+
/**
* Sets a launch cookie that can be used to track the activity and task that are launch as a
* result of this option. If the launched activity is a trampoline that starts another activity
@@ -2175,6 +2215,13 @@ public class ActivityOptions extends ComponentOptions {
if (mApplyActivityFlagsForBubbles) {
b.putBoolean(KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, mApplyActivityFlagsForBubbles);
}
+ if (mApplyMultipleTaskFlagForShortcut) {
+ b.putBoolean(KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT,
+ mApplyMultipleTaskFlagForShortcut);
+ }
+ if (mApplyNoUserActionFlagForShortcut) {
+ b.putBoolean(KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT, true);
+ }
if (mAnimSpecs != null) {
b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a1e4dbf0b50d62e4673be0759a9675ea984970ba..2c8264423f2dd0fba91d590ea91de7d8d406c38c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -29,6 +29,8 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
+import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
@@ -198,6 +200,7 @@ import android.window.SplashScreen;
import android.window.SplashScreenView;
import android.window.WindowProviderService;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
@@ -5906,9 +5909,21 @@ public final class ActivityThread extends ClientTransactionHandler
final boolean shouldUpdateResources = hasPublicResConfigChange
|| shouldUpdateResources(activityToken, currentResConfig, newConfig,
amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
+
+ // TODO(b/266924897): temporary workaround, remove for U.
+ boolean skipActivityRelaunchWhenDocking = activity.getResources().getBoolean(
+ R.bool.config_skipActivityRelaunchWhenDocking);
+ int handledConfigChanges = activity.mActivityInfo.getRealConfigChanged();
+ if (skipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(activity.mCurrentConfig,
+ newConfig)) {
+ // If we're not relaunching this activity when docking, we should send the configuration
+ // changed event. Pretend as if the activity is handling uiMode config changes in its
+ // manifest so that we'll report any dock changes.
+ handledConfigChanges |= ActivityInfo.CONFIG_UI_MODE;
+ }
+
final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
- r != null ? r.mSizeConfigurations : null,
- activity.mActivityInfo.getRealConfigChanged());
+ r != null ? r.mSizeConfigurations : null, handledConfigChanges);
// Nothing significant, don't proceed with updating and reporting.
if (!shouldUpdateResources && !shouldReportChange) {
return null;
@@ -5954,6 +5969,25 @@ public final class ActivityThread extends ClientTransactionHandler
return configToReport;
}
+ /**
+ * Returns true if the uiMode configuration changed, and desk mode
+ * ({@link android.content.res.Configuration#UI_MODE_TYPE_DESK}) was the only change to uiMode.
+ */
+ private boolean onlyDeskInUiModeChanged(Configuration oldConfig, Configuration newConfig) {
+ boolean deskModeChanged = isInDeskUiMode(oldConfig) != isInDeskUiMode(newConfig);
+
+ // UI mode contains fields other than the UI mode type, so determine if any other fields
+ // changed.
+ boolean uiModeOtherFieldsChanged =
+ (oldConfig.uiMode & ~UI_MODE_TYPE_MASK) != (newConfig.uiMode & ~UI_MODE_TYPE_MASK);
+
+ return deskModeChanged && !uiModeOtherFieldsChanged;
+ }
+
+ private static boolean isInDeskUiMode(Configuration config) {
+ return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK;
+ }
+
/**
* Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be
* dispatched.
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 28c273ec50a6eb1f2c2cc957d160877907b9b947..f6056bd39005d15ffe1cccd42a099f0ba2ecbaa7 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -205,6 +205,20 @@ interface IWallpaperManager {
*/
void notifyGoingToSleep(int x, int y, in Bundle extras);
+ /**
+ * Called when the screen has been fully turned on and is visible.
+ *
+ * @hide
+ */
+ void notifyScreenTurnedOn(int displayId);
+
+ /**
+ * Called when the screen starts turning on.
+ *
+ * @hide
+ */
+ void notifyScreenTurningOn(int displayId);
+
/**
* Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default
* dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7f4af8b3dc78f686563a80546251071b0408aef4..90e3e9d1a9c33a2687143ce7e090b37a2c2e38f7 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6189,10 +6189,8 @@ public class Notification implements Parcelable
private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
StandardTemplateParams p) {
final boolean tombstone = (action.actionIntent == null);
- RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
- emphasizedMode ? getEmphasizedActionLayoutResource()
- : tombstone ? getActionTombstoneLayoutResource()
- : getActionLayoutResource());
+ final RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
+ getActionButtonLayoutResource(emphasizedMode, tombstone));
if (!tombstone) {
button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
}
@@ -6204,6 +6202,12 @@ public class Notification implements Parcelable
// change the background bgColor
CharSequence title = action.title;
int buttonFillColor = getColors(p).getSecondaryAccentColor();
+ if (tombstone) {
+ buttonFillColor = setAlphaComponentByFloatDimen(mContext,
+ ContrastColorUtil.resolveSecondaryColor(
+ mContext, getColors(p).getBackgroundColor(), mInNightMode),
+ R.dimen.notification_action_disabled_container_alpha);
+ }
if (isLegacy()) {
title = ContrastColorUtil.clearColorSpans(title);
} else {
@@ -6219,8 +6223,14 @@ public class Notification implements Parcelable
title = ensureColorSpanContrast(title, buttonFillColor);
}
button.setTextViewText(R.id.action0, processTextSpans(title));
- final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+ int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
buttonFillColor, mInNightMode);
+ if (tombstone) {
+ textColor = setAlphaComponentByFloatDimen(mContext,
+ ContrastColorUtil.resolveSecondaryColor(
+ mContext, getColors(p).getBackgroundColor(), mInNightMode),
+ R.dimen.notification_action_disabled_content_alpha);
+ }
button.setTextColor(R.id.action0, textColor);
// We only want about 20% alpha for the ripple
final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
@@ -6250,6 +6260,26 @@ public class Notification implements Parcelable
return button;
}
+ private int getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone) {
+ if (emphasizedMode) {
+ return tombstone ? getEmphasizedTombstoneActionLayoutResource()
+ : getEmphasizedActionLayoutResource();
+ } else {
+ return tombstone ? getActionTombstoneLayoutResource()
+ : getActionLayoutResource();
+ }
+ }
+
+ /**
+ * Set the alpha component of {@code color} to be {@code alphaDimenResId}.
+ */
+ private static int setAlphaComponentByFloatDimen(Context context, @ColorInt int color,
+ @DimenRes int alphaDimenResId) {
+ final TypedValue alphaValue = new TypedValue();
+ context.getResources().getValue(alphaDimenResId, alphaValue, true);
+ return ColorUtils.setAlphaComponent(color, Math.round(alphaValue.getFloat() * 255));
+ }
+
/**
* Extract the color from a full-length span from the text.
*
@@ -6729,6 +6759,10 @@ public class Notification implements Parcelable
return R.layout.notification_material_action_emphasized;
}
+ private int getEmphasizedTombstoneActionLayoutResource() {
+ return R.layout.notification_material_action_emphasized_tombstone;
+ }
+
private int getActionTombstoneLayoutResource() {
return R.layout.notification_material_action_tombstone;
}
@@ -6935,8 +6969,10 @@ public class Notification implements Parcelable
/**
* Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
* permission. The permission is checked when a notification is enqueued.
+ *
+ * @hide
*/
- private boolean hasColorizedPermission() {
+ public boolean hasColorizedPermission() {
return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
}
@@ -7948,8 +7984,6 @@ public class Notification implements Parcelable
* @hide
*/
public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
- // TODO(b/228941516): This icon should be downscaled to avoid using too much memory,
- // see reduceImageSizes.
mShortcutIcon = conversationIcon;
return this;
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f6d27ad08b00980495c43eae8982ba8f934bff8d..37a90de8d6001f333bc2d4224a5c5c5bfd332a7d 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -317,7 +317,10 @@ public class NotificationManager {
/**
* Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes.
- * This broadcast is only sent to registered receivers.
+ *
+ *
This broadcast is only sent to registered receivers and (starting from
+ * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
+ * Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
*
* @hide
*/
@@ -337,7 +340,10 @@ public class NotificationManager {
/**
* Intent that is broadcast when the state of getNotificationPolicy() changes.
- * This broadcast is only sent to registered receivers.
+ *
+ *
This broadcast is only sent to registered receivers and (starting from
+ * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
+ * Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_POLICY_CHANGED
@@ -345,7 +351,10 @@ public class NotificationManager {
/**
* Intent that is broadcast when the state of getCurrentInterruptionFilter() changes.
- * This broadcast is only sent to registered receivers.
+ *
+ *
This broadcast is only sent to registered receivers and (starting from
+ * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
+ * Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_INTERRUPTION_FILTER_CHANGED
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index a51b9d3956df247a9f50a2e687b748ee305dccf0..27f9f54d95229aef5958fb9f9b56b6752694cac3 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1405,6 +1405,17 @@ public class PropertyInvalidatedCache {
return isDisabled();
}
+ /**
+ * Return the number of entries in the cache. This is used for testing and has package-only
+ * visibility.
+ * @hide
+ */
+ public int size() {
+ synchronized (mLock) {
+ return mCache.size();
+ }
+ }
+
/**
* Returns a list of caches alive at the current time.
*/
@@ -1612,8 +1623,12 @@ public class PropertyInvalidatedCache {
* @hide
*/
public static void onTrimMemory() {
- for (PropertyInvalidatedCache pic : getActiveCaches()) {
- pic.clear();
+ ArrayList activeCaches;
+ synchronized (sGlobalLock) {
+ activeCaches = getActiveCaches();
+ }
+ for (int i = 0; i < activeCaches.size(); i++) {
+ activeCaches.get(i).clear();
}
}
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 3bf3067f8410d2fdeb90557022f9d1408115087a..1551ce9a91912bcd60460fcb16a46928d7319dd0 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -48,6 +48,12 @@ import java.util.Objects;
public class TaskInfo {
private static final String TAG = "TaskInfo";
+ /**
+ * The value to use when the property has not a specific value.
+ * @hide
+ */
+ public static final int PROPERTY_VALUE_UNSET = -1;
+
/**
* The id of the user the task was running as if this is a leaf task. The id of the current
* running user of the system otherwise.
@@ -187,6 +193,14 @@ public class TaskInfo {
*/
public int launchIntoPipHostTaskId;
+ /**
+ * The task id of the parent Task of the launch-into-pip Activity, i.e., if task have more than
+ * one activity it will create new task for this activity, this id is the origin task id and
+ * the pip activity will be reparent to origin task when it exit pip mode.
+ * @hide
+ */
+ public int lastParentTaskIdBeforePip;
+
/**
* The {@link Rect} copied from {@link DisplayCutout#getSafeInsets()} if the cutout is not of
* (LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS),
@@ -221,6 +235,46 @@ public class TaskInfo {
*/
public boolean topActivityEligibleForLetterboxEducation;
+ /**
+ * Whether the double tap is enabled
+ * @hide
+ */
+ public boolean isLetterboxDoubleTapEnabled;
+
+ /**
+ * Whether the update comes from a letterbox double-tap action from the user or not.
+ * @hide
+ */
+ public boolean isFromLetterboxDoubleTap;
+
+ /**
+ * If {@link isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position or
+ * {@link TaskInfo.PROPERTY_VALUE_UNSET} otherwise.
+ * @hide
+ */
+ public int topActivityLetterboxVerticalPosition;
+
+ /**
+ * If {@link isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position or
+ * {@link TaskInfo.PROPERTY_VALUE_UNSET} otherwise.
+ * @hide
+ */
+ public int topActivityLetterboxHorizontalPosition;
+
+ /**
+ * If {@link isLetterboxDoubleTapEnabled} it contains the current width of the letterboxed
+ * activity or {@link TaskInfo.PROPERTY_VALUE_UNSET} otherwise
+ * @hide
+ */
+ public int topActivityLetterboxWidth;
+
+ /**
+ * If {@link isLetterboxDoubleTapEnabled} it contains the current height of the letterboxed
+ * activity or {@link TaskInfo.PROPERTY_VALUE_UNSET} otherwise
+ * @hide
+ */
+ public int topActivityLetterboxHeight;
+
/**
* Whether this task is resizable. Unlike {@link #resizeMode} (which is what the top activity
* supports), this is what the system actually uses for resizability based on other policy and
@@ -399,7 +453,8 @@ public class TaskInfo {
/** @hide */
public boolean hasCompatUI() {
return hasCameraCompatControl() || topActivityInSizeCompat
- || topActivityEligibleForLetterboxEducation;
+ || topActivityEligibleForLetterboxEducation
+ || isLetterboxDoubleTapEnabled;
}
/**
@@ -439,11 +494,18 @@ public class TaskInfo {
&& isResizeable == that.isResizeable
&& supportsMultiWindow == that.supportsMultiWindow
&& displayAreaFeatureId == that.displayAreaFeatureId
+ && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
+ && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
+ && topActivityLetterboxWidth == that.topActivityLetterboxWidth
+ && topActivityLetterboxHeight == that.topActivityLetterboxHeight
+ && topActivityLetterboxHorizontalPosition
+ == that.topActivityLetterboxHorizontalPosition
&& Objects.equals(positionInParent, that.positionInParent)
&& Objects.equals(pictureInPictureParams, that.pictureInPictureParams)
&& Objects.equals(shouldDockBigOverlays, that.shouldDockBigOverlays)
&& Objects.equals(displayCutoutInsets, that.displayCutoutInsets)
&& getWindowingMode() == that.getWindowingMode()
+ && configuration.uiMode == that.configuration.uiMode
&& Objects.equals(taskDescription, that.taskDescription)
&& isFocused == that.isFocused
&& isVisible == that.isVisible
@@ -464,14 +526,21 @@ public class TaskInfo {
return displayId == that.displayId
&& taskId == that.taskId
&& topActivityInSizeCompat == that.topActivityInSizeCompat
+ && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
&& topActivityEligibleForLetterboxEducation
== that.topActivityEligibleForLetterboxEducation
+ && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
+ && topActivityLetterboxHorizontalPosition
+ == that.topActivityLetterboxHorizontalPosition
+ && topActivityLetterboxWidth == that.topActivityLetterboxWidth
+ && topActivityLetterboxHeight == that.topActivityLetterboxHeight
&& cameraCompatControlState == that.cameraCompatControlState
// Bounds are important if top activity has compat controls.
&& (!hasCompatUI() || configuration.windowConfiguration.getBounds()
.equals(that.configuration.windowConfiguration.getBounds()))
&& (!hasCompatUI() || configuration.getLayoutDirection()
== that.configuration.getLayoutDirection())
+ && (!hasCompatUI() || configuration.uiMode == that.configuration.uiMode)
&& (!hasCompatUI() || isVisible == that.isVisible);
}
@@ -501,6 +570,7 @@ public class TaskInfo {
pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR);
shouldDockBigOverlays = source.readBoolean();
launchIntoPipHostTaskId = source.readInt();
+ lastParentTaskIdBeforePip = source.readInt();
displayCutoutInsets = source.readTypedObject(Rect.CREATOR);
topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
isResizeable = source.readBoolean();
@@ -518,6 +588,12 @@ public class TaskInfo {
mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
displayAreaFeatureId = source.readInt();
cameraCompatControlState = source.readInt();
+ isLetterboxDoubleTapEnabled = source.readBoolean();
+ isFromLetterboxDoubleTap = source.readBoolean();
+ topActivityLetterboxVerticalPosition = source.readInt();
+ topActivityLetterboxHorizontalPosition = source.readInt();
+ topActivityLetterboxWidth = source.readInt();
+ topActivityLetterboxHeight = source.readInt();
}
/**
@@ -547,6 +623,7 @@ public class TaskInfo {
dest.writeTypedObject(pictureInPictureParams, flags);
dest.writeBoolean(shouldDockBigOverlays);
dest.writeInt(launchIntoPipHostTaskId);
+ dest.writeInt(lastParentTaskIdBeforePip);
dest.writeTypedObject(displayCutoutInsets, flags);
dest.writeTypedObject(topActivityInfo, flags);
dest.writeBoolean(isResizeable);
@@ -564,6 +641,12 @@ public class TaskInfo {
dest.writeTypedObject(mTopActivityLocusId, flags);
dest.writeInt(displayAreaFeatureId);
dest.writeInt(cameraCompatControlState);
+ dest.writeBoolean(isLetterboxDoubleTapEnabled);
+ dest.writeBoolean(isFromLetterboxDoubleTap);
+ dest.writeInt(topActivityLetterboxVerticalPosition);
+ dest.writeInt(topActivityLetterboxHorizontalPosition);
+ dest.writeInt(topActivityLetterboxWidth);
+ dest.writeInt(topActivityLetterboxHeight);
}
@Override
@@ -587,6 +670,7 @@ public class TaskInfo {
+ " pictureInPictureParams=" + pictureInPictureParams
+ " shouldDockBigOverlays=" + shouldDockBigOverlays
+ " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId
+ + " lastParentTaskIdBeforePip=" + lastParentTaskIdBeforePip
+ " displayCutoutSafeInsets=" + displayCutoutInsets
+ " topActivityInfo=" + topActivityInfo
+ " launchCookies=" + launchCookies
@@ -598,6 +682,13 @@ public class TaskInfo {
+ " topActivityInSizeCompat=" + topActivityInSizeCompat
+ " topActivityEligibleForLetterboxEducation= "
+ topActivityEligibleForLetterboxEducation
+ + " topActivityLetterboxed= " + isLetterboxDoubleTapEnabled
+ + " isFromDoubleTap= " + isFromLetterboxDoubleTap
+ + " topActivityLetterboxVerticalPosition= " + topActivityLetterboxVerticalPosition
+ + " topActivityLetterboxHorizontalPosition= "
+ + topActivityLetterboxHorizontalPosition
+ + " topActivityLetterboxWidth=" + topActivityLetterboxWidth
+ + " topActivityLetterboxHeight=" + topActivityLetterboxHeight
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " cameraCompatControlState="
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 067a4c3c047ea348adea44def2f1a15946ff90f0..a34a50c4b7b07bc49ad6ecf5bcd28eb84af06cb9 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -27,6 +27,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemProperties;
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
@@ -101,11 +102,13 @@ public final class WallpaperColors implements Parcelable {
// Decides when dark theme is optimal for this wallpaper
private static final float DARK_THEME_MEAN_LUMINANCE = 0.3f;
// Minimum mean luminosity that an image needs to have to support dark text
- private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.7f;
+ private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = SystemProperties.getInt(
+ "persist.wallpapercolors.threshold", 70) / 100f;
// We also check if the image has dark pixels in it,
// to avoid bright images with some dark spots.
private static final float DARK_PIXEL_CONTRAST = 5.5f;
- private static final float MAX_DARK_AREA = 0.05f;
+ private static final float MAX_DARK_AREA = SystemProperties.getInt(
+ "persist.wallpapercolors.max_dark_area", 5) / 100f;
private final List mMainColors;
private final Map mAllColors;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 67d51481830f0792a4011b41c83cc39ba1a834df..d99e4575ea4246a2485b0e866eab53e57d75adbb 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6639,6 +6639,13 @@ public class DevicePolicyManager {
*/
public static final int KEYGUARD_DISABLE_IRIS = 1 << 8;
+ /**
+ * Disable all keyguard shortcuts.
+ *
+ * @hide
+ */
+ public static final int KEYGUARD_DISABLE_SHORTCUTS_ALL = 1 << 9;
+
/**
* NOTE: Please remember to update the DevicePolicyManagerTest's testKeyguardDisabledFeatures
* CTS test when adding to the list above.
@@ -6682,7 +6689,8 @@ public class DevicePolicyManager {
*/
public static final int ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY =
DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
- | DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+ | DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS
+ | DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL;
/**
* Keyguard features that when set on a normal or organization-owned managed profile, have
@@ -6789,6 +6797,8 @@ public class DevicePolicyManager {
* {@link #ENCRYPTION_STATUS_UNSUPPORTED}, {@link #ENCRYPTION_STATUS_INACTIVE},
* {@link #ENCRYPTION_STATUS_ACTIVATING}, {@link #ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY},
* {@link #ENCRYPTION_STATUS_ACTIVE}, or {@link #ENCRYPTION_STATUS_ACTIVE_PER_USER}.
+ *
+ * @throws SecurityException if called on a parent instance.
*/
public int getStorageEncryptionStatus() {
throwIfParentInstance("getStorageEncryptionStatus");
@@ -9180,7 +9190,8 @@ public class DevicePolicyManager {
* @see #isProfileOwnerApp
* @see #isDeviceOwnerApp
* @param admin Which {@link DeviceAdminReceiver} this request is associate with.
- * @param profileName The name of the profile.
+ * @param profileName The name of the profile. If the name is longer than 200 characters
+ * it will be truncated.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
public void setProfileName(@NonNull ComponentName admin, String profileName) {
@@ -14627,7 +14638,8 @@ public class DevicePolicyManager {
/**
* Called by a device owner or a profile owner to disable user control over apps. User will not
* be able to clear app data or force-stop packages. When called by a device owner, applies to
- * all users on the device.
+ * all users on the device. Packages with user control disabled are exempted from
+ * App Standby Buckets.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with
* @param packages The package names for the apps.
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index fe10b7f8b3f4e4818daa277da45303f1dcd7400d..27f6a266597cd4a502a918a3b4fd3b8b509fef9c 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -31,6 +31,7 @@ import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
+import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -311,19 +312,26 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
super.onLayout(changed, left, top, right, bottom);
} catch (final RuntimeException e) {
Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e);
- removeViewInLayout(mView);
- View child = getErrorView();
- prepareView(child);
- addViewInLayout(child, 0, child.getLayoutParams());
- measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
- child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight,
- child.getMeasuredHeight() + mPaddingTop + mPaddingBottom);
- mView = child;
- mViewMode = VIEW_MODE_ERROR;
+ handleViewError();
}
}
+ /**
+ * Remove bad view and replace with error message view
+ */
+ private void handleViewError() {
+ removeViewInLayout(mView);
+ View child = getErrorView();
+ prepareView(child);
+ addViewInLayout(child, 0, child.getLayoutParams());
+ measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
+ child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight,
+ child.getMeasuredHeight() + mPaddingTop + mPaddingBottom);
+ mView = child;
+ mViewMode = VIEW_MODE_ERROR;
+ }
+
/**
* Provide guidance about the size of this widget to the AppWidgetManager. The widths and
* heights should correspond to the full area the AppWidgetHostView is given. Padding added by
@@ -953,4 +961,15 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
reapplyLastRemoteViews();
}
}
+
+ @Override
+ protected void dispatchDraw(@NonNull Canvas canvas) {
+ try {
+ super.dispatchDraw(canvas);
+ } catch (Exception e) {
+ // Catch draw exceptions that may be caused by RemoteViews
+ Log.e(TAG, "Drawing view failed: " + e);
+ post(this::handleViewError);
+ }
+ }
}
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
index 77072890a1eb0b40485f3a68825139e24dad1ab6..856bde870bcfef5f711269d3f5afa4aa7376b949 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -69,6 +69,12 @@ public final class ContentCaptureOptions implements Parcelable {
*/
public final int logHistorySize;
+ /**
+ * Disable flush when receiving a VIEW_TREE_APPEARING event.
+ * @hide
+ */
+ public final boolean disableFlushForViewTreeAppearing;
+
/**
* List of activities explicitly allowlisted for content capture (or {@code null} if allowlisted
* for all acitivites in the package).
@@ -90,7 +96,8 @@ public final class ContentCaptureOptions implements Parcelable {
public ContentCaptureOptions(int loggingLevel) {
this(/* lite= */ true, loggingLevel, /* maxBufferSize= */ 0,
/* idleFlushingFrequencyMs= */ 0, /* textChangeFlushingFrequencyMs= */ 0,
- /* logHistorySize= */ 0, /* whitelistedComponents= */ null);
+ /* logHistorySize= */ 0, /* disableFlushForViewTreeAppearing= */ false,
+ /* whitelistedComponents= */ null);
}
/**
@@ -98,10 +105,23 @@ public final class ContentCaptureOptions implements Parcelable {
*/
public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
int textChangeFlushingFrequencyMs, int logHistorySize,
- @SuppressLint("NullableCollection")
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
+ @Nullable ArraySet whitelistedComponents) {
+ this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
+ textChangeFlushingFrequencyMs, logHistorySize,
+ ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+ whitelistedComponents);
+ }
+
+ /** @hide */
+ public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
+ int textChangeFlushingFrequencyMs, int logHistorySize,
+ boolean disableFlushForViewTreeAppearing,
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
@Nullable ArraySet whitelistedComponents) {
this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
- textChangeFlushingFrequencyMs, logHistorySize, whitelistedComponents);
+ textChangeFlushingFrequencyMs, logHistorySize, disableFlushForViewTreeAppearing,
+ whitelistedComponents);
}
/** @hide */
@@ -111,11 +131,14 @@ public final class ContentCaptureOptions implements Parcelable {
ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
- ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE, whitelistedComponents);
+ ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
+ ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+ whitelistedComponents);
}
private ContentCaptureOptions(boolean lite, int loggingLevel, int maxBufferSize,
int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize,
+ boolean disableFlushForViewTreeAppearing,
@Nullable ArraySet whitelistedComponents) {
this.lite = lite;
this.loggingLevel = loggingLevel;
@@ -123,6 +146,7 @@ public final class ContentCaptureOptions implements Parcelable {
this.idleFlushingFrequencyMs = idleFlushingFrequencyMs;
this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
this.logHistorySize = logHistorySize;
+ this.disableFlushForViewTreeAppearing = disableFlushForViewTreeAppearing;
this.whitelistedComponents = whitelistedComponents;
}
@@ -171,7 +195,8 @@ public final class ContentCaptureOptions implements Parcelable {
.append(", maxBufferSize=").append(maxBufferSize)
.append(", idleFlushingFrequencyMs=").append(idleFlushingFrequencyMs)
.append(", textChangeFlushingFrequencyMs=").append(textChangeFlushingFrequencyMs)
- .append(", logHistorySize=").append(logHistorySize);
+ .append(", logHistorySize=").append(logHistorySize)
+ .append(", disableFlushForViewTreeAppearing=").append(disableFlushForViewTreeAppearing);
if (whitelistedComponents != null) {
string.append(", whitelisted=").append(whitelistedComponents);
}
@@ -189,6 +214,7 @@ public final class ContentCaptureOptions implements Parcelable {
pw.print(", idle="); pw.print(idleFlushingFrequencyMs);
pw.print(", textIdle="); pw.print(textChangeFlushingFrequencyMs);
pw.print(", logSize="); pw.print(logHistorySize);
+ pw.print(", disableFlushForViewTreeAppearing="); pw.print(disableFlushForViewTreeAppearing);
if (whitelistedComponents != null) {
pw.print(", whitelisted="); pw.print(whitelistedComponents);
}
@@ -209,6 +235,7 @@ public final class ContentCaptureOptions implements Parcelable {
parcel.writeInt(idleFlushingFrequencyMs);
parcel.writeInt(textChangeFlushingFrequencyMs);
parcel.writeInt(logHistorySize);
+ parcel.writeBoolean(disableFlushForViewTreeAppearing);
parcel.writeArraySet(whitelistedComponents);
}
@@ -226,12 +253,13 @@ public final class ContentCaptureOptions implements Parcelable {
final int idleFlushingFrequencyMs = parcel.readInt();
final int textChangeFlushingFrequencyMs = parcel.readInt();
final int logHistorySize = parcel.readInt();
+ final boolean disableFlushForViewTreeAppearing = parcel.readBoolean();
@SuppressWarnings("unchecked")
final ArraySet whitelistedComponents =
(ArraySet) parcel.readArraySet(null);
return new ContentCaptureOptions(loggingLevel, maxBufferSize,
idleFlushingFrequencyMs, textChangeFlushingFrequencyMs, logHistorySize,
- whitelistedComponents);
+ disableFlushForViewTreeAppearing, whitelistedComponents);
}
@Override
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a320f1e9509c40fd7a575dd6660d8a3e48b5149a..809dc3c411887cdcf2bd4127c2a808af158292eb 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5737,18 +5737,17 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS =
- "android.intent.extra.EXTRA_CHOOSER_CUSTOM_ACTIONS";
+ "android.intent.extra.CHOOSER_CUSTOM_ACTIONS";
/**
* Optional argument to be used with {@link #ACTION_CHOOSER}.
- * A {@link android.app.PendingIntent} to be sent when the user wants to do payload reselection
- * in the sharesheet.
- * A reselection action allows the user to return to the source app to change the content being
- * shared.
+ * A {@link android.app.PendingIntent} to be sent when the user wants to modify the content that
+ * they're sharing. This can be used to allow the user to return to the source app to, for
+ * example, select different media.
* @hide
*/
- public static final String EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION =
- "android.intent.extra.EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION";
+ public static final String EXTRA_CHOOSER_MODIFY_SHARE_ACTION =
+ "android.intent.extra.CHOOSER_MODIFY_SHARE_ACTION";
/**
* An {@code ArrayList} of {@code String} annotations describing content for
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 9e5e8deda84b26a27419d4a73abb8e2246d643d9..319f1298a87fb674e3d71924b5f3c741a25092f2 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1034,6 +1034,20 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION =
254631730L; // buganizer id
+ /**
+ * This change id enables compat policy that ignores app requested orientation in
+ * response to an app calling {@link android.app.Activity#setRequestedOrientation} more
+ * than twice in one second if an activity is not letterboxed for fixed orientation.
+ * See com.android.server.wm.LetterboxUiController#shouldIgnoreRequestedOrientation
+ * for details.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED =
+ 273509367L; // buganizer id
+
/**
* This change id forces the packages it is applied to never have Display API sandboxing
* applied for a letterbox or SCM activity. The Display APIs will continue to provide
@@ -1057,6 +1071,80 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
@TestApi
public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id
+ /**
+ * This change id excludes the packages it is applied to from ignoreOrientationRequest behaviour
+ * that can be enabled by the device manufacturers for the com.android.server.wm.DisplayArea
+ * or for the whole display.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_RESPECT_REQUESTED_ORIENTATION = 236283604L; // buganizer id
+
+ /**
+ * This change id excludes the packages it is applied to from the camera compat force rotation
+ * treatment. See com.android.server.wm.DisplayRotationCompatPolicy for context.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION =
+ 263959004L; // buganizer id
+
+ /**
+ * This change id excludes the packages it is applied to from activity refresh after camera
+ * compat force rotation treatment. See com.android.server.wm.DisplayRotationCompatPolicy for
+ * context.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // buganizer id
+
+ /**
+ * This change id makes the packages it is applied to do activity refresh after camera compat
+ * force rotation treatment using "resumed -> paused -> resumed" cycle rather than "resumed ->
+ * ... -> stopped -> ... -> resumed" cycle. See
+ * com.android.server.wm.DisplayRotationCompatPolicy for context.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
+ 264301586L; // buganizer id
+
+ /**
+ * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
+ * an activity bounds for:
+ *
+ *
For {@link android.view.View#getWindowVisibleDisplayFrame} and
+ * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
+ * through
+ * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
+ * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+ *
+ *
Some applications assume that they occupy the whole screen and therefore use the display
+ * coordinates in their calculations as if an activity is positioned in the top-left corner of
+ * the screen, with left coordinate equal to 0. This may not be the case of applications in
+ * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
+ * case the activity is Letterboxed or is in multi-window mode.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
+
/**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
@@ -1152,6 +1240,91 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
@Overridable
public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L;
+ // Compat framework that per-app overrides rely on only supports booleans. That's why we have
+ // multiple OVERRIDE_*_ORIENTATION_* change ids below instead of just one override with
+ // the integer value.
+
+ /**
+ * Enables {@link #SCREEN_ORIENTATION_PORTRAIT}. Unless OVERRIDE_ANY_ORIENTATION
+ * is enabled, this override is used only when no other fixed orientation was specified by the
+ * activity.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L;
+
+ /**
+ * Enables {@link #SCREEN_ORIENTATION_NOSENSOR}. Unless OVERRIDE_ANY_ORIENTATION
+ * is enabled, this override is used only when no other fixed orientation was specified by the
+ * activity.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR = 265451093L;
+
+ /**
+ * Enables {@link #SCREEN_ORIENTATION_REVERSE_LANDSCAPE}. Unless OVERRIDE_ANY_ORIENTATION
+ * is enabled, this override is used only when activity specify landscape orientation.
+ * This can help apps that assume that landscape display orientation corresponds to {@link
+ * android.view.Surface#ROTATION_90}, while on some devices it can be {@link
+ * android.view.Surface#ROTATION_270}.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE = 266124927L;
+
+ /**
+ * When enabled, allows OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE,
+ * OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR and OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
+ * to override any orientation requested by the activity.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_ANY_ORIENTATION = 265464455L;
+
+ /**
+ * When enabled, activates OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE,
+ * OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR and OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
+ * only when an app is connected to the camera. See
+ * com.android.server.wm.DisplayRotationCompatPolicy for more context.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA = 265456536L;
+
+ /**
+ * This override fixes display orientation to landscape natural orientation when a task is
+ * fullscreen. While display rotation is fixed to landscape, the orientation requested by the
+ * activity will be still respected by bounds resolution logic. For instance, if an activity
+ * requests portrait orientation and this override is set, then activity will appear in the
+ * letterbox mode for fixed orientation with the display rotated to the lanscape natural
+ * orientation.
+ *
+ *
This override is applicable only when natural orientation of the device is
+ * landscape and display ignores orientation requestes.
+ *
+ *
Main use case for this override are camera-using activities that are portrait-only and
+ * assume alignment with natural device orientation. Such activities can automatically be
+ * rotated with com.android.server.wm.DisplayRotationCompatPolicy but not all of them can
+ * handle dynamic rotation and thus can benefit from this override.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L;
+
/**
* Compares activity window layout min width/height with require space for multi window to
* determine if it can be put into multi window mode.
@@ -1370,8 +1543,19 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
* @hide
*/
public boolean isFixedOrientation() {
- return isFixedOrientationLandscape() || isFixedOrientationPortrait()
- || screenOrientation == SCREEN_ORIENTATION_LOCKED;
+ return isFixedOrientation(screenOrientation);
+ }
+
+ /**
+ * Returns true if the passed activity's orientation is fixed.
+ * @hide
+ */
+ public static boolean isFixedOrientation(@ScreenOrientation int orientation) {
+ return orientation == SCREEN_ORIENTATION_LOCKED
+ // Orientation is fixed to natural display orientation
+ || orientation == SCREEN_ORIENTATION_NOSENSOR
+ || isFixedOrientationLandscape(orientation)
+ || isFixedOrientationPortrait(orientation);
}
/**
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index c8bbb0c1994d6cdf8c7fcf8d305029b693e4a4bb..6f09e79bdba9659668f7ebf68b8eb166faf45586 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1452,6 +1452,16 @@ public final class AssetManager implements AutoCloseable {
}
}
+ /**
+ * @hide
+ */
+ Configuration[] getSizeAndUiModeConfigurations() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetSizeAndUiModeConfigurations(mObject);
+ }
+ }
+
/**
* Change the configuration used when retrieving resources. Not for use by
* applications.
@@ -1603,6 +1613,7 @@ public final class AssetManager implements AutoCloseable {
private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid);
private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem);
private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr);
+ private static native @Nullable Configuration[] nativeGetSizeAndUiModeConfigurations(long ptr);
private static native void nativeSetResourceResolutionLoggingEnabled(long ptr, boolean enabled);
private static native @Nullable String nativeGetLastResourceResolution(long ptr);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index a03286d3ec6f67842108b1bd851495feb109d2c3..9b169499b41fc889a68eb6e1be246f504f74a111 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2207,6 +2207,11 @@ public class Resources {
return mResourcesImpl.getSizeConfigurations();
}
+ /** @hide */
+ public Configuration[] getSizeAndUiModeConfigurations() {
+ return mResourcesImpl.getSizeAndUiModeConfigurations();
+ }
+
/**
* Return the compatibility mode information for the application.
* The returned object should be treated as read-only.
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index ff072916292be1046b5c724946f443e4c327d04c..3bb237ac12287c63f825595014fee309d1e17ab1 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -203,6 +203,10 @@ public class ResourcesImpl {
return mAssets.getSizeConfigurations();
}
+ Configuration[] getSizeAndUiModeConfigurations() {
+ return mAssets.getSizeAndUiModeConfigurations();
+ }
+
CompatibilityInfo getCompatibilityInfo() {
return mDisplayAdjustments.getCompatibilityInfo();
}
diff --git a/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java b/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
index 41f2f9c28349690a735cb0a2c57d94793b5605d6..b86b97c76740dde20d31e0740b423359f96477c4 100644
--- a/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
+++ b/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
@@ -17,7 +17,7 @@
package android.database.sqlite;
/**
- * Thrown if the the bind or column parameter index is out of range
+ * Thrown if the bind or column parameter index is out of range.
*/
public class SQLiteBindOrColumnIndexOutOfRangeException extends SQLiteException {
public SQLiteBindOrColumnIndexOutOfRangeException() {}
diff --git a/core/java/android/database/sqlite/SQLiteDiskIOException.java b/core/java/android/database/sqlite/SQLiteDiskIOException.java
index 01b2069c23db37c7884f845765e24a650e389714..152d90a76ba6b4cea785574ace6133defbea69e1 100644
--- a/core/java/android/database/sqlite/SQLiteDiskIOException.java
+++ b/core/java/android/database/sqlite/SQLiteDiskIOException.java
@@ -17,7 +17,7 @@
package android.database.sqlite;
/**
- * An exception that indicates that an IO error occured while accessing the
+ * Indicates that an IO error occurred while accessing the
* SQLite database file.
*/
public class SQLiteDiskIOException extends SQLiteException {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 5291d2b7389184ef9f6e57ac1defefb55bdc4400..ccc39b6080d7b4198f60b8f6ad9e9be5725654f3 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -17,19 +17,13 @@
package android.hardware;
import static android.system.OsConstants.EACCES;
-import static android.system.OsConstants.EBUSY;
-import static android.system.OsConstants.EINVAL;
import static android.system.OsConstants.ENODEV;
-import static android.system.OsConstants.ENOSYS;
-import static android.system.OsConstants.EOPNOTSUPP;
-import static android.system.OsConstants.EUSERS;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.ActivityThread;
import android.app.AppOpsManager;
-import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.ImageFormat;
@@ -47,7 +41,6 @@ import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RSIllegalArgumentException;
@@ -58,6 +51,7 @@ import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
@@ -284,14 +278,6 @@ public class Camera {
*/
public native static int getNumberOfCameras();
- private static final boolean sLandscapeToPortrait =
- SystemProperties.getBoolean(CameraManager.LANDSCAPE_TO_PORTRAIT_PROP, false);
-
- private static boolean shouldOverrideToPortrait() {
- return CompatChanges.isChangeEnabled(CameraManager.OVERRIDE_FRONT_CAMERA_APP_COMPAT)
- && sLandscapeToPortrait;
- }
-
/**
* Returns the information about a particular camera.
* If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1.
@@ -301,7 +287,8 @@ public class Camera {
* low-level failure).
*/
public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(
+ ActivityThread.currentApplication().getApplicationContext());
_getCameraInfo(cameraId, overrideToPortrait, cameraInfo);
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
@@ -498,9 +485,24 @@ public class Camera {
mEventHandler = null;
}
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(
+ ActivityThread.currentApplication().getApplicationContext());
+ boolean forceSlowJpegMode = shouldForceSlowJpegMode();
return native_setup(new WeakReference(this), cameraId,
- ActivityThread.currentOpPackageName(), overrideToPortrait);
+ ActivityThread.currentOpPackageName(), overrideToPortrait, forceSlowJpegMode);
+ }
+
+ private boolean shouldForceSlowJpegMode() {
+ Context applicationContext = ActivityThread.currentApplication().getApplicationContext();
+ String[] slowJpegPackageNames = applicationContext.getResources().getStringArray(
+ R.array.config_forceSlowJpegModeList);
+ String callingPackageName = applicationContext.getPackageName();
+ for (String packageName : slowJpegPackageNames) {
+ if (TextUtils.equals(packageName, callingPackageName)) {
+ return true;
+ }
+ }
+ return false;
}
/** used by Camera#open, Camera#open(int) */
@@ -571,7 +573,7 @@ public class Camera {
@UnsupportedAppUsage
private native int native_setup(Object cameraThis, int cameraId, String packageName,
- boolean overrideToPortrait);
+ boolean overrideToPortrait, boolean forceSlowJpegMode);
private native final void native_release();
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 10a7538cf4880ec23232474ded4ddf4ba3394fb1..2f81e0c118d8b85c6be01b50aad49f18b2253de5 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -880,8 +880,8 @@ public abstract class CameraDevice implements AutoCloseable {
*
{@code PRIV}
{@code PREVIEW}
{@code PREVIEW}
{@code YUV / PRIV}
{@code s1440p}
{@code VIDEO_CALL}
Preview with video call
*
{@code YUV / PRIV}
{@code s1440p}
{@code PREVIEW_VIDEO_STILL}
{@code YUV / JPEG}
{@code MAXIMUM}
{@code STILL_CAPTURE}
Multi-purpose stream with JPEG or YUV still capture
*
{@code YUV}
{@code PREVIEW}
{@code STILL_CAPTURE}
{@code JPEG}
{@code MAXIMUM}
{@code STILL_CAPTURE}
YUV and JPEG concurrent still image capture (for testing)
- *
{@code PRIV}
{@code PREVIEW}
{@code PREVIEW}
{@code YUV / PRIV}
{@code RECORD}
{@code VIDEO_RECORD}
{@code YUV / JPEG}
{@code RECORD}
{@code STILL_CAPTURE}
Preview, video record and JPEG or YUV video snapshot
- *
{@code PRIV}
{@code PREVIEW}
{@code PREVIEW}
{@code YUV}
{@code PREVIEW}
{@code PREVIEW}
{@code YUV / JPEG}
{@code MAXIMUM}
{@code STILL_CAPTURE}
Preview, in-application image processing, and JPEG or YUV still image capture
+ *
{@code PRIV}
{@code PREVIEW}
{@code PREVIEW}
{@code YUV / PRIV}
{@code RECORD}
{@code VIDEO_RECORD}
{@code JPEG}
{@code RECORD}
{@code STILL_CAPTURE}
Preview, video record and JPEG video snapshot
+ *
{@code PRIV}
{@code PREVIEW}
{@code PREVIEW}
{@code YUV}
{@code PREVIEW}
{@code PREVIEW}
{@code JPEG}
{@code MAXIMUM}
{@code STILL_CAPTURE}
Preview, in-application image processing, and JPEG still image capture
*
*
*
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index ed1e9e5f62285e369e693bfa18cc72fee34a7bd5..3b2dd5ebdfc67e4885c48cccd2a806105672cab8 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -115,8 +115,14 @@ public final class CameraManager {
@ChangeId
@Overridable
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
- @TestApi
- public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L;
+ public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L;
+
+ /**
+ * Package-level opt in/out for the above.
+ * @hide
+ */
+ public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT =
+ "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT";
/**
* System property for allowing the above
@@ -392,6 +398,23 @@ public final class CameraManager {
* except that it uses {@link java.util.concurrent.Executor} as an argument
* instead of {@link android.os.Handler}.
*
+ *
Note: If the order between some availability callbacks matters, the implementation of the
+ * executor should handle those callbacks in the same thread to maintain the callbacks' order.
+ * Some examples are:
+ *
+ *
+ *
+ *
{@link AvailabilityCallback#onCameraAvailable} and
+ * {@link AvailabilityCallback#onCameraUnavailable} of the same camera ID.
+ *
+ *
{@link AvailabilityCallback#onCameraAvailable} or
+ * {@link AvailabilityCallback#onCameraUnavailable} of a logical multi-camera, and {@link
+ * AvailabilityCallback#onPhysicalCameraUnavailable} or
+ * {@link AvailabilityCallback#onPhysicalCameraAvailable} of its physical
+ * cameras.
+ *
+ *
+ *
* @param executor The executor which will be used to invoke the callback.
* @param callback the new callback to send camera availability notices to
*
@@ -608,7 +631,7 @@ public final class CameraManager {
try {
Size displaySize = getDisplaySize();
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait);
try {
@@ -728,7 +751,7 @@ public final class CameraManager {
"Camera service is currently unavailable");
}
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
cameraUser = cameraService.connectDevice(callbacks, cameraId,
mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
@@ -1160,9 +1183,26 @@ public final class CameraManager {
return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId);
}
- private static boolean shouldOverrideToPortrait() {
- return CompatChanges.isChangeEnabled(OVERRIDE_FRONT_CAMERA_APP_COMPAT)
- && CameraManagerGlobal.sLandscapeToPortrait;
+ /**
+ * @hide
+ */
+ public static boolean shouldOverrideToPortrait(@Nullable Context context) {
+ if (!CameraManagerGlobal.sLandscapeToPortrait) {
+ return false;
+ }
+
+ if (context != null) {
+ PackageManager packageManager = context.getPackageManager();
+
+ try {
+ return packageManager.getProperty(PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT,
+ context.getOpPackageName()).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ // No such property
+ }
+ }
+
+ return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT);
}
/**
@@ -2319,6 +2359,15 @@ public final class CameraManager {
final AvailabilityCallback callback = mCallbackMap.keyAt(i);
postSingleUpdate(callback, executor, id, null /*physicalId*/, status);
+
+ // Send the NOT_PRESENT state for unavailable physical cameras
+ if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(id)) {
+ ArrayList unavailableIds = mUnavailablePhysicalDevices.get(id);
+ for (String unavailableId : unavailableIds) {
+ postSingleUpdate(callback, executor, id, unavailableId,
+ ICameraServiceListener.STATUS_NOT_PRESENT);
+ }
+ }
}
} // onStatusChangedLocked
@@ -2338,9 +2387,8 @@ public final class CameraManager {
}
//TODO: Do we need to treat this as error?
- if (!mDeviceStatus.containsKey(id) || !isAvailable(mDeviceStatus.get(id))
- || !mUnavailablePhysicalDevices.containsKey(id)) {
- Log.e(TAG, String.format("Camera %s is not available. Ignore physical camera "
+ if (!mDeviceStatus.containsKey(id) || !mUnavailablePhysicalDevices.containsKey(id)) {
+ Log.e(TAG, String.format("Camera %s is not present. Ignore physical camera "
+ "status change", id));
return;
}
@@ -2365,6 +2413,12 @@ public final class CameraManager {
return;
}
+ if (!isAvailable(mDeviceStatus.get(id))) {
+ Log.i(TAG, String.format("Camera %s is not available. Ignore physical camera "
+ + "status change callback(s)", id));
+ return;
+ }
+
final int callbackCount = mCallbackMap.size();
for (int i = 0; i < callbackCount; i++) {
Executor executor = mCallbackMap.valueAt(i);
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
index 34d016adbc0654b050478370c13ae70b2cb2d475..7c54a9b01ddedea7bc0516bff0b60a3fe39eadad 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
@@ -36,4 +36,5 @@ parcelable CameraOutputConfig
int surfaceGroupId;
String physicalCameraId;
List sharedSurfaceConfigs;
+ boolean isMultiResolutionOutput;
}
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 41370e37f0924650e23b90362c5c22975605932b..02e7f6036b3431db24af67c60ab2c291f8918acd 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -228,6 +228,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
OutputConfiguration cameraOutput = new OutputConfiguration(output.surfaceGroupId,
outputSurface);
+ if (output.isMultiResolutionOutput) {
+ cameraOutput.setMultiResolutionOutput();
+ }
if ((output.sharedSurfaceConfigs != null) && !output.sharedSurfaceConfigs.isEmpty()) {
cameraOutput.enableSurfaceSharing();
for (CameraOutputConfig sharedOutputConfig : output.sharedSurfaceConfigs) {
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 9179f5de1a4f37e89e3ae09a6e74f73ec38f69d7..96472f4bad17464f13b95fa7c2b32327ac8ed102 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -87,6 +87,7 @@ public class CameraDeviceImpl extends CameraDevice
// TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
private ICameraDeviceUserWrapper mRemoteDevice;
+ private boolean mRemoteDeviceInit = false;
// Lock to synchronize cross-thread access to device public interface
final Object mInterfaceLock = new Object(); // access from this class and Session only!
@@ -338,6 +339,8 @@ public class CameraDeviceImpl extends CameraDevice
mDeviceExecutor.execute(mCallOnOpened);
mDeviceExecutor.execute(mCallOnUnconfigured);
+
+ mRemoteDeviceInit = true;
}
}
@@ -1754,8 +1757,8 @@ public class CameraDeviceImpl extends CameraDevice
}
synchronized(mInterfaceLock) {
- if (mRemoteDevice == null) {
- return; // Camera already closed
+ if (mRemoteDevice == null && mRemoteDeviceInit) {
+ return; // Camera already closed, user is not interested in errors anymore.
}
// Redirect device callback to the offline session in case we are in the middle
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 956a474401bae346b5d49f6ded0a12d14ce932cc..d64009c734017ed6644ef40800152d8807f4d1cf 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1220,14 +1220,6 @@ public final class MandatoryStreamCombination {
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD,
STREAM_USE_CASE_STILL_CAPTURE)},
"Preview, video record and JPEG video snapshot"),
- new StreamCombinationTemplate(new StreamTemplate [] {
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
- STREAM_USE_CASE_PREVIEW),
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD,
- STREAM_USE_CASE_RECORD),
- new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
- STREAM_USE_CASE_STILL_CAPTURE)},
- "Preview, video record and YUV video snapshot"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
STREAM_USE_CASE_PREVIEW),
@@ -1236,14 +1228,6 @@ public final class MandatoryStreamCombination {
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD,
STREAM_USE_CASE_STILL_CAPTURE)},
"Preview, in-application video processing and JPEG video snapshot"),
- new StreamCombinationTemplate(new StreamTemplate [] {
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
- STREAM_USE_CASE_PREVIEW),
- new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
- STREAM_USE_CASE_RECORD),
- new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
- STREAM_USE_CASE_STILL_CAPTURE)},
- "Preview, in-application video processing and YUV video snapshot"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
STREAM_USE_CASE_PREVIEW),
@@ -1252,14 +1236,6 @@ public final class MandatoryStreamCombination {
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM,
STREAM_USE_CASE_STILL_CAPTURE)},
"Preview, in-application image processing, and JPEG still image capture"),
- new StreamCombinationTemplate(new StreamTemplate [] {
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
- STREAM_USE_CASE_PREVIEW),
- new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
- STREAM_USE_CASE_PREVIEW),
- new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM,
- STREAM_USE_CASE_STILL_CAPTURE)},
- "Preview, in-application image processing, and YUV still image capture"),
};
private static StreamCombinationTemplate sPreviewStabilizedStreamCombinations[] = {
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 558bb6335dd3d61d4239577fcefe355f8c3b7938..e3d650248ff8a6d46a569b6c2b68e37c54305a3a 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -421,7 +421,7 @@ public final class OutputConfiguration implements Parcelable {
* call, or no non-negative group ID has been set.
* @hide
*/
- void setMultiResolutionOutput() {
+ public void setMultiResolutionOutput() {
if (mIsShared) {
throw new IllegalStateException("Multi-resolution output flag must not be set for " +
"configuration with surface sharing");
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index dba1a5e8dfc616135e2b85aab27c349a802c53fb..6a667fe39974d16e0b1322be865a2fbde2b382b1 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -251,6 +251,10 @@ public final class DeviceStateManager {
@Nullable
private Boolean lastResult;
+ public FoldStateListener(Context context) {
+ this(context, folded -> {});
+ }
+
public FoldStateListener(Context context, Consumer listener) {
mFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
@@ -266,5 +270,10 @@ public final class DeviceStateManager {
mDelegate.accept(folded);
}
}
+
+ @Nullable
+ public Boolean getFolded() {
+ return lastResult;
+ }
}
}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 63dc7c7ed661516eccb659982aca3aee94483b30..aef2ae26747d2420a71ccbc5bc9d8803e14ac69c 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -40,6 +40,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Trace;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -1006,7 +1007,8 @@ public final class DisplayManagerGlobal {
@Override
public void onDisplayEvent(int displayId, @DisplayEvent int event) {
if (DEBUG) {
- Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event);
+ Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + eventToString(
+ event));
}
handleDisplayEvent(displayId, event);
}
@@ -1040,6 +1042,12 @@ public final class DisplayManagerGlobal {
@Override
public void handleMessage(Message msg) {
+ if (DEBUG) {
+ Trace.beginSection(
+ "DisplayListenerDelegate(" + eventToString(msg.what)
+ + ", display=" + msg.arg1
+ + ", listener=" + mListener.getClass() + ")");
+ }
switch (msg.what) {
case EVENT_DISPLAY_ADDED:
if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) {
@@ -1066,6 +1074,9 @@ public final class DisplayManagerGlobal {
}
break;
}
+ if (DEBUG) {
+ Trace.endSection();
+ }
}
}
@@ -1172,4 +1183,18 @@ public final class DisplayManagerGlobal {
updateCallbackIfNeededLocked();
}
}
+
+ private static String eventToString(@DisplayEvent int event) {
+ switch (event) {
+ case EVENT_DISPLAY_ADDED:
+ return "ADDED";
+ case EVENT_DISPLAY_CHANGED:
+ return "CHANGED";
+ case EVENT_DISPLAY_REMOVED:
+ return "REMOVED";
+ case EVENT_DISPLAY_BRIGHTNESS_CHANGED:
+ return "BRIGHTNESS_CHANGED";
+ }
+ return "UNKNOWN";
+ }
}
diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
index 9c2aa66993347f591c2de0f9eaf805c3b3a69fda..a36ccf6150bae83ee6f6f9c7dac11814f4c0b59e 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
@@ -39,5 +39,15 @@ oneway interface IUdfpsHbmListener {
* {@link android.view.Display#getDisplayId()}.
*/
void onHbmDisabled(int displayId);
+
+ /**
+ * To avoid delay in switching refresh rate when activating LHBM, allow screens to request
+ * higher refersh rate if auth is possible on particular screen
+ *
+ * @param displayId The displayId for which the refresh rate should be unset. See
+ * {@link android.view.Display#getDisplayId()}.
+ * @param isPossible If authentication is possible on particualr screen
+ */
+ void onAuthenticationPossible(int displayId, boolean isPossible);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index baf8836c5af584ec241ec998d5ecb9f02ab947c4..b6cd06ea8b5cf423ecfc3b1054aef8217a13ef4f 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -168,7 +168,7 @@ public final class InputManager {
* The android:label attribute specifies a human-readable descriptive
* label to describe the keyboard layout in the user interface, such as "English (US)".
* The android:keyboardLayout attribute refers to a
- *
+ *
* key character map resource that defines the keyboard layout.
*
*/
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 3d5c34c384314327793894d66bc4bf2340c60490..4f49f12691d0b1508a7361e9bd97ef6f1f2c7320 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -649,8 +649,11 @@ public abstract class BatteryStats implements Parcelable {
return Uid.PROCESS_STATE_NONEXISTENT;
} else if (procState == ActivityManager.PROCESS_STATE_TOP) {
return Uid.PROCESS_STATE_TOP;
- } else if (ActivityManager.isForegroundService(procState)) {
- // State when app has put itself in the foreground.
+ } else if (procState == ActivityManager.PROCESS_STATE_BOUND_TOP) {
+ return Uid.PROCESS_STATE_BACKGROUND;
+ } else if (procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+ } else if (procState == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
} else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
// Persistent and other foreground states go here.
@@ -2804,6 +2807,15 @@ public abstract class BatteryStats implements Parcelable {
*/
public abstract long getMobileRadioMeasuredBatteryConsumptionUC();
+ /**
+ * Returns the battery consumption (in microcoulombs) of the phone calls, derived from on device
+ * power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getPhoneEnergyConsumptionUC();
+
/**
* Returns the battery consumption (in microcoulombs) of the screen while on, derived from on
* device power measurement data.
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index e1d15defad3812c378f568c123aa71900af486f6..125bdaf07b9083957684d7576e1952884f46ab10 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -74,6 +74,7 @@ interface IUserManager {
String getUserAccount(int userId);
void setUserAccount(int userId, String accountName);
long getUserCreationTime(int userId);
+ int getUserSwitchability(int userId);
boolean isUserSwitcherEnabled(int mUserId);
boolean isRestricted(int userId);
boolean canHaveRestrictedProfile(int userId);
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 320b02c2a0b415415df8920643e5250b37f21c8b..bc65061ba244f4baed5bd5f7b557af97f3c5ac23 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -110,7 +110,8 @@ public final class Trace {
public static final long TRACE_TAG_THERMAL = 1L << 27;
private static final long TRACE_TAG_NOT_READY = 1L << 63;
- private static final int MAX_SECTION_NAME_LEN = 127;
+ /** @hide **/
+ public static final int MAX_SECTION_NAME_LEN = 127;
// Must be volatile to avoid word tearing.
// This is only kept in case any apps get this by reflection but do not
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 91d231eb1c398c8808a45c970b063f8c07d507bc..787b609ab2c14bbaf9710a1916523476784a57d1 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -51,8 +51,7 @@ public final class UidBatteryConsumer extends BatteryConsumer {
}
/**
- * The state of an application when it is either running a foreground (top) activity
- * or a foreground service.
+ * The state of an application when it is either running a foreground (top) activity.
*/
public static final int STATE_FOREGROUND = 0;
@@ -64,7 +63,8 @@ public final class UidBatteryConsumer extends BatteryConsumer {
* {@link android.app.ActivityManager#PROCESS_STATE_TRANSIENT_BACKGROUND},
* {@link android.app.ActivityManager#PROCESS_STATE_BACKUP},
* {@link android.app.ActivityManager#PROCESS_STATE_SERVICE},
- * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER}.
+ * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER},
+ * {@link android.app.ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE}.
*/
public static final int STATE_BACKGROUND = 1;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index e7641d8084ccca466d0aee20ae243b3faf233d20..83a4e9a8e6a89fe6340ed0703eeb683acdc812aa 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -58,7 +58,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.provider.Settings;
-import android.telephony.TelephonyManager;
import android.util.AndroidException;
import android.util.ArraySet;
import android.util.Log;
@@ -595,8 +594,11 @@ public class UserManager {
/**
* Specifies if a user is disallowed from transferring files over USB.
*
- *
This restriction can only be set by a device owner, a profile owner on the primary
- * user or a profile owner of an organization-owned managed profile on the parent profile.
+ *
This restriction can only be set by a
+ * device owner or a
+ * profile owner on the primary user's profile or a profile owner of an organization-owned
+ *
+ * managed profile on the parent profile.
* When it is set by a device owner, it applies globally. When it is set by a profile owner
* on the primary user or by a profile owner of an organization-owned managed profile on
* the parent profile, it disables the primary user from transferring files over USB. No other
@@ -1712,7 +1714,7 @@ public class UserManager {
public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 1 << 2;
/**
- * Result returned in {@link #getUserSwitchability()} indicating user swichability.
+ * Result returned in {@link #getUserSwitchability()} indicating user switchability.
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@@ -2079,25 +2081,16 @@ public class UserManager {
* @hide
*/
@Deprecated
- @RequiresPermission(allOf = {
- Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.MANAGE_USERS}, // Can be INTERACT_ACROSS_USERS instead.
- conditional = true)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@UserHandleAware
public boolean canSwitchUsers() {
- boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
- boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
- boolean inCall = false;
- TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
- if (telephonyManager != null) {
- inCall = telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
+ try {
+ return mService.getUserSwitchability(mUserId) == SWITCHABILITY_STATUS_OK;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
- boolean isUserSwitchDisallowed = hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId);
- return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall
- && !isUserSwitchDisallowed;
}
/**
@@ -2131,34 +2124,14 @@ public class UserManager {
* @return A {@link UserSwitchabilityResult} flag indicating if the user is switchable.
* @hide
*/
- @RequiresPermission(allOf = {Manifest.permission.READ_PHONE_STATE,
- android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
public @UserSwitchabilityResult int getUserSwitchability(UserHandle userHandle) {
- final TelephonyManager tm =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-
- int flags = SWITCHABILITY_STATUS_OK;
- if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
- flags |= SWITCHABILITY_STATUS_USER_IN_CALL;
- }
- if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, userHandle)) {
- flags |= SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
- }
-
- // System User is always unlocked in Headless System User Mode, so ignore this flag
- if (!isHeadlessSystemUserMode()) {
- final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
- final boolean systemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
-
- if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
- flags |= SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
- }
+ try {
+ return mService.getUserSwitchability(userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
-
- return flags;
}
/**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 08de87ebe2e61c4cd3407f13d3e33da224813dee..0d9f500cb1028ac6d20f9ca1ac8d0fde11d1ad40 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2552,6 +2552,8 @@ public class StorageManager {
* This API doesn't require any special permissions, though typical implementations
* will require being called from an SELinux domain that allows setting file attributes
* related to quota (eg the GID or project ID).
+ * If the calling user has MANAGE_EXTERNAL_STORAGE permissions, quota for shared profile's
+ * volumes is also updated.
*
* The default platform user of this API is the MediaProvider process, which is
* responsible for managing all of external storage.
@@ -2572,7 +2574,14 @@ public class StorageManager {
@QuotaType int quotaType) throws IOException {
long projectId;
final String filePath = path.getCanonicalPath();
- final StorageVolume volume = getStorageVolume(path);
+ int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE;
+ // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are also
+ // returned by enabling FLAG_INCLUDE_SHARED_PROFILE.
+ if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
+ volFlags |= FLAG_INCLUDE_SHARED_PROFILE;
+ }
+ final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags);
+ final StorageVolume volume = getStorageVolume(availableVolumes, path);
if (volume == null) {
Log.w(TAG, "Failed to update quota type for " + filePath);
return;
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 3bf9ca04414179efeb46980c40885ae92a18af7c..6f2a915cee46df8829c14bcb7dc2f138560d23f2 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -16,7 +16,9 @@
package android.preference;
+import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.app.NotificationManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
@@ -35,6 +37,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.preference.VolumePreference.VolumeStore;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.System;
@@ -44,6 +47,7 @@ import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.os.SomeArgs;
import java.util.concurrent.TimeUnit;
@@ -115,7 +119,6 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
private final int mMaxStreamVolume;
private boolean mAffectedByRingerMode;
private boolean mNotificationOrRing;
- private final boolean mNotifAliasRing;
private final Receiver mReceiver = new Receiver();
private Handler mHandler;
@@ -138,12 +141,15 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
private int mRingerMode;
private int mZenMode;
private boolean mPlaySample;
+ private final boolean mDeviceHasProductStrategies;
private static final int MSG_SET_STREAM_VOLUME = 0;
private static final int MSG_START_SAMPLE = 1;
private static final int MSG_STOP_SAMPLE = 2;
private static final int MSG_INIT_SAMPLE = 3;
+ private static final int MSG_UPDATE_SLIDER_MAYBE_LATER = 4;
private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
+ private static final int CHECK_UPDATE_SLIDER_LATER_MS = 500;
private static final long SET_STREAM_VOLUME_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
private static final long START_SAMPLE_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
private static final long DURATION_TO_START_DELAYING = TimeUnit.MILLISECONDS.toMillis(2000);
@@ -158,6 +164,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
this(context, streamType, defaultUri, callback, true /* playSample */);
}
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public SeekBarVolumizer(
Context context,
int streamType,
@@ -166,6 +173,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
boolean playSample) {
mContext = context;
mAudioManager = context.getSystemService(AudioManager.class);
+ mDeviceHasProductStrategies = hasAudioProductStrategies();
mNotificationManager = context.getSystemService(NotificationManager.class);
mNotificationPolicy = mNotificationManager.getConsolidatedNotificationPolicy();
mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy
@@ -180,11 +188,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
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()) {
+ if (mDeviceHasProductStrategies) {
mVolumeGroupId = getVolumeGroupIdForLegacyStreamType(mStreamType);
mAttributes = getAudioAttributesForLegacyStreamType(
mStreamType);
@@ -211,6 +217,12 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
mDefaultUri = defaultUri;
}
+ /**
+ * DO NOT CALL every time this is needed, use once in constructor,
+ * read mDeviceHasProductStrategies instead
+ * @return true if stream types are used for volume management, false if volume groups are
+ * used for volume management
+ */
private boolean hasAudioProductStrategies() {
return AudioManager.getAudioProductStrategies().size() > 0;
}
@@ -288,7 +300,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
* 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
+ if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+ || mStreamType == AudioManager.STREAM_RING
|| (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
mSeekBar.setProgress(0, true);
}
@@ -326,6 +340,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
onInitSample();
}
break;
+ case MSG_UPDATE_SLIDER_MAYBE_LATER:
+ onUpdateSliderMaybeLater();
+ break;
default:
Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
}
@@ -349,6 +366,21 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
: isDelay() ? START_SAMPLE_DELAY_MS : 0);
}
+ private void onUpdateSliderMaybeLater() {
+ if (isDelay()) {
+ postUpdateSliderMaybeLater();
+ return;
+ }
+ updateSlider();
+ }
+
+ private void postUpdateSliderMaybeLater() {
+ if (mHandler == null) return;
+ mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SLIDER_MAYBE_LATER),
+ CHECK_UPDATE_SLIDER_LATER_MS);
+ }
+
// After stop volume it needs to add a small delay when playing volume or set stream.
// It is because the call volume is from the earpiece and the alarm/ring/media
// is from the speaker. If play the alarm volume or set alarm stream right after stop
@@ -365,7 +397,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
// set the time of stop volume
if ((mStreamType == AudioManager.STREAM_VOICE_CALL
|| mStreamType == AudioManager.STREAM_RING
- || (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION)
+ || (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+ && mStreamType == AudioManager.STREAM_NOTIFICATION)
|| mStreamType == AudioManager.STREAM_ALARM)) {
sStopVolumeTime = java.lang.System.currentTimeMillis();
}
@@ -416,7 +450,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
postStopSample();
mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
mReceiver.setListening(false);
- if (hasAudioProductStrategies()) {
+ if (mDeviceHasProductStrategies) {
unregisterVolumeGroupCb();
}
mSeekBar.setOnSeekBarChangeListener(null);
@@ -436,7 +470,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
System.getUriFor(System.VOLUME_SETTINGS_INT[mStreamType]),
false, mVolumeObserver);
mReceiver.setListening(true);
- if (hasAudioProductStrategies()) {
+ if (mDeviceHasProductStrategies) {
registerVolumeGroupCb();
}
}
@@ -460,6 +494,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
mLastProgress = progress;
mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
mHandler.removeMessages(MSG_START_SAMPLE);
+ mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME),
isDelay() ? SET_STREAM_VOLUME_DELAY_MS : 0);
}
@@ -603,7 +638,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
- if (hasAudioProductStrategies() && !isDelay()) {
+ if (mDeviceHasProductStrategies && !isDelay()) {
updateVolumeSlider(streamType, streamValue);
}
} else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
@@ -615,9 +650,16 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
} else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
- if (hasAudioProductStrategies() && !isDelay()) {
- int streamVolume = mAudioManager.getStreamVolume(streamType);
- updateVolumeSlider(streamType, streamVolume);
+
+ if (mDeviceHasProductStrategies) {
+ if (isDelay()) {
+ // not the right time to update the sliders, try again later
+ postUpdateSliderMaybeLater();
+ } else {
+ int streamVolume = mAudioManager.getStreamVolume(streamType);
+ updateVolumeSlider(streamType, streamVolume);
+ }
+
} else {
int volumeGroup = getVolumeGroupIdForLegacyStreamType(streamType);
if (volumeGroup != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
@@ -644,8 +686,10 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
private void updateVolumeSlider(int streamType, int streamValue) {
- final boolean streamMatch = mNotifAliasRing && mNotificationOrRing
- ? isNotificationOrRing(streamType) : streamType == mStreamType;
+ final boolean streamMatch = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+ && 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/provider/Settings.java b/core/java/android/provider/Settings.java
index 038beabcf22fc0a1e708a36102863d75807c1d0f..7a757690b8687bd89a4cf0df8bd74a4c14e2608f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7999,6 +7999,15 @@ public final class Settings {
public static final String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED =
"accessibility_display_inversion_enabled";
+ /**
+ * Flag that specifies whether font size has been changed. The flag will
+ * be set when users change the scaled value of font size for the first time.
+ * @hide
+ */
+ @Readable
+ public static final String ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED =
+ "accessibility_font_scaling_has_been_changed";
+
/**
* Setting that specifies whether display color space adjustment is
* enabled.
@@ -9199,6 +9208,14 @@ public final class Settings {
public static final String SCREENSAVER_COMPLICATIONS_ENABLED =
"screensaver_complications_enabled";
+ /**
+ * Whether home controls are enabled to be shown over the screensaver by the user.
+ *
+ * @hide
+ */
+ public static final String SCREENSAVER_HOME_CONTROLS_ENABLED =
+ "screensaver_home_controls_enabled";
+
/**
* Default, indicates that the user has not yet started the dock setup flow.
@@ -9231,6 +9248,14 @@ public final class Settings {
*/
public static final int DOCK_SETUP_PROMPTED = 3;
+ /**
+ * Indicates that the user has started dock setup but never finished it.
+ * One of the possible states for {@link #DOCK_SETUP_STATE}.
+ *
+ * @hide
+ */
+ public static final int DOCK_SETUP_INCOMPLETE = 4;
+
/**
* Indicates that the user has completed dock setup.
* One of the possible states for {@link #DOCK_SETUP_STATE}.
@@ -9239,6 +9264,14 @@ public final class Settings {
*/
public static final int DOCK_SETUP_COMPLETED = 10;
+ /**
+ * Indicates that dock setup timed out before the user could complete it.
+ * One of the possible states for {@link #DOCK_SETUP_STATE}.
+ *
+ * @hide
+ */
+ public static final int DOCK_SETUP_TIMED_OUT = 11;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -9246,7 +9279,9 @@ public final class Settings {
DOCK_SETUP_STARTED,
DOCK_SETUP_PAUSED,
DOCK_SETUP_PROMPTED,
- DOCK_SETUP_COMPLETED
+ DOCK_SETUP_INCOMPLETE,
+ DOCK_SETUP_COMPLETED,
+ DOCK_SETUP_TIMED_OUT
})
public @interface DockSetupState {
}
@@ -9515,7 +9550,7 @@ public final class Settings {
/**
* Indicates whether "seen" notifications should be suppressed from the lockscreen.
*
- * Type: int (0 for false, 1 for true)
+ * Type: int (0 for unset, 1 for true, 2 for false)
*
* @hide
*/
@@ -9818,11 +9853,12 @@ public final class Settings {
"fingerprint_side_fps_auth_downtime";
/**
- * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+ * Whether or not a SFPS device is enabling the performant auth setting.
+ * The "_V2" suffix was added to re-introduce the default behavior for
+ * users. See b/265264294 fore more details.
* @hide
*/
- public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
- "sfps_require_screen_on_to_auth_enabled";
+ public static final String SFPS_PERFORMANT_AUTH_ENABLED = "sfps_performant_auth_enabled_v2";
/**
* Whether or not debugging is enabled.
@@ -9900,6 +9936,28 @@ public final class Settings {
public static final String ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED =
"active_unlock_on_unlock_intent_when_biometric_enrolled";
+ /**
+ * If active unlock triggers on unlock intents, then also request active unlock on
+ * these wake-up reasons. See {@link PowerManager.WakeReason} for value mappings.
+ * WakeReasons should be separated by a pipe. For example: "0|3" or "0". If this
+ * setting should be disabled, then this should be set to an empty string. A null value
+ * will use the system default value (WAKE_REASON_UNFOLD_DEVICE).
+ * @hide
+ */
+ public static final String ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS =
+ "active_unlock_wakeups_considered_unlock_intents";
+
+ /**
+ * If active unlock triggers and succeeds on these wakeups, force dismiss keyguard on
+ * these wake reasons. See {@link PowerManager#WakeReason} for value mappings.
+ * WakeReasons should be separated by a pipe. For example: "0|3" or "0". If this
+ * setting should be disabled, then this should be set to an empty string. A null value
+ * will use the system default value (WAKE_REASON_UNFOLD_DEVICE).
+ * @hide
+ */
+ public static final String ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD =
+ "active_unlock_wakeups_to_force_dismiss_keyguard";
+
/**
* Whether the assist gesture should be enabled.
*
@@ -10938,21 +10996,46 @@ public final class Settings {
public @interface DeviceStateRotationLockSetting {
}
+ /** @hide */
+ public static final int DEVICE_STATE_ROTATION_KEY_UNKNOWN = -1;
+ /** @hide */
+ public static final int DEVICE_STATE_ROTATION_KEY_FOLDED = 0;
+ /** @hide */
+ public static final int DEVICE_STATE_ROTATION_KEY_HALF_FOLDED = 1;
+ /** @hide */
+ public static final int DEVICE_STATE_ROTATION_KEY_UNFOLDED = 2;
+
+ /**
+ * The different postures that can be used as keys with
+ * {@link #DEVICE_STATE_ROTATION_LOCK}.
+ * @hide
+ */
+ @IntDef(prefix = {"DEVICE_STATE_ROTATION_KEY_"}, value = {
+ DEVICE_STATE_ROTATION_KEY_UNKNOWN,
+ DEVICE_STATE_ROTATION_KEY_FOLDED,
+ DEVICE_STATE_ROTATION_KEY_HALF_FOLDED,
+ DEVICE_STATE_ROTATION_KEY_UNFOLDED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeviceStateRotationLockKey {
+ }
+
/**
* Rotation lock setting keyed on device state.
*
- * This holds a serialized map using int keys that represent Device States and value of
+ * This holds a serialized map using int keys that represent postures in
+ * {@link DeviceStateRotationLockKey} and value of
* {@link DeviceStateRotationLockSetting} representing the rotation lock setting for that
- * device state.
+ * posture.
*
* Serialized as key0:value0:key1:value1:...:keyN:valueN.
*
* Example: "0:1:1:2:2:1"
* This example represents a map of:
*
*
* @hide
@@ -11015,6 +11098,13 @@ public final class Settings {
public static final String EXTRA_AUTOMATIC_POWER_SAVE_MODE =
"extra_automatic_power_save_mode";
+ /**
+ * Whether lockscreen weather is enabled.
+ *
+ * @hide
+ */
+ public static final String LOCK_SCREEN_WEATHER_ENABLED = "lockscreen_weather_enabled";
+
/**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
diff --git a/core/java/android/service/appprediction/AppPredictionService.java b/core/java/android/service/appprediction/AppPredictionService.java
index 4f37cd91b11fd015c46586a2960262c72e30e313..a2ffa5d34219f7a95553ffe69f726a7dc8b628b7 100644
--- a/core/java/android/service/appprediction/AppPredictionService.java
+++ b/core/java/android/service/appprediction/AppPredictionService.java
@@ -328,7 +328,7 @@ public abstract class AppPredictionService extends Service {
Slog.e(TAG, "Callback is null, likely the binder has died.");
return false;
}
- return mCallback.equals(callback);
+ return mCallback.asBinder().equals(callback.asBinder());
}
public void destroy() {
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 0fb9f57f5f574380e3b180167a435317328ab735..b0e847cd53f94b9c7a81043420d059364c7000ae 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -166,7 +166,7 @@ public final class FillEventHistory implements Parcelable {
}
/**
- * Description of an event that occured after the latest call to
+ * Description of an event that occurred after the latest call to
* {@link FillCallback#onSuccess(FillResponse)}.
*/
public static final class Event {
diff --git a/core/java/android/service/chooser/ChooserAction.java b/core/java/android/service/chooser/ChooserAction.java
index 3010049633d41f37edf9310f51bd8f22d2cdee8b..a61b781b6bca20b8f67319e123d80d523256024d 100644
--- a/core/java/android/service/chooser/ChooserAction.java
+++ b/core/java/android/service/chooser/ChooserAction.java
@@ -27,10 +27,9 @@ import java.util.Objects;
/**
* A ChooserAction is an app-defined action that can be provided to the Android Sharesheet to
- * be shown to the user when {@link android.content.Intent.ACTION_CHOOSER} is invoked.
+ * be shown to the user when {@link android.content.Intent#ACTION_CHOOSER} is invoked.
*
- * @see android.content.Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS
- * @see android.content.Intent.EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION
+ * @see android.content.Intent#EXTRA_CHOOSER_CUSTOM_ACTIONS
* @hide
*/
public final class ChooserAction implements Parcelable {
@@ -88,6 +87,7 @@ public final class ChooserAction implements Parcelable {
return "ChooserAction {" + "label=" + mLabel + ", intent=" + mAction + "}";
}
+ @NonNull
public static final Parcelable.Creator CREATOR =
new Creator() {
@Override
@@ -137,6 +137,7 @@ public final class ChooserAction implements Parcelable {
* object.
* @return the built action
*/
+ @NonNull
public ChooserAction build() {
return new ChooserAction(mIcon, mLabel, mAction);
}
diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index a2fa1392b0796dddc82a1a758b6bf24402134a38..a3892238f1e6a789ebdc0397b5d9f94709a39198 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -58,11 +58,13 @@ public class DreamActivity extends Activity {
setTitle(title);
}
- final Bundle extras = getIntent().getExtras();
- mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK);
-
- if (mCallback != null) {
+ final Object callback = getIntent().getExtras().getBinder(EXTRA_CALLBACK);
+ if (callback instanceof DreamService.DreamActivityCallbacks) {
+ mCallback = (DreamService.DreamActivityCallbacks) callback;
mCallback.onActivityCreated(this);
+ } else {
+ mCallback = null;
+ finishAndRemoveTask();
}
}
diff --git a/core/java/android/service/dreams/DreamOverlayConnectionHandler.java b/core/java/android/service/dreams/DreamOverlayConnectionHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..cafe02ad86580962feab13ef1e85d5aee2ab3fbb
--- /dev/null
+++ b/core/java/android/service/dreams/DreamOverlayConnectionHandler.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ObservableServiceConnection;
+import com.android.internal.util.PersistentServiceConnection;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Handles the service connection to {@link IDreamOverlay}
+ *
+ * @hide
+ */
+@VisibleForTesting
+public final class DreamOverlayConnectionHandler {
+ private static final String TAG = "DreamOverlayConnection";
+
+ private static final int MSG_ADD_CONSUMER = 1;
+ private static final int MSG_REMOVE_CONSUMER = 2;
+ private static final int MSG_OVERLAY_CLIENT_READY = 3;
+
+ private final Handler mHandler;
+ private final PersistentServiceConnection mConnection;
+ // Retrieved Client
+ private IDreamOverlayClient mClient;
+ // A list of pending requests to execute on the overlay.
+ private final List> mConsumers = new ArrayList<>();
+ private final OverlayConnectionCallback mCallback;
+
+ DreamOverlayConnectionHandler(
+ Context context,
+ Looper looper,
+ Intent serviceIntent,
+ int minConnectionDurationMs,
+ int maxReconnectAttempts,
+ int baseReconnectDelayMs) {
+ this(context, looper, serviceIntent, minConnectionDurationMs, maxReconnectAttempts,
+ baseReconnectDelayMs, new Injector());
+ }
+
+ @VisibleForTesting
+ public DreamOverlayConnectionHandler(
+ Context context,
+ Looper looper,
+ Intent serviceIntent,
+ int minConnectionDurationMs,
+ int maxReconnectAttempts,
+ int baseReconnectDelayMs,
+ Injector injector) {
+ mCallback = new OverlayConnectionCallback();
+ mHandler = new Handler(looper, new OverlayHandlerCallback());
+ mConnection = injector.buildConnection(
+ context,
+ mHandler,
+ serviceIntent,
+ minConnectionDurationMs,
+ maxReconnectAttempts,
+ baseReconnectDelayMs
+ );
+ }
+
+ /**
+ * Bind to the overlay service. If binding fails, we automatically call unbind to clean
+ * up resources.
+ *
+ * @return true if binding was successful, false otherwise.
+ */
+ public boolean bind() {
+ mConnection.addCallback(mCallback);
+ final boolean success = mConnection.bind();
+ if (!success) {
+ unbind();
+ }
+ return success;
+ }
+
+ /**
+ * Unbind from the overlay service, clearing any pending callbacks.
+ */
+ public void unbind() {
+ mConnection.removeCallback(mCallback);
+ // Remove any pending messages.
+ mHandler.removeCallbacksAndMessages(null);
+ mClient = null;
+ mConsumers.clear();
+ mConnection.unbind();
+ }
+
+ /**
+ * Adds a consumer to run once the overlay service has connected. If the overlay service
+ * disconnects (eg binding dies) and then reconnects, this consumer will be re-run unless
+ * removed.
+ *
+ * @param consumer The consumer to run. This consumer is always executed asynchronously.
+ */
+ public void addConsumer(Consumer consumer) {
+ final Message msg = mHandler.obtainMessage(MSG_ADD_CONSUMER, consumer);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Removes the consumer, preventing this consumer from being called again.
+ *
+ * @param consumer The consumer to remove.
+ */
+ public void removeConsumer(Consumer consumer) {
+ final Message msg = mHandler.obtainMessage(MSG_REMOVE_CONSUMER, consumer);
+ mHandler.sendMessage(msg);
+ // Clear any pending messages to add this consumer
+ mHandler.removeMessages(MSG_ADD_CONSUMER, consumer);
+ }
+
+ private final class OverlayHandlerCallback implements Handler.Callback {
+ @Override
+ public boolean handleMessage(@NonNull Message msg) {
+ switch (msg.what) {
+ case MSG_OVERLAY_CLIENT_READY:
+ onOverlayClientReady((IDreamOverlayClient) msg.obj);
+ break;
+ case MSG_ADD_CONSUMER:
+ onAddConsumer((Consumer) msg.obj);
+ break;
+ case MSG_REMOVE_CONSUMER:
+ onRemoveConsumer((Consumer) msg.obj);
+ break;
+ }
+ return true;
+ }
+ }
+
+ private void onOverlayClientReady(IDreamOverlayClient client) {
+ mClient = client;
+ for (Consumer consumer : mConsumers) {
+ consumer.accept(mClient);
+ }
+ }
+
+ private void onAddConsumer(Consumer consumer) {
+ if (mClient != null) {
+ consumer.accept(mClient);
+ }
+ mConsumers.add(consumer);
+ }
+
+ private void onRemoveConsumer(Consumer consumer) {
+ mConsumers.remove(consumer);
+ }
+
+ private final class OverlayConnectionCallback implements
+ ObservableServiceConnection.Callback {
+
+ private final IDreamOverlayClientCallback mClientCallback =
+ new IDreamOverlayClientCallback.Stub() {
+ @Override
+ public void onDreamOverlayClient(IDreamOverlayClient client) {
+ final Message msg =
+ mHandler.obtainMessage(MSG_OVERLAY_CLIENT_READY, client);
+ mHandler.sendMessage(msg);
+ }
+ };
+
+ @Override
+ public void onConnected(
+ ObservableServiceConnection connection,
+ IDreamOverlay service) {
+ try {
+ service.getClient(mClientCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "could not get DreamOverlayClient", e);
+ }
+ }
+
+ @Override
+ public void onDisconnected(ObservableServiceConnection connection,
+ int reason) {
+ mClient = null;
+ // Cancel any pending messages about the overlay being ready, since it is no
+ // longer ready.
+ mHandler.removeMessages(MSG_OVERLAY_CLIENT_READY);
+ }
+ }
+
+ /**
+ * 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 PersistentServiceConnection buildConnection(
+ Context context,
+ Handler handler,
+ Intent serviceIntent,
+ int minConnectionDurationMs,
+ int maxReconnectAttempts,
+ int baseReconnectDelayMs) {
+ final Executor executor = handler::post;
+ final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
+ return new PersistentServiceConnection<>(
+ context,
+ executor,
+ handler,
+ IDreamOverlay.Stub::asInterface,
+ serviceIntent,
+ flags,
+ minConnectionDurationMs,
+ maxReconnectAttempts,
+ baseReconnectDelayMs
+ );
+ }
+ }
+}
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 6e8198ba0cd1a460c9cb4dfa0c63ed764f8c1911..5469916bea4e03480767141ae45584aeea114a87 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -27,6 +27,8 @@ import android.os.RemoteException;
import android.util.Log;
import android.view.WindowManager;
+import java.util.concurrent.Executor;
+
/**
* Basic implementation of for {@link IDreamOverlay} for testing.
@@ -36,37 +38,138 @@ import android.view.WindowManager;
public abstract class DreamOverlayService extends Service {
private static final String TAG = "DreamOverlayService";
private static final boolean DEBUG = false;
- private boolean mShowComplications;
- private ComponentName mDreamComponent;
- private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+ // The last client that started dreaming and hasn't ended
+ private OverlayClient mCurrentClient;
+
+ /**
+ * Executor used to run callbacks that subclasses will implement. Any calls coming over Binder
+ * from {@link OverlayClient} should perform the work they need to do on this executor.
+ */
+ private Executor mExecutor;
+
+ // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
+ // requests to the {@link DreamOverlayService}
+ private static class OverlayClient extends IDreamOverlayClient.Stub {
+ private final DreamOverlayService mService;
+ private boolean mShowComplications;
+ private ComponentName mDreamComponent;
+ IDreamOverlayCallback mDreamOverlayCallback;
+
+ OverlayClient(DreamOverlayService service) {
+ mService = service;
+ }
+
@Override
- public void startDream(WindowManager.LayoutParams layoutParams,
- IDreamOverlayCallback callback, String dreamComponent,
- boolean shouldShowComplications) {
- mDreamOverlayCallback = callback;
+ public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
+ String dreamComponent, boolean shouldShowComplications) throws RemoteException {
mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
mShowComplications = shouldShowComplications;
- onStartDream(layoutParams);
+ mDreamOverlayCallback = callback;
+ mService.startDream(this, params);
}
@Override
public void wakeUp() {
- onWakeUp(() -> {
+ mService.wakeUp(this, () -> {
try {
mDreamOverlayCallback.onWakeUpComplete();
} catch (RemoteException e) {
- Log.e(TAG, "Could not notify dream of wakeUp:" + e);
+ Log.e(TAG, "Could not notify dream of wakeUp", e);
}
});
}
- };
- IDreamOverlayCallback mDreamOverlayCallback;
+ @Override
+ public void endDream() {
+ mService.endDream(this);
+ }
+
+ private void onExitRequested() {
+ try {
+ mDreamOverlayCallback.onExitRequested();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not request exit:" + e);
+ }
+ }
+
+ private boolean shouldShowComplications() {
+ return mShowComplications;
+ }
+
+ private ComponentName getComponent() {
+ return mDreamComponent;
+ }
+ }
+
+ private void startDream(OverlayClient client, WindowManager.LayoutParams params) {
+ // Run on executor as this is a binder call from OverlayClient.
+ mExecutor.execute(() -> {
+ endDreamInternal(mCurrentClient);
+ mCurrentClient = client;
+ onStartDream(params);
+ });
+ }
+
+ private void endDream(OverlayClient client) {
+ // Run on executor as this is a binder call from OverlayClient.
+ mExecutor.execute(() -> endDreamInternal(client));
+ }
+
+ private void endDreamInternal(OverlayClient client) {
+ if (client == null || client != mCurrentClient) {
+ return;
+ }
+
+ onEndDream();
+ mCurrentClient = null;
+ }
+
+ private void wakeUp(OverlayClient client, Runnable callback) {
+ // Run on executor as this is a binder call from OverlayClient.
+ mExecutor.execute(() -> {
+ if (mCurrentClient != client) {
+ return;
+ }
+
+ onWakeUp(callback);
+ });
+ }
+
+ private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+ @Override
+ public void getClient(IDreamOverlayClientCallback callback) {
+ try {
+ callback.onDreamOverlayClient(
+ new OverlayClient(DreamOverlayService.this));
+ } catch (RemoteException e) {
+ Log.e(TAG, "could not send client to callback", e);
+ }
+ }
+ };
public DreamOverlayService() {
}
+ /**
+ * This constructor allows providing an executor to run callbacks on.
+ *
+ * @hide
+ */
+ public DreamOverlayService(@NonNull Executor executor) {
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (mExecutor == null) {
+ // If no executor was provided, use the main executor. onCreate is the earliest time
+ // getMainExecutor is available.
+ mExecutor = getMainExecutor();
+ }
+ }
+
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
@@ -76,6 +179,10 @@ public abstract class DreamOverlayService extends Service {
/**
* This method is overridden by implementations to handle when the dream has started and the
* window is ready to be interacted with.
+ *
+ * This callback will be run on the {@link Executor} provided in the constructor if provided, or
+ * on the main executor if none was provided.
+ *
* @param layoutParams The {@link android.view.WindowManager.LayoutParams} associated with the
* dream window.
*/
@@ -83,31 +190,51 @@ public abstract class DreamOverlayService extends Service {
/**
* This method is overridden by implementations to handle when the dream has been requested
- * to wakeup. This allows any overlay animations to run.
+ * to wakeup. This allows any overlay animations to run. By default, the method will invoke
+ * the callback immediately.
+ *
+ * This callback will be run on the {@link Executor} provided in the constructor if provided, or
+ * on the main executor if none was provided.
*
* @param onCompleteCallback The callback to trigger to notify the dream service that the
* overlay has completed waking up.
* @hide
*/
public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+ onCompleteCallback.run();
+ }
+
+ /**
+ * This method is overridden by implementations to handle when the dream has ended. There may
+ * be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}.
+ *
+ * This callback will be run on the {@link Executor} provided in the constructor if provided, or
+ * on the main executor if none was provided.
+ */
+ public void onEndDream() {
}
/**
* This method is invoked to request the dream exit.
*/
public final void requestExit() {
- try {
- mDreamOverlayCallback.onExitRequested();
- } catch (RemoteException e) {
- Log.e(TAG, "Could not request exit:" + e);
+ if (mCurrentClient == null) {
+ throw new IllegalStateException("requested exit with no dream present");
}
+
+ mCurrentClient.onExitRequested();
}
/**
* Returns whether to show complications on the dream overlay.
*/
public final boolean shouldShowComplications() {
- return mShowComplications;
+ if (mCurrentClient == null) {
+ throw new IllegalStateException(
+ "requested if should show complication when no dream active");
+ }
+
+ return mCurrentClient.shouldShowComplications();
}
/**
@@ -115,6 +242,10 @@ public abstract class DreamOverlayService extends Service {
* @hide
*/
public final ComponentName getDreamComponent() {
- return mDreamComponent;
+ if (mCurrentClient == null) {
+ throw new IllegalStateException("requested dream component when no dream active");
+ }
+
+ return mCurrentClient.getComponent();
}
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 8b9852a8f1b7963a94b957944a2f13a0ab114c0e..9107c5f4bbdbeba374521ebe6aea0c6ca4243bb0 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -68,8 +68,6 @@ import android.view.accessibility.AccessibilityEvent;
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;
@@ -77,8 +75,6 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -245,68 +241,7 @@ public class DreamService extends Service implements Window.Callback {
private DreamServiceWrapper mDreamServiceWrapper;
private Runnable mDispatchAfterOnAttachedToWindow;
- private OverlayConnection mOverlayConnection;
-
- private static class OverlayConnection extends PersistentServiceConnection {
- // Overlay set during onBind.
- private IDreamOverlay mOverlay;
- // A list of pending requests to execute on the overlay.
- private final ArrayList> mConsumers = new ArrayList<>();
-
- private final Callback mCallback = new Callback() {
- @Override
- public void onConnected(ObservableServiceConnection connection,
- IDreamOverlay service) {
- mOverlay = service;
- for (Consumer consumer : mConsumers) {
- consumer.accept(mOverlay);
- }
- }
-
- @Override
- public void onDisconnected(ObservableServiceConnection connection,
- int reason) {
- mOverlay = null;
- }
- };
-
- OverlayConnection(Context context,
- Executor executor,
- Handler handler,
- ServiceTransformer transformer,
- Intent serviceIntent,
- int flags,
- int minConnectionDurationMs,
- int maxReconnectAttempts,
- int baseReconnectDelayMs) {
- super(context, executor, handler, transformer, serviceIntent, flags,
- minConnectionDurationMs,
- maxReconnectAttempts, baseReconnectDelayMs);
- }
-
- @Override
- public boolean bind() {
- addCallback(mCallback);
- return super.bind();
- }
-
- @Override
- public void unbind() {
- removeCallback(mCallback);
- super.unbind();
- }
-
- public void addConsumer(Consumer consumer) {
- mConsumers.add(consumer);
- if (mOverlay != null) {
- consumer.accept(mOverlay);
- }
- }
-
- public void removeConsumer(Consumer consumer) {
- mConsumers.remove(consumer);
- }
- }
+ private DreamOverlayConnectionHandler mOverlayConnection;
private final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
@Override
@@ -1009,18 +944,18 @@ public class DreamService extends Service implements Window.Callback {
final Resources resources = getResources();
final Intent overlayIntent = new Intent().setComponent(overlayComponent);
- mOverlayConnection = new OverlayConnection(
+ mOverlayConnection = new DreamOverlayConnectionHandler(
/* context= */ this,
- getMainExecutor(),
- mHandler,
- IDreamOverlay.Stub::asInterface,
+ Looper.getMainLooper(),
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();
+ if (!mOverlayConnection.bind()) {
+ // Binding failed.
+ mOverlayConnection = null;
+ }
}
return mDreamServiceWrapper;
@@ -1031,6 +966,7 @@ public class DreamService extends Service implements Window.Callback {
// We must unbind from any overlay connection if we are unbound before finishing.
if (mOverlayConnection != null) {
mOverlayConnection.unbind();
+ mOverlayConnection = null;
}
return super.onUnbind(intent);
@@ -1044,6 +980,23 @@ public class DreamService extends Service implements Window.Callback {
*
*/
public final void finish() {
+ // If there is an active overlay connection, signal that the dream is ending before
+ // continuing. Note that the overlay cannot rely on the unbound state, since another dream
+ // might have bound to it in the meantime.
+ if (mOverlayConnection != null) {
+ mOverlayConnection.addConsumer(overlay -> {
+ try {
+ overlay.endDream();
+ mOverlayConnection.unbind();
+ mOverlayConnection = null;
+ finish();
+ } catch (RemoteException e) {
+ Log.e(mTag, "could not inform overlay of dream end:" + e);
+ }
+ });
+ return;
+ }
+
if (mDebug) Slog.v(mTag, "finish(): mFinished=" + mFinished);
Activity activity = mActivity;
@@ -1060,10 +1013,6 @@ public class DreamService extends Service implements Window.Callback {
}
mFinished = true;
- if (mOverlayConnection != null) {
- mOverlayConnection.unbind();
- }
-
if (mDreamToken == null) {
if (mDebug) Slog.v(mTag, "finish() called when not attached.");
stopSelf();
@@ -1266,9 +1215,10 @@ public class DreamService extends Service implements Window.Callback {
* Must run on mHandler.
*
* @param dreamToken Token for this dream service.
- * @param started A callback that will be invoked once onDreamingStarted has completed.
+ * @param started A callback that will be invoked once onDreamingStarted has completed.
*/
- private void attach(IBinder dreamToken, boolean canDoze, IRemoteCallback started) {
+ private void attach(IBinder dreamToken, boolean canDoze, boolean isPreviewMode,
+ IRemoteCallback started) {
if (mDreamToken != null) {
Slog.e(mTag, "attach() called when dream with token=" + mDreamToken
+ " already attached");
@@ -1316,12 +1266,18 @@ public class DreamService extends Service implements Window.Callback {
i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
final ServiceInfo serviceInfo = fetchServiceInfo(this,
new ComponentName(this, getClass()));
- i.putExtra(DreamActivity.EXTRA_DREAM_TITLE, fetchDreamLabel(this, serviceInfo));
+ i.putExtra(DreamActivity.EXTRA_DREAM_TITLE,
+ fetchDreamLabel(this, serviceInfo, isPreviewMode));
try {
if (!ActivityTaskManager.getService().startDreamActivity(i)) {
detach();
}
+ } catch (SecurityException e) {
+ Log.w(mTag,
+ "Received SecurityException trying to start DreamActivity. "
+ + "Aborting dream start.");
+ detach();
} catch (RemoteException e) {
Log.w(mTag, "Could not connect to activity task manager to start dream activity");
e.rethrowFromSystemServer();
@@ -1359,7 +1315,7 @@ public class DreamService extends Service implements Window.Callback {
mWindow.getDecorView().addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
- private Consumer mDreamStartOverlayConsumer;
+ private Consumer mDreamStartOverlayConsumer;
@Override
public void onViewAttachedToWindow(View v) {
@@ -1369,6 +1325,10 @@ public class DreamService extends Service implements Window.Callback {
// Request the DreamOverlay be told to dream with dream's window
// parameters once the window has been attached.
mDreamStartOverlayConsumer = overlay -> {
+ if (mWindow == null) {
+ Slog.d(TAG, "mWindow is null");
+ return;
+ }
try {
overlay.startDream(mWindow.getAttributes(), mOverlayCallback,
mDreamComponent.flattenToString(),
@@ -1391,6 +1351,7 @@ public class DreamService extends Service implements Window.Callback {
mActivity = null;
finish();
}
+
if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) {
mOverlayConnection.removeConsumer(mDreamStartOverlayConsumer);
}
@@ -1431,10 +1392,18 @@ public class DreamService extends Service implements Window.Callback {
@Nullable
private static CharSequence fetchDreamLabel(Context context,
- @Nullable ServiceInfo serviceInfo) {
- if (serviceInfo == null) return null;
+ @Nullable ServiceInfo serviceInfo,
+ boolean isPreviewMode) {
+ if (serviceInfo == null) {
+ return null;
+ }
final PackageManager pm = context.getPackageManager();
- return serviceInfo.loadLabel(pm);
+ final CharSequence dreamLabel = serviceInfo.loadLabel(pm);
+ if (!isPreviewMode || dreamLabel == null) {
+ return dreamLabel;
+ }
+ // When in preview mode, return a special label indicating the dream is in preview.
+ return context.getResources().getString(R.string.dream_preview_title, dreamLabel);
}
@Nullable
@@ -1490,8 +1459,9 @@ public class DreamService extends Service implements Window.Callback {
final class DreamServiceWrapper extends IDreamService.Stub {
@Override
public void attach(final IBinder dreamToken, final boolean canDoze,
- IRemoteCallback started) {
- mHandler.post(() -> DreamService.this.attach(dreamToken, canDoze, started));
+ final boolean isPreviewMode, IRemoteCallback started) {
+ mHandler.post(
+ () -> DreamService.this.attach(dreamToken, canDoze, isPreviewMode, started));
}
@Override
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 7aeceb2ce5380990b22fb281c4fb446b203b8e5a..7ec75a50ed00affddde67321f58f8a202a941d96 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -16,8 +16,7 @@
package android.service.dreams;
-import android.service.dreams.IDreamOverlayCallback;
-import android.view.WindowManager.LayoutParams;
+import android.service.dreams.IDreamOverlayClientCallback;
/**
* {@link IDreamOverlay} provides a way for a component to annotate a dream with additional view
@@ -28,17 +27,7 @@ import android.view.WindowManager.LayoutParams;
*/
interface IDreamOverlay {
/**
- * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
- token of the Dream Activity.
- * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
- * dream.
- * @param dreamComponent The component name of the dream service requesting overlay.
- * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
- * and weather.
+ * Retrieves a client the caller can use to interact with the dream overlay.
*/
- void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
- in String dreamComponent, in boolean shouldShowComplications);
-
- /** Called when the dream is waking, to do any exit animations */
- void wakeUp();
+ void getClient(in IDreamOverlayClientCallback callback);
}
diff --git a/core/java/android/service/dreams/IDreamOverlayClient.aidl b/core/java/android/service/dreams/IDreamOverlayClient.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..78b7280ae652ec11e745616442392678c7602ffb
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamOverlayClient.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams;
+
+import android.service.dreams.IDreamOverlayCallback;
+import android.view.WindowManager.LayoutParams;
+
+/**
+* {@link IDreamOverlayClient} allows {@link DreamService} instances to act upon the dream overlay.
+*
+* @hide
+*/
+interface IDreamOverlayClient {
+ /**
+ * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
+ token of the Dream Activity.
+ * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
+ * dream.
+ * @param dreamComponent The component name of the dream service requesting overlay.
+ * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
+ * and weather.
+ */
+ void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
+ in String dreamComponent, in boolean shouldShowComplications);
+
+ /** Called when the dream is waking, to do any exit animations */
+ void wakeUp();
+
+ /** Called when the dream has ended. */
+ void endDream();
+}
diff --git a/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..244d999c623bd605127d550f7196dace3ec99020
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams;
+
+import android.service.dreams.IDreamOverlayClient;
+
+/**
+* {@link IDreamOverlayClientCallback} allows receiving a requested {@link IDreamOverlayClient}.
+* @hide
+*/
+interface IDreamOverlayClientCallback {
+ /**
+ * Called with a unique {@link IDreamOverlayClient}.
+ */
+ void onDreamOverlayClient(in IDreamOverlayClient client);
+}
diff --git a/core/java/android/service/dreams/IDreamService.aidl b/core/java/android/service/dreams/IDreamService.aidl
index ce04354989179e46cc49fc29ea4f7b071bc57ebd..8b5d8754647c2a56fdfe43e87c8c6a3d94d8d6eb 100644
--- a/core/java/android/service/dreams/IDreamService.aidl
+++ b/core/java/android/service/dreams/IDreamService.aidl
@@ -22,7 +22,7 @@ import android.os.IRemoteCallback;
* @hide
*/
oneway interface IDreamService {
- void attach(IBinder windowToken, boolean canDoze, IRemoteCallback started);
+ void attach(IBinder windowToken, boolean canDoze, boolean isPreviewMode, IRemoteCallback started);
void detach();
void wakeUp();
}
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 4b25c8832068f6bd78d1f0386d556e7a608b8c11..182a49758892af3b6277addf2bc0a1e56720bd4f 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -52,7 +52,7 @@ public final class Adjustment implements Parcelable {
/** @hide */
@StringDef (prefix = { "KEY_" }, value = {
KEY_CONTEXTUAL_ACTIONS, KEY_GROUP_KEY, KEY_IMPORTANCE, KEY_PEOPLE, KEY_SNOOZE_CRITERIA,
- KEY_TEXT_REPLIES, KEY_USER_SENTIMENT
+ KEY_TEXT_REPLIES, KEY_USER_SENTIMENT, KEY_IMPORTANCE_PROPOSAL
})
@Retention(RetentionPolicy.SOURCE)
public @interface Keys {}
@@ -121,6 +121,19 @@ public final class Adjustment implements Parcelable {
*/
public static final String KEY_IMPORTANCE = "key_importance";
+ /**
+ * Weaker than {@link #KEY_IMPORTANCE}, this adjustment suggests an importance rather than
+ * mandates an importance change.
+ *
+ * A notification listener can interpet this suggestion to show the user a prompt to change
+ * notification importance for the notification (or type, or app) moving forward.
+ *
+ * Data type: int, one of importance values e.g.
+ * {@link android.app.NotificationManager#IMPORTANCE_MIN}.
+ * @hide
+ */
+ public static final String KEY_IMPORTANCE_PROPOSAL = "key_importance_proposal";
+
/**
* Data type: float, a ranking score from 0 (lowest) to 1 (highest).
* Used to rank notifications inside that fall under the same classification (i.e. alerting,
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index ad2e9d5109987c46e0eca8011e7e700db4df9d24..dc4cb9f0983501c7be62aa38c3747b006bccd028 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1711,6 +1711,8 @@ public abstract class NotificationListenerService extends Service {
private ShortcutInfo mShortcutInfo;
private @RankingAdjustment int mRankingAdjustment;
private boolean mIsBubble;
+ // Notification assistant importance suggestion
+ private int mProposedImportance;
private static final int PARCEL_VERSION = 2;
@@ -1748,6 +1750,7 @@ public abstract class NotificationListenerService extends Service {
out.writeParcelable(mShortcutInfo, flags);
out.writeInt(mRankingAdjustment);
out.writeBoolean(mIsBubble);
+ out.writeInt(mProposedImportance);
}
/** @hide */
@@ -1786,6 +1789,7 @@ public abstract class NotificationListenerService extends Service {
mShortcutInfo = in.readParcelable(cl, android.content.pm.ShortcutInfo.class);
mRankingAdjustment = in.readInt();
mIsBubble = in.readBoolean();
+ mProposedImportance = in.readInt();
}
@@ -1877,6 +1881,22 @@ public abstract class NotificationListenerService extends Service {
return mRankingScore;
}
+ /**
+ * Returns the proposed importance provided by the {@link NotificationAssistantService}.
+ *
+ * This can be used to suggest that the user change the importance of this type of
+ * notification moving forward. A value of
+ * {@link NotificationManager#IMPORTANCE_UNSPECIFIED} means that the NAS has not recommended
+ * a change to the importance, and no UI should be shown to the user. See
+ * {@link Adjustment#KEY_IMPORTANCE_PROPOSAL}.
+ *
+ * @return the importance of the notification
+ * @hide
+ */
+ public @NotificationManager.Importance int getProposedImportance() {
+ return mProposedImportance;
+ }
+
/**
* If the system has overridden the group key, then this will be non-null, and this
* key should be used to bundle notifications.
@@ -2041,7 +2061,7 @@ public abstract class NotificationListenerService extends Service {
boolean noisy, ArrayList smartActions,
ArrayList smartReplies, boolean canBubble,
boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo,
- int rankingAdjustment, boolean isBubble) {
+ int rankingAdjustment, boolean isBubble, int proposedImportance) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -2067,6 +2087,7 @@ public abstract class NotificationListenerService extends Service {
mShortcutInfo = shortcutInfo;
mRankingAdjustment = rankingAdjustment;
mIsBubble = isBubble;
+ mProposedImportance = proposedImportance;
}
/**
@@ -2107,7 +2128,8 @@ public abstract class NotificationListenerService extends Service {
other.mIsConversation,
other.mShortcutInfo,
other.mRankingAdjustment,
- other.mIsBubble);
+ other.mIsBubble,
+ other.mProposedImportance);
}
/**
@@ -2166,7 +2188,8 @@ public abstract class NotificationListenerService extends Service {
&& Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
(other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()))
&& Objects.equals(mRankingAdjustment, other.mRankingAdjustment)
- && Objects.equals(mIsBubble, other.mIsBubble);
+ && Objects.equals(mIsBubble, other.mIsBubble)
+ && Objects.equals(mProposedImportance, other.mProposedImportance);
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index e285b1c771221508868927814e295ed06ba7abac..29b0d444e514a19884fcf14da8b95ad88b90adb7 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -953,7 +953,7 @@ public class ZenModeConfig implements Parcelable {
private static Uri safeUri(TypedXmlPullParser parser, String att) {
final String val = parser.getAttributeValue(null, att);
- if (TextUtils.isEmpty(val)) return null;
+ if (val == null) return null;
return Uri.parse(val);
}
@@ -1008,9 +1008,8 @@ public class ZenModeConfig implements Parcelable {
.allowAlarms(allowAlarms)
.allowMedia(allowMedia)
.allowSystem(allowSystem)
- .allowConversations(allowConversations
- ? ZenModeConfig.getZenPolicySenders(allowConversationsFrom)
- : ZenPolicy.PEOPLE_TYPE_NONE);
+ .allowConversations(allowConversations ? allowConversationsFrom
+ : ZenPolicy.CONVERSATION_SENDERS_NONE);
if (suppressedVisualEffects == 0) {
builder.showAllVisualEffects();
} else {
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index b09d2e9e7f139b85628f7da435402fff01b7a31b..e234755b2b13df3a5736301511a28f3fd4a2b625 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -16,6 +16,7 @@
package android.service.quickaccesswallet;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -24,28 +25,73 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
/**
* A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit
* card, library card, transit pass, etc. Cards are identified by a String identifier and contain a
- * card image, card image content description, and a {@link PendingIntent} to be used if the user
- * clicks on the card. Cards may be displayed with an icon and label, though these are optional.
+ * card type, card image, card image content description, and a {@link PendingIntent} to be used if
+ * the user clicks on the card. Cards may be displayed with an icon and label, though these are
+ * optional. Non-payment cards will also have a second image that will be displayed when the card is
+ * tapped.
*/
+
public final class WalletCard implements Parcelable {
+ /**
+ * Unknown cards refer to cards whose types are unspecified.
+ * @hide
+ */
+ public static final int CARD_TYPE_UNKNOWN = 0;
+
+ /**
+ * Payment cards refer to credit cards, debit cards or any other cards in the wallet used to
+ * make cash-equivalent payments.
+ * @hide
+ */
+ public static final int CARD_TYPE_PAYMENT = 1;
+
+ /**
+ * Non-payment cards refer to any cards that are not used for cash-equivalent payment, including
+ * event tickets, flights, offers, loyalty cards, gift cards and transit tickets.
+ * @hide
+ */
+ public static final int CARD_TYPE_NON_PAYMENT = 2;
+
private final String mCardId;
+ private final int mCardType;
private final Icon mCardImage;
private final CharSequence mContentDescription;
private final PendingIntent mPendingIntent;
private final Icon mCardIcon;
private final CharSequence mCardLabel;
+ private final Icon mNonPaymentCardSecondaryImage;
private WalletCard(Builder builder) {
this.mCardId = builder.mCardId;
+ this.mCardType = builder.mCardType;
this.mCardImage = builder.mCardImage;
this.mContentDescription = builder.mContentDescription;
this.mPendingIntent = builder.mPendingIntent;
this.mCardIcon = builder.mCardIcon;
this.mCardLabel = builder.mCardLabel;
+ this.mNonPaymentCardSecondaryImage = builder.mNonPaymentCardSecondaryImage;
+ }
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CARD_TYPE_"}, value = {
+ CARD_TYPE_UNKNOWN,
+ CARD_TYPE_PAYMENT,
+ CARD_TYPE_NON_PAYMENT
+ })
+ public @interface CardType {
}
@Override
@@ -56,29 +102,44 @@ public final class WalletCard implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mCardId);
+ dest.writeInt(mCardType);
mCardImage.writeToParcel(dest, flags);
TextUtils.writeToParcel(mContentDescription, dest, flags);
PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
- if (mCardIcon == null) {
+ writeIconIfNonNull(mCardIcon, dest, flags);
+ TextUtils.writeToParcel(mCardLabel, dest, flags);
+ writeIconIfNonNull(mNonPaymentCardSecondaryImage, dest, flags);
+
+ }
+
+ /** Utility function called by writeToParcel
+ */
+ private void writeIconIfNonNull(Icon icon, Parcel dest, int flags) {
+ if (icon == null) {
dest.writeByte((byte) 0);
} else {
dest.writeByte((byte) 1);
- mCardIcon.writeToParcel(dest, flags);
+ icon.writeToParcel(dest, flags);
}
- TextUtils.writeToParcel(mCardLabel, dest, flags);
}
private static WalletCard readFromParcel(Parcel source) {
String cardId = source.readString();
+ int cardType = source.readInt();
Icon cardImage = Icon.CREATOR.createFromParcel(source);
CharSequence contentDesc = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source);
Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- return new Builder(cardId, cardImage, contentDesc, pendingIntent)
+ Icon nonPaymentCardSecondaryImage = source.readByte() == 0 ? null :
+ Icon.CREATOR.createFromParcel(source);
+ Builder builder = new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent)
.setCardIcon(cardIcon)
- .setCardLabel(cardLabel)
- .build();
+ .setCardLabel(cardLabel);
+
+ return cardType == CARD_TYPE_NON_PAYMENT
+ ? builder.setNonPaymentCardSecondaryImage(nonPaymentCardSecondaryImage).build() :
+ builder.build();
}
@NonNull
@@ -103,6 +164,16 @@ public final class WalletCard implements Parcelable {
return mCardId;
}
+ /**
+ * Returns the card type.
+ * @hide
+ */
+ @NonNull
+ @CardType
+ public int getCardType() {
+ return mCardType;
+ }
+
/**
* The visual representation of the card. If the card image Icon is a bitmap, it should have a
* width of {@link GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
@@ -158,23 +229,37 @@ public final class WalletCard implements Parcelable {
}
/**
- * Builder for {@link WalletCard} objects. You must to provide cardId, cardImage,
+ * Visual representation of the card when it is tapped. May include additional information
+ * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
+ * @hide
+ */
+ @Nullable
+ public Icon getNonPaymentCardSecondaryImage() {
+ return mNonPaymentCardSecondaryImage;
+ }
+
+ /**
+ * Builder for {@link WalletCard} objects. You must provide cardId, cardImage,
* contentDescription, and pendingIntent. If the card is opaque and should be shown with
* elevation, set hasShadow to true. cardIcon and cardLabel are optional.
*/
public static final class Builder {
private String mCardId;
+ private int mCardType;
private Icon mCardImage;
private CharSequence mContentDescription;
private PendingIntent mPendingIntent;
private Icon mCardIcon;
private CharSequence mCardLabel;
+ private Icon mNonPaymentCardSecondaryImage;
/**
* @param cardId The card id must be non-null and unique within the list of
* cards returned. Note:
* this card ID should not contain PII (Personally
* Identifiable Information, such as username or email address).
+ * @param cardType Integer representing the card type. The card type must be
+ * non-null.
* @param cardImage The visual representation of the card. If the card image Icon
* is a bitmap, it should have a width of {@link
* GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
@@ -193,17 +278,32 @@ public final class WalletCard implements Parcelable {
* request device unlock before sending the pending intent. It is
* recommended that the pending intent be immutable (use {@link
* PendingIntent#FLAG_IMMUTABLE}).
+ * @hide
*/
public Builder(@NonNull String cardId,
+ @NonNull @CardType int cardType,
@NonNull Icon cardImage,
@NonNull CharSequence contentDescription,
- @NonNull PendingIntent pendingIntent) {
+ @NonNull PendingIntent pendingIntent
+ ) {
mCardId = cardId;
+ mCardType = cardType;
mCardImage = cardImage;
mContentDescription = contentDescription;
mPendingIntent = pendingIntent;
}
+ /**
+ * Called when a card type is not provided, in which case it defaults to CARD_TYPE_UNKNOWN.
+ */
+ public Builder(@NonNull String cardId,
+ @NonNull Icon cardImage,
+ @NonNull CharSequence contentDescription,
+ @NonNull PendingIntent pendingIntent) {
+ this(cardId, WalletCard.CARD_TYPE_UNKNOWN, cardImage, contentDescription,
+ pendingIntent);
+ }
+
/**
* An icon may be shown alongside the card image to convey information about how the card
* can be used, or if some other action must be taken before using the card. For example, an
@@ -235,6 +335,20 @@ public final class WalletCard implements Parcelable {
return this;
}
+ /**
+ * Visual representation of the card when it is tapped. May include additional information
+ * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
+ * @hide
+ */
+ @NonNull
+ public Builder
+ setNonPaymentCardSecondaryImage(@Nullable Icon nonPaymentCardSecondaryImage) {
+ Preconditions.checkState(mCardType == CARD_TYPE_NON_PAYMENT,
+ "This field can only be set on non-payment cards");
+ mNonPaymentCardSecondaryImage = nonPaymentCardSecondaryImage;
+ return this;
+ }
+
/**
* Builds a new {@link WalletCard} instance.
*
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 506b3b81eb9ad0524c6d0b4d9650dd9932e94824..81c5796374af3d810c47cb2890403300aae3a7ff 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -506,7 +506,7 @@ public class TileService extends Service {
* the calling package or if the calling user cannot act on behalf of the user from the
* {@code context}.
*
{@link IllegalArgumentException} if the user of the {@code context} is not the
- * current user.
+ * current user. Only thrown for apps targeting {@link Build.VERSION_CODES#TIRAMISU}
*
*/
public static final void requestListeningState(Context context, ComponentName component) {
diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java
index 3a148dffe6d6996c5f7f631d6ea238483452bd52..b13a069116af088d3a790a1af43123b6dd4c5050 100644
--- a/core/java/android/service/smartspace/SmartspaceService.java
+++ b/core/java/android/service/smartspace/SmartspaceService.java
@@ -302,7 +302,7 @@ public abstract class SmartspaceService extends Service {
Slog.e(TAG, "Callback is null, likely the binder has died.");
return false;
}
- return mCallback.equals(callback);
+ return mCallback.asBinder().equals(callback.asBinder());
}
@Override
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 1a00acf475e3db905a9af91ef03c39c7f40ecea0..15a8502a2e7ff5999dcd4372b7dc57645fa54165 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -32,6 +32,8 @@ interface IWallpaperEngine {
oneway void setDisplayPadding(in Rect padding);
@UnsupportedAppUsage
oneway void setVisibility(boolean visible);
+ oneway void onScreenTurningOn();
+ oneway void onScreenTurnedOn();
oneway void setInAmbientMode(boolean inAmbientDisplay, long animationDuration);
@UnsupportedAppUsage
oneway void dispatchPointer(in MotionEvent event);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 2d1a41e92a99a8d0ebf04badd02d41f5b6459aac..8f1fc1b9348e7fdbab4b4bce77adf8f0336762e2 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -61,6 +61,7 @@ import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -96,6 +97,7 @@ import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.ClientWindowFrames;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.HandlerCaller;
import com.android.internal.view.BaseIWindow;
@@ -104,9 +106,10 @@ import com.android.internal.view.BaseSurfaceHolder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -165,12 +168,15 @@ public abstract class WallpaperService extends Service {
private static final int MSG_ZOOM = 10100;
private static final int MSG_RESIZE_PREVIEW = 10110;
private static final int MSG_REPORT_SHOWN = 10150;
+ private static final int MSG_UPDATE_SCREEN_TURNING_ON = 10170;
private static final int MSG_UPDATE_DIMMING = 10200;
- private static final List PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
- Float.NEGATIVE_INFINITY);
+ /** limit calls to {@link Engine#onComputeColors} to at most once per second */
private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
+ /** limit calls to {@link Engine#processLocalColorsInternal} to at most once per 2 seconds */
+ private static final int PROCESS_LOCAL_COLORS_INTERVAL_MS = 2000;
+
private static final boolean ENABLE_WALLPAPER_DIMMING =
SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", true);
@@ -179,6 +185,9 @@ public abstract class WallpaperService extends Service {
private final ArrayList mActiveEngines
= new ArrayList();
+ private Handler mBackgroundHandler;
+ private HandlerThread mBackgroundThread;
+
static final class WallpaperCommand {
String action;
int x;
@@ -197,14 +206,6 @@ public abstract class WallpaperService extends Service {
*/
public class Engine {
IWallpaperEngineWrapper mIWallpaperEngine;
- final ArraySet mLocalColorAreas = new ArraySet<>(4);
- final ArraySet mLocalColorsToAdd = new ArraySet<>(4);
-
- // 2D matrix [x][y] to represent a page of a portion of a window
- EngineWindowPage[] mWindowPages = new EngineWindowPage[0];
- Bitmap mLastScreenshot;
- int mLastWindowPage = -1;
- private boolean mResetWindowPages;
// Copies from mIWallpaperEngine.
HandlerCaller mCaller;
@@ -213,6 +214,16 @@ public abstract class WallpaperService extends Service {
boolean mInitializing = true;
boolean mVisible;
+ /**
+ * Whether the screen is turning on.
+ * After the display is powered on, brightness is initially off. It is turned on only after
+ * all windows have been drawn, and sysui notifies that it's ready (See
+ * {@link com.android.internal.policy.IKeyguardDrawnCallback}).
+ * As some wallpapers use visibility as a signal to start animations, this makes sure
+ * {@link Engine#onVisibilityChanged} is invoked only when the display is both on and
+ * visible (with brightness on).
+ */
+ private boolean mIsScreenTurningOn;
boolean mReportedVisible;
boolean mDestroyed;
// Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false
@@ -266,21 +277,44 @@ public abstract class WallpaperService extends Service {
final Object mLock = new Object();
boolean mOffsetMessageEnqueued;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- float mPendingXOffset;
- float mPendingYOffset;
- float mPendingXOffsetStep;
- float mPendingYOffsetStep;
+ @GuardedBy("mLock")
+ private float mPendingXOffset;
+ @GuardedBy("mLock")
+ private float mPendingYOffset;
+ @GuardedBy("mLock")
+ private float mPendingXOffsetStep;
+ @GuardedBy("mLock")
+ private float mPendingYOffsetStep;
+
+ /**
+ * local color extraction related fields. When a user calls `addLocalColorAreas`
+ */
+ @GuardedBy("mLock")
+ private final ArraySet mLocalColorAreas = new ArraySet<>(4);
+
+ @GuardedBy("mLock")
+ private final ArraySet mLocalColorsToAdd = new ArraySet<>(4);
+ private long mLastProcessLocalColorsTimestamp;
+ private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false);
+ private int mPixelCopyCount = 0;
+ // 2D matrix [x][y] to represent a page of a portion of a window
+ @GuardedBy("mLock")
+ private EngineWindowPage[] mWindowPages = new EngineWindowPage[0];
+ private Bitmap mLastScreenshot;
+ private boolean mResetWindowPages;
+
boolean mPendingSync;
MotionEvent mPendingMove;
boolean mIsInAmbientMode;
- // Needed for throttling onComputeColors.
+ // used to throttle onComputeColors
private long mLastColorInvalidation;
private final Runnable mNotifyColorsChanged = this::notifyColorsChanged;
+
private final Supplier mClockFunction;
private final Handler mHandler;
-
private Display mDisplay;
private Context mDisplayContext;
private int mDisplayState;
@@ -820,7 +854,7 @@ public abstract class WallpaperService extends Service {
+ "was not established.");
}
mResetWindowPages = true;
- processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+ processLocalColors();
} catch (RemoteException e) {
Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
}
@@ -965,6 +999,7 @@ public abstract class WallpaperService extends Service {
out.print(" mDestroyed="); out.println(mDestroyed);
out.print(prefix); out.print("mVisible="); out.print(mVisible);
out.print(" mReportedVisible="); out.println(mReportedVisible);
+ out.print(" mIsScreenTurningOn="); out.println(mIsScreenTurningOn);
out.print(prefix); out.print("mDisplay="); out.println(mDisplay);
out.print(prefix); out.print("mCreated="); out.print(mCreated);
out.print(" mSurfaceCreated="); out.print(mSurfaceCreated);
@@ -1356,10 +1391,9 @@ public abstract class WallpaperService extends Service {
mIsCreating = false;
mSurfaceCreated = true;
if (redrawNeeded) {
- resetWindowPages();
mSession.finishDrawing(mWindow, null /* postDrawTransaction */,
Integer.MAX_VALUE);
- processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+ processLocalColors();
}
reposition();
reportEngineShown(shouldWaitForEngineShown());
@@ -1425,7 +1459,7 @@ public abstract class WallpaperService extends Service {
com.android.internal.R.dimen.config_wallpaperDimAmount);
mWallpaperDimAmount = mDefaultDimAmount;
mPreviousWallpaperDimAmount = mWallpaperDimAmount;
- mDisplayState = mDisplay.getState();
+ mDisplayState = mDisplay.getCommittedState();
mDisplayInstallOrientation = mDisplay.getInstallOrientation();
if (DEBUG) Log.v(TAG, "onCreate(): " + this);
@@ -1500,11 +1534,18 @@ public abstract class WallpaperService extends Service {
}
}
+ void onScreenTurningOnChanged(boolean isScreenTurningOn) {
+ if (!mDestroyed) {
+ mIsScreenTurningOn = isScreenTurningOn;
+ reportVisibility();
+ }
+ }
+
void doVisibilityChanged(boolean visible) {
if (!mDestroyed) {
mVisible = visible;
reportVisibility();
- if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+ if (mReportedVisible) processLocalColors();
} else {
AnimationHandler.requestAnimatorsEnabled(visible, this);
}
@@ -1516,8 +1557,10 @@ public abstract class WallpaperService extends Service {
return;
}
if (!mDestroyed) {
- mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getState();
- boolean visible = mVisible && mDisplayState != Display.STATE_OFF;
+ mDisplayState =
+ mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getCommittedState();
+ boolean displayVisible = Display.isOnState(mDisplayState) && !mIsScreenTurningOn;
+ boolean visible = mVisible && displayVisible;
if (mReportedVisible != visible) {
mReportedVisible = visible;
if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible
@@ -1588,48 +1631,83 @@ public abstract class WallpaperService extends Service {
}
// setup local color extraction data
- processLocalColors(xOffset, xOffsetStep);
+ processLocalColors();
}
- private void processLocalColors(float xOffset, float xOffsetStep) {
- // implemented by the wallpaper
- if (supportsLocalColorExtraction()) return;
- if (DEBUG) {
- Log.d(TAG, "processLocalColors " + xOffset + " of step "
- + xOffsetStep);
+ /**
+ * Thread-safe util to call {@link #processLocalColorsInternal} with a minimum interval of
+ * {@link #PROCESS_LOCAL_COLORS_INTERVAL_MS} between two calls.
+ */
+ private void processLocalColors() {
+ if (mProcessLocalColorsPending.compareAndSet(false, true)) {
+ final long now = mClockFunction.get();
+ final long timeSinceLastColorProcess = now - mLastProcessLocalColorsTimestamp;
+ final long timeToWait = Math.max(0,
+ PROCESS_LOCAL_COLORS_INTERVAL_MS - timeSinceLastColorProcess);
+
+ mHandler.postDelayed(() -> {
+ mLastProcessLocalColorsTimestamp = now + timeToWait;
+ mProcessLocalColorsPending.set(false);
+ processLocalColorsInternal();
+ }, timeToWait);
}
- //below is the default implementation
- if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN
- || !mSurfaceHolder.getSurface().isValid()) return;
- int xCurrentPage;
+ }
+
+ /**
+ * Default implementation of the local color extraction.
+ * This will take a screenshot of the whole wallpaper on the main thread.
+ * Then, in a background thread, for each launcher page, for each area that needs color
+ * extraction in this page, creates a sub-bitmap and call {@link WallpaperColors#fromBitmap}
+ * to extract the colors. Every time a launcher page has been processed, call
+ * {@link #notifyLocalColorsChanged} with the color and areas of this page.
+ */
+ private void processLocalColorsInternal() {
+ if (supportsLocalColorExtraction()) return;
+ float xOffset;
+ float xOffsetStep;
+ float wallpaperDimAmount;
+ int xPage;
int xPages;
- if (!validStep(xOffsetStep)) {
+ Set areas;
+ EngineWindowPage current;
+
+ synchronized (mLock) {
+ xOffset = mPendingXOffset;
+ xOffsetStep = mPendingXOffsetStep;
+ wallpaperDimAmount = mWallpaperDimAmount;
+
if (DEBUG) {
- Log.w(TAG, "invalid offset step " + xOffsetStep);
+ Log.d(TAG, "processLocalColors " + xOffset + " of step "
+ + xOffsetStep);
}
- xOffset = 0;
- xOffsetStep = 1;
- xCurrentPage = 0;
- xPages = 1;
- } else {
- xPages = Math.round(1 / xOffsetStep) + 1;
- xOffsetStep = (float) 1 / (float) xPages;
- float shrink = (float) (xPages - 1) / (float) xPages;
- xOffset *= shrink;
- xCurrentPage = Math.round(xOffset / xOffsetStep);
- }
- if (DEBUG) {
- Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage);
- Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
- }
+ if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN
+ || !mSurfaceHolder.getSurface().isValid()) return;
+ int xCurrentPage;
+ if (!validStep(xOffsetStep)) {
+ if (DEBUG) {
+ Log.w(TAG, "invalid offset step " + xOffsetStep);
+ }
+ xOffset = 0;
+ xOffsetStep = 1;
+ xCurrentPage = 0;
+ xPages = 1;
+ } else {
+ xPages = Math.round(1 / xOffsetStep) + 1;
+ xOffsetStep = (float) 1 / (float) xPages;
+ float shrink = (float) (xPages - 1) / (float) xPages;
+ xOffset *= shrink;
+ xCurrentPage = Math.round(xOffset / xOffsetStep);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage);
+ Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+ }
+
+ float finalXOffsetStep = xOffsetStep;
+ float finalXOffset = xOffset;
- float finalXOffsetStep = xOffsetStep;
- float finalXOffset = xOffset;
- mHandler.post(() -> {
- Trace.beginSection("WallpaperService#processLocalColors");
resetWindowPages();
- int xPage = xCurrentPage;
- EngineWindowPage current;
+ xPage = xCurrentPage;
if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
mWindowPages = new EngineWindowPage[xPages];
initWindowPages(mWindowPages, finalXOffsetStep);
@@ -1656,11 +1734,12 @@ public abstract class WallpaperService extends Service {
xPage = mWindowPages.length - 1;
}
current = mWindowPages[xPage];
- updatePage(current, xPage, xPages, finalXOffsetStep);
- Trace.endSection();
- });
+ areas = new HashSet<>(current.getAreas());
+ }
+ updatePage(current, areas, xPage, xPages, wallpaperDimAmount);
}
+ @GuardedBy("mLock")
private void initWindowPages(EngineWindowPage[] windowPages, float step) {
for (int i = 0; i < windowPages.length; i++) {
windowPages[i] = new EngineWindowPage();
@@ -1677,16 +1756,16 @@ public abstract class WallpaperService extends Service {
}
}
- void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
- float xOffsetStep) {
+ void updatePage(EngineWindowPage currentPage, Set areas, int pageIndx, int numPages,
+ float wallpaperDimAmount) {
+
// in case the clock is zero, we start with negative time
long current = SystemClock.elapsedRealtime() - DEFAULT_UPDATE_SCREENSHOT_DURATION;
long lapsed = current - currentPage.getLastUpdateTime();
// Always update the page when the last update time is <= 0
// This is important especially when the device first boots
- if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) {
- return;
- }
+ if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
+
Surface surface = mSurfaceHolder.getSurface();
if (!surface.isValid()) return;
boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y;
@@ -1699,43 +1778,59 @@ public abstract class WallpaperService extends Service {
Log.e(TAG, "wrong width and height values of bitmap " + width + " " + height);
return;
}
+ final String pixelCopySectionName = "WallpaperService#pixelCopy";
+ final int pixelCopyCount = mPixelCopyCount++;
+ Trace.beginAsyncSection(pixelCopySectionName, pixelCopyCount);
Bitmap screenShot = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
final Bitmap finalScreenShot = screenShot;
- Trace.beginSection("WallpaperService#pixelCopy");
- PixelCopy.request(surface, screenShot, (res) -> {
- Trace.endSection();
- if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
- if (res != PixelCopy.SUCCESS) {
- Bitmap lastBitmap = currentPage.getBitmap();
- // assign the last bitmap taken for now
- currentPage.setBitmap(mLastScreenshot);
- Bitmap lastScreenshot = mLastScreenshot;
- if (lastScreenshot != null && !lastScreenshot.isRecycled()
- && !Objects.equals(lastBitmap, lastScreenshot)) {
- updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
+ try {
+ // TODO(b/274427458) check if this can be done in the background.
+ PixelCopy.request(surface, screenShot, (res) -> {
+ Trace.endAsyncSection(pixelCopySectionName, pixelCopyCount);
+ if (DEBUG) {
+ Log.d(TAG, "result of pixel copy is: "
+ + (res == PixelCopy.SUCCESS ? "SUCCESS" : "FAILURE"));
}
- } else {
- mLastScreenshot = finalScreenShot;
- // going to hold this lock for a while
- currentPage.setBitmap(finalScreenShot);
- currentPage.setLastUpdateTime(current);
- updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
- }
- }, mHandler);
-
+ if (res != PixelCopy.SUCCESS) {
+ Bitmap lastBitmap = currentPage.getBitmap();
+ // assign the last bitmap taken for now
+ currentPage.setBitmap(mLastScreenshot);
+ Bitmap lastScreenshot = mLastScreenshot;
+ if (lastScreenshot != null && !Objects.equals(lastBitmap, lastScreenshot)) {
+ updatePageColors(
+ currentPage, areas, pageIndx, numPages, wallpaperDimAmount);
+ }
+ } else {
+ mLastScreenshot = finalScreenShot;
+ currentPage.setBitmap(finalScreenShot);
+ currentPage.setLastUpdateTime(current);
+ updatePageColors(
+ currentPage, areas, pageIndx, numPages, wallpaperDimAmount);
+ }
+ }, mBackgroundHandler);
+ } catch (IllegalArgumentException e) {
+ // this can potentially happen if the surface is invalidated right between the
+ // surface.isValid() check and the PixelCopy operation.
+ // in this case, stop: we'll compute colors on the next processLocalColors call.
+ Log.w(TAG, "Cancelling processLocalColors: exception caught during PixelCopy");
+ }
}
// locked by the passed page
- private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
- float xOffsetStep) {
+ private void updatePageColors(EngineWindowPage page, Set areas,
+ int pageIndx, int numPages, float wallpaperDimAmount) {
if (page.getBitmap() == null) return;
+ if (!mBackgroundHandler.getLooper().isCurrentThread()) {
+ throw new IllegalStateException(
+ "ProcessLocalColors should be called from the background thread");
+ }
Trace.beginSection("WallpaperService#updatePageColors");
if (DEBUG) {
Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
+ page.getAreas().size() + " and bitmap size of "
+ page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight());
}
- for (RectF area: page.getAreas()) {
+ for (RectF area: areas) {
if (area == null) continue;
RectF subArea = generateSubRect(area, pageIndx, numPages);
Bitmap b = page.getBitmap();
@@ -1745,12 +1840,12 @@ public abstract class WallpaperService extends Service {
int height = Math.round(b.getHeight() * subArea.height());
Bitmap target;
try {
- target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height);
+ target = Bitmap.createBitmap(b, x, y, width, height);
} catch (Exception e) {
Log.e(TAG, "Error creating page local color bitmap", e);
continue;
}
- WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount);
+ WallpaperColors color = WallpaperColors.fromBitmap(target, wallpaperDimAmount);
target.recycle();
WallpaperColors currentColor = page.getColors(area);
@@ -1767,12 +1862,14 @@ public abstract class WallpaperService extends Service {
+ " local color callback for area" + area + " for page " + pageIndx
+ " of " + numPages);
}
- try {
- mConnection.onLocalWallpaperColorsChanged(area, color,
- mDisplayContext.getDisplayId());
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
- }
+ mHandler.post(() -> {
+ try {
+ mConnection.onLocalWallpaperColorsChanged(area, color,
+ mDisplayContext.getDisplayId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+ }
+ });
}
}
Trace.endSection();
@@ -1798,16 +1895,17 @@ public abstract class WallpaperService extends Service {
return new RectF(left, in.top, right, in.bottom);
}
+ @GuardedBy("mLock")
private void resetWindowPages() {
if (supportsLocalColorExtraction()) return;
if (!mResetWindowPages) return;
mResetWindowPages = false;
- mLastWindowPage = -1;
for (int i = 0; i < mWindowPages.length; i++) {
mWindowPages[i].setLastUpdateTime(0L);
}
}
+ @GuardedBy("mLock")
private int getRectFPage(RectF area, float step) {
if (!isValid(area)) return 0;
if (!validStep(step)) return 0;
@@ -1828,12 +1926,12 @@ public abstract class WallpaperService extends Service {
if (DEBUG) {
Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
}
- mHandler.post(() -> {
- mLocalColorsToAdd.addAll(regions);
- processLocalColors(mPendingXOffset, mPendingYOffset);
+ mBackgroundHandler.post(() -> {
+ synchronized (mLock) {
+ mLocalColorsToAdd.addAll(regions);
+ }
+ processLocalColors();
});
-
-
}
/**
@@ -1843,16 +1941,18 @@ public abstract class WallpaperService extends Service {
*/
public void removeLocalColorsAreas(@NonNull List regions) {
if (supportsLocalColorExtraction()) return;
- mHandler.post(() -> {
- float step = mPendingXOffsetStep;
- mLocalColorsToAdd.removeAll(regions);
- mLocalColorAreas.removeAll(regions);
- if (!validStep(step)) {
- return;
- }
- for (int i = 0; i < mWindowPages.length; i++) {
- for (int j = 0; j < regions.size(); j++) {
- mWindowPages[i].removeArea(regions.get(j));
+ mBackgroundHandler.post(() -> {
+ synchronized (mLock) {
+ float step = mPendingXOffsetStep;
+ mLocalColorsToAdd.removeAll(regions);
+ mLocalColorAreas.removeAll(regions);
+ if (!validStep(step)) {
+ return;
+ }
+ for (int i = 0; i < mWindowPages.length; i++) {
+ for (int j = 0; j < regions.size(); j++) {
+ mWindowPages[i].removeArea(regions.get(j));
+ }
}
}
});
@@ -1870,7 +1970,7 @@ public abstract class WallpaperService extends Service {
}
private boolean validStep(float step) {
- return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.;
+ return !Float.isNaN(step) && step > 0f && step <= 1f;
}
void doCommand(WallpaperCommand cmd) {
@@ -2334,6 +2434,20 @@ public abstract class WallpaperService extends Service {
}
}
+ public void updateScreenTurningOn(boolean isScreenTurningOn) {
+ Message msg = mCaller.obtainMessageBO(MSG_UPDATE_SCREEN_TURNING_ON, isScreenTurningOn,
+ null);
+ mCaller.sendMessage(msg);
+ }
+
+ public void onScreenTurningOn() throws RemoteException {
+ updateScreenTurningOn(true);
+ }
+
+ public void onScreenTurnedOn() throws RemoteException {
+ updateScreenTurningOn(false);
+ }
+
@Override
public void executeMessage(Message message) {
if (mDetached.get()) {
@@ -2384,6 +2498,13 @@ public abstract class WallpaperService extends Service {
+ ": " + message.arg1);
mEngine.doVisibilityChanged(message.arg1 != 0);
break;
+ case MSG_UPDATE_SCREEN_TURNING_ON:
+ if (DEBUG) {
+ Log.v(TAG,
+ message.arg1 != 0 ? "Screen turning on" : "Screen turned on");
+ }
+ mEngine.onScreenTurningOnChanged(/* isScreenTurningOn= */ message.arg1 != 0);
+ break;
case MSG_WALLPAPER_OFFSETS: {
mEngine.doOffsetsChanged(true);
} break;
@@ -2474,6 +2595,9 @@ public abstract class WallpaperService extends Service {
@Override
public void onCreate() {
Trace.beginSection("WPMS.onCreate");
+ mBackgroundThread = new HandlerThread("DefaultWallpaperLocalColorExtractor");
+ mBackgroundThread.start();
+ mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
super.onCreate();
Trace.endSection();
}
@@ -2486,6 +2610,7 @@ public abstract class WallpaperService extends Service {
mActiveEngines.get(i).detach();
}
mActiveEngines.clear();
+ mBackgroundThread.quitSafely();
Trace.endSection();
}
diff --git a/core/java/android/text/TextShaper.java b/core/java/android/text/TextShaper.java
index a1d6cc8e283a15934671d28c51d89c125b747882..6da0b63dbc1f3124721eb73990111eadbf109ab7 100644
--- a/core/java/android/text/TextShaper.java
+++ b/core/java/android/text/TextShaper.java
@@ -173,7 +173,7 @@ public class TextShaper {
private TextShaper() {}
/**
- * An consumer interface for accepting text shape result.
+ * A consumer interface for accepting text shape result.
*/
public interface GlyphsConsumer {
/**
diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java
index b7873b73cb28ace5a5689b08d23b12ed139ee6ee..61f29a40ff501c49bb2948c2df4804c2f316a01d 100644
--- a/core/java/android/util/SparseSetArray.java
+++ b/core/java/android/util/SparseSetArray.java
@@ -139,4 +139,9 @@ public class SparseSetArray {
public T valueAt(int intIndex, int valueIndex) {
return mData.valueAt(intIndex).valueAt(valueIndex);
}
+
+ /** @return The set of values for key at position {@code intIndex}. */
+ public ArraySet valuesAt(int intIndex) {
+ return mData.valueAt(intIndex);
+ }
}
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index eb467e0dcc38b138cd95f9c0b7d5db2de7aa86cb..992a5861e07707dc06a6fb1a550dbabd90005328 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -74,6 +74,11 @@ public class ApkSignatureSchemeV2Verifier {
private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+ /**
+ * The maximum number of signers supported by the v2 APK signature scheme.
+ */
+ private static final int MAX_V2_SIGNERS = 10;
+
/**
* Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
*
@@ -183,6 +188,11 @@ public class ApkSignatureSchemeV2Verifier {
}
while (signers.hasRemaining()) {
signerCount++;
+ if (signerCount > MAX_V2_SIGNERS) {
+ throw new SecurityException(
+ "APK Signature Scheme v2 only supports a maximum of " + MAX_V2_SIGNERS
+ + " signers");
+ }
try {
ByteBuffer signer = getLengthPrefixedSlice(signers);
X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory);
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
index 45254908c5c967400cfeb77b3f3f873f72ae9571..a6aca330d323ebf85f74d6aaa59ccc1894fbb91f 100644
--- a/core/java/android/util/jar/StrictJarVerifier.java
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -78,6 +78,11 @@ class StrictJarVerifier {
"SHA1",
};
+ /**
+ * The maximum number of signers supported by the JAR signature scheme.
+ */
+ private static final int MAX_JAR_SIGNERS = 10;
+
private final String jarName;
private final StrictJarManifest manifest;
private final HashMap metaEntries;
@@ -293,10 +298,16 @@ class StrictJarVerifier {
return false;
}
+ int signerCount = 0;
Iterator it = metaEntries.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
+ if (++signerCount > MAX_JAR_SIGNERS) {
+ throw new SecurityException(
+ "APK Signature Scheme v1 only supports a maximum of " + MAX_JAR_SIGNERS
+ + " signers");
+ }
verifyCertificate(key);
it.remove();
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 52d222b19b6ac1f6a0b72315041ca73cf3ea891d..f85f9067e347676a3137207187d927cff27ff2a4 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1599,6 +1599,21 @@ public final class Display {
}
}
+ /**
+ * Returns the committed state of the display.
+ *
+ * @return The latest committed display state, such as {@link #STATE_ON}. The display state
+ * {@link Display#getState()} is set as committed only after power state changes finish.
+ *
+ * @hide
+ */
+ public int getCommittedState() {
+ synchronized (mLock) {
+ updateDisplayInfoLocked();
+ return mIsValid ? mDisplayInfo.committedState : STATE_UNKNOWN;
+ }
+ }
+
/**
* Returns true if the specified UID has access to this display.
* @hide
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 12ce8ee5e0ad0e81f6acc3bb53eca27531f568e5..f65a69a8e2bc182870339de8991ce1bb23ff7db5 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -252,6 +252,12 @@ public final class DisplayInfo implements Parcelable {
*/
public int state;
+ /**
+ * The current committed state of the display. For example, this becomes
+ * {@link android.view.Display#STATE_ON} only after the power state ON is fully committed.
+ */
+ public int committedState;
+
/**
* The UID of the application that owns this display, or zero if it is owned by the system.
*
@@ -380,6 +386,7 @@ public final class DisplayInfo implements Parcelable {
&& appVsyncOffsetNanos == other.appVsyncOffsetNanos
&& presentationDeadlineNanos == other.presentationDeadlineNanos
&& state == other.state
+ && committedState == other.committedState
&& ownerUid == other.ownerUid
&& Objects.equals(ownerPackageName, other.ownerPackageName)
&& removeMode == other.removeMode
@@ -431,6 +438,7 @@ public final class DisplayInfo implements Parcelable {
appVsyncOffsetNanos = other.appVsyncOffsetNanos;
presentationDeadlineNanos = other.presentationDeadlineNanos;
state = other.state;
+ committedState = other.committedState;
ownerUid = other.ownerUid;
ownerPackageName = other.ownerPackageName;
removeMode = other.removeMode;
@@ -482,6 +490,7 @@ public final class DisplayInfo implements Parcelable {
appVsyncOffsetNanos = source.readLong();
presentationDeadlineNanos = source.readLong();
state = source.readInt();
+ committedState = source.readInt();
ownerUid = source.readInt();
ownerPackageName = source.readString8();
uniqueId = source.readString8();
@@ -538,6 +547,7 @@ public final class DisplayInfo implements Parcelable {
dest.writeLong(appVsyncOffsetNanos);
dest.writeLong(presentationDeadlineNanos);
dest.writeInt(state);
+ dest.writeInt(committedState);
dest.writeInt(ownerUid);
dest.writeString8(ownerPackageName);
dest.writeString8(uniqueId);
@@ -761,6 +771,8 @@ public final class DisplayInfo implements Parcelable {
sb.append(rotation);
sb.append(", state ");
sb.append(Display.stateToString(state));
+ sb.append(", committedState ");
+ sb.append(Display.stateToString(committedState));
if (Process.myUid() != Process.SYSTEM_UID) {
sb.append("}");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 06e96ef58cdaaa17e3a79e21504edc7a89f689ec..483c39d98ae16b20cf1ce7cbf9b8ab0fb90bf7ea 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,6 +17,7 @@
package android.view;
import static android.content.res.Resources.ID_NULL;
+import static android.os.Trace.TRACE_TAG_APP;
import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
@@ -984,6 +985,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
private static boolean sAcceptZeroSizeDragShadow;
+ /**
+ * When true, measure and layout passes of all the newly attached views will be logged with
+ * {@link Trace}, so we can better debug jank due to complex view hierarchies.
+ */
+ private static boolean sTraceLayoutSteps;
+
+ /**
+ * When not null, emits a {@link Trace} instant event and the stacktrace every time a relayout
+ * of a class having this name happens.
+ */
+ private static String sTraceRequestLayoutClass;
+
+ /** Used to avoid computing the full strings each time when layout tracing is enabled. */
+ @Nullable
+ private ViewTraversalTracingStrings mTracingStrings;
+
/**
* Prior to R, {@link #dispatchApplyWindowInsets} had an issue:
*
The modified insets changed by {@link #onApplyWindowInsets} were passed to the
@@ -3532,6 +3549,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* 1 PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE
* 1 PFLAG4_DRAG_A11Y_STARTED
* 1 PFLAG4_AUTO_HANDWRITING_INITIATION_ENABLED
+ * 1 PFLAG4_TRAVERSAL_TRACING_ENABLED
+ * 1 PFLAG4_RELAYOUT_TRACING_ENABLED
* |-------|-------|-------|-------|
*/
@@ -3612,6 +3631,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Indicates that the view enables auto handwriting initiation.
*/
private static final int PFLAG4_AUTO_HANDWRITING_ENABLED = 0x000010000;
+
+ /**
+ * When set, measure and layout passes of this view will be logged with {@link Trace}, so we
+ * can better debug jank due to complex view hierarchies.
+ */
+ private static final int PFLAG4_TRAVERSAL_TRACING_ENABLED = 0x000040000;
+
+ /**
+ * When set, emits a {@link Trace} instant event and stacktrace every time a requestLayout of
+ * this class happens.
+ */
+ private static final int PFLAG4_RELAYOUT_TRACING_ENABLED = 0x000080000;
+
/* End of masks for mPrivateFlags4 */
/** @hide */
@@ -6537,6 +6569,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
out.append(mRight);
out.append(',');
out.append(mBottom);
+ appendId(out);
+ if (mAutofillId != null) {
+ out.append(" aid="); out.append(mAutofillId);
+ }
+ out.append("}");
+ return out.toString();
+ }
+
+ void appendId(StringBuilder out) {
final int id = getId();
if (id != NO_ID) {
out.append(" #");
@@ -6568,11 +6609,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
}
- if (mAutofillId != null) {
- out.append(" aid="); out.append(mAutofillId);
- }
- out.append("}");
- return out.toString();
}
/**
@@ -8738,7 +8774,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
@UnsupportedAppUsage
- public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ @TestApi
+ public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8746,6 +8783,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
+ // If "Sandboxing View Bounds APIs" override is enabled, applyViewBoundsSandboxingIfNeeded
+ // will sandbox outRect within window bounds.
+ mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -15558,7 +15598,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
@UnsupportedAppUsage
- public void getWindowDisplayFrame(Rect outRect) {
+ @TestApi
+ public void getWindowDisplayFrame(@NonNull Rect outRect) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
return;
@@ -20775,6 +20816,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (isFocused()) {
notifyFocusChangeToImeFocusController(true /* hasFocus */);
}
+
+ if (sTraceLayoutSteps) {
+ setTraversalTracingEnabled(true);
+ }
+ if (sTraceRequestLayoutClass != null
+ && sTraceRequestLayoutClass.equals(getClass().getSimpleName())) {
+ setRelayoutTracingEnabled(true);
+ }
}
/**
@@ -23674,6 +23723,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
+ /**
+ * Enable measure/layout debugging on traces.
+ *
+ * @see Trace
+ * @hide
+ */
+ public static void setTraceLayoutSteps(boolean traceLayoutSteps) {
+ sTraceLayoutSteps = traceLayoutSteps;
+ }
+
+ /**
+ * Enable request layout tracing classes with {@code s} simple name.
+ *
+ * When set, a {@link Trace} instant event and a log with the stacktrace is emitted every
+ * time a requestLayout of a class matching {@code s} name happens.
+ * This applies only to views attached from this point onwards.
+ *
+ * @see Trace#instant(long, String)
+ * @hide
+ */
+ public static void setTracedRequestLayoutClassClass(String s) {
+ sTraceRequestLayoutClass = s;
+ }
+
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
@@ -23708,7 +23781,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
+ if (isTraversalTracingEnabled()) {
+ Trace.beginSection(mTracingStrings.onMeasureBeforeLayout);
+ }
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
+ if (isTraversalTracingEnabled()) {
+ Trace.endSection();
+ }
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
@@ -23721,7 +23800,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
+ if (isTraversalTracingEnabled()) {
+ Trace.beginSection(mTracingStrings.onLayout);
+ }
onLayout(changed, l, t, r, b);
+ if (isTraversalTracingEnabled()) {
+ Trace.endSection();
+ }
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
@@ -25714,6 +25799,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (info != null) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
+ // If OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS override is enabled,
+ // applyViewLocationSandboxingIfNeeded sandboxes outLocation within window bounds.
+ info.mViewRootImpl.applyViewLocationSandboxingIfNeeded(outLocation);
}
}
@@ -26278,6 +26366,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return (viewRoot != null && viewRoot.isInLayout());
}
+ /** To be used only for debugging purposes. */
+ private void printStackStrace(String name) {
+ Log.d(VIEW_LOG_TAG, "---- ST:" + name);
+
+ StringBuilder sb = new StringBuilder();
+ StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
+ int startIndex = 1;
+ int endIndex = Math.min(stackTraceElements.length, startIndex + 20); // max 20 entries.
+ for (int i = startIndex; i < endIndex; i++) {
+ StackTraceElement s = stackTraceElements[i];
+ sb.append(s.getMethodName())
+ .append("(")
+ .append(s.getFileName())
+ .append(":")
+ .append(s.getLineNumber())
+ .append(") <- ");
+ }
+ Log.d(VIEW_LOG_TAG, name + ": " + sb);
+ }
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
@@ -26291,6 +26398,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@CallSuper
public void requestLayout() {
+ if (isRelayoutTracingEnabled()) {
+ Trace.instantForTrack(TRACE_TAG_APP, "requestLayoutTracing",
+ mTracingStrings.classSimpleName);
+ printStackStrace(mTracingStrings.requestLayoutStacktracePrefix);
+ }
+
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
@@ -26384,8 +26497,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
+ if (isTraversalTracingEnabled()) {
+ Trace.beginSection(mTracingStrings.onMeasure);
+ }
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (isTraversalTracingEnabled()) {
+ Trace.endSection();
+ }
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
@@ -31555,6 +31674,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
== PFLAG4_AUTO_HANDWRITING_ENABLED;
}
+ private void setTraversalTracingEnabled(boolean enabled) {
+ if (enabled) {
+ if (mTracingStrings == null) {
+ mTracingStrings = new ViewTraversalTracingStrings(this);
+ }
+ mPrivateFlags4 |= PFLAG4_TRAVERSAL_TRACING_ENABLED;
+ } else {
+ mPrivateFlags4 &= ~PFLAG4_TRAVERSAL_TRACING_ENABLED;
+ }
+ }
+
+ private boolean isTraversalTracingEnabled() {
+ return (mPrivateFlags4 & PFLAG4_TRAVERSAL_TRACING_ENABLED)
+ == PFLAG4_TRAVERSAL_TRACING_ENABLED;
+ }
+
+ private void setRelayoutTracingEnabled(boolean enabled) {
+ if (enabled) {
+ if (mTracingStrings == null) {
+ mTracingStrings = new ViewTraversalTracingStrings(this);
+ }
+ mPrivateFlags4 |= PFLAG4_RELAYOUT_TRACING_ENABLED;
+ } else {
+ mPrivateFlags4 &= ~PFLAG4_RELAYOUT_TRACING_ENABLED;
+ }
+ }
+
+ private boolean isRelayoutTracingEnabled() {
+ return (mPrivateFlags4 & PFLAG4_RELAYOUT_TRACING_ENABLED)
+ == PFLAG4_RELAYOUT_TRACING_ENABLED;
+ }
+
/**
* Collects a {@link ViewTranslationRequest} which represents the content to be translated in
* the view.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5f04d5846977194d7b45a7df3fc1a83d79bb4373..6feb899745dcaeaa70812d97efc55a631de4a8e0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
@@ -73,15 +74,16 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CO
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -94,12 +96,14 @@ import android.animation.LayoutTransition;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Size;
import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -711,6 +715,7 @@ public final class ViewRootImpl implements ViewParent,
// These are accessed by multiple threads.
final Rect mWinFrame; // frame given by window manager.
+ private final Rect mLastLayoutFrame;
Rect mOverrideInsetsFrame;
final Rect mPendingBackDropFrame = new Rect();
@@ -870,6 +875,15 @@ public final class ViewRootImpl implements ViewParent,
private boolean mRelayoutRequested;
+ /**
+ * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getLocationOnScreen(int[])},
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}
+ * within Activity bounds is enabled for the current application.
+ */
+ private final boolean mViewBoundsSandboxingEnabled;
+
private int mLastTransformHint = Integer.MIN_VALUE;
/**
@@ -931,6 +945,7 @@ public final class ViewRootImpl implements ViewParent,
mHeight = -1;
mDirty = new Rect();
mWinFrame = new Rect();
+ mLastLayoutFrame = new Rect();
mWindow = new W(this);
mLeashToken = new Binder();
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
@@ -956,6 +971,8 @@ public final class ViewRootImpl implements ViewParent,
mHandwritingInitiator = new HandwritingInitiator(mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
+ mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
+
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -1112,6 +1129,8 @@ public final class ViewRootImpl implements ViewParent,
// Update the last resource config in case the resource configuration was changed while
// activity relaunched.
updateLastConfigurationFromResources(getConfiguration());
+ // Make sure to report the completion of draw for relaunch with preserved window.
+ reportNextDraw("rebuilt");
}
private Configuration getConfiguration() {
@@ -1273,7 +1292,7 @@ public final class ViewRootImpl implements ViewParent,
mTmpFrames.attachedFrame = attachedFrame;
mTmpFrames.compatScale = compatScale[0];
mInvCompatScale = 1f / compatScale[0];
- } catch (RemoteException e) {
+ } catch (RemoteException | RuntimeException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
@@ -1301,7 +1320,7 @@ public final class ViewRootImpl implements ViewParent,
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
mInsetsController.getRequestedVisibilities(), 1f /* compactScale */,
mTmpFrames);
- setFrame(mTmpFrames.frame);
+ setFrame(mTmpFrames.frame, true /* withinRelayout */);
registerBackCallbackOnWindow();
if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
// For apps requesting legacy back behavior, we add a compat callback that
@@ -1389,6 +1408,8 @@ public final class ViewRootImpl implements ViewParent,
listener, listener.data, mHandler, true /*waitForPresentTime*/);
mAttachInfo.mThreadedRenderer.addObserver(mHardwareRendererObserver);
}
+ // Update unbuffered request when set the root view.
+ mUnbufferedInputSource = mView.mUnbufferedInputSource;
}
view.assignParent(this);
@@ -1825,7 +1846,7 @@ public final class ViewRootImpl implements ViewParent,
onMovedToDisplay(displayId, mLastConfigurationFromResources);
}
- setFrame(frame);
+ setFrame(frame, false /* withinRelayout */);
mTmpFrames.displayFrame.set(displayFrame);
if (mTmpFrames.attachedFrame != null && attachedFrame != null) {
mTmpFrames.attachedFrame.set(attachedFrame);
@@ -2768,7 +2789,7 @@ public final class ViewRootImpl implements ViewParent,
* TODO(b/260382739): Apply this to all windows.
*/
private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
- return lp.type == TYPE_NOTIFICATION_SHADE;
+ return (lp.privateFlags & PRIVATE_FLAG_OPTIMIZE_MEASURE) != 0;
}
private Rect getWindowBoundsInsetSystemBars() {
@@ -5741,7 +5762,7 @@ public final class ViewRootImpl implements ViewParent,
mTmpFrames.frame.right = l + w;
mTmpFrames.frame.top = t;
mTmpFrames.frame.bottom = t + h;
- setFrame(mTmpFrames.frame);
+ setFrame(mTmpFrames.frame, false /* withinRelayout */);
maybeHandleWindowMove(mWinFrame);
}
break;
@@ -8211,7 +8232,7 @@ public final class ViewRootImpl implements ViewParent,
// If the position and the size of the frame are both changed, it will trigger a BLAST
// sync, and we still need to call relayout to obtain the syncSeqId. Otherwise, we just
// need to send attributes via relayoutAsync.
- final Rect oldFrame = mWinFrame;
+ final Rect oldFrame = mLastLayoutFrame;
final Rect newFrame = mTmpFrames.frame;
final boolean positionChanged =
newFrame.top != oldFrame.top || newFrame.left != oldFrame.left;
@@ -8341,7 +8362,7 @@ public final class ViewRootImpl implements ViewParent,
params.restore();
}
- setFrame(mTmpFrames.frame);
+ setFrame(mTmpFrames.frame, true /* withinRelayout */);
return relayoutResult;
}
@@ -8376,8 +8397,18 @@ public final class ViewRootImpl implements ViewParent,
mIsSurfaceOpaque = opaque;
}
- private void setFrame(Rect frame) {
+ /**
+ * Set the mWinFrame of this window.
+ * @param frame the new frame of this window.
+ * @param withinRelayout {@code true} if this setting is within the relayout, or is the initial
+ * setting. That will make sure in the relayout process, we always compare
+ * the window frame with the last processed window frame.
+ */
+ private void setFrame(Rect frame, boolean withinRelayout) {
mWinFrame.set(frame);
+ if (withinRelayout) {
+ mLastLayoutFrame.set(frame);
+ }
final WindowConfiguration winConfig = getCompatWindowConfiguration();
mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop()
@@ -8412,6 +8443,9 @@ public final class ViewRootImpl implements ViewParent,
*/
void getDisplayFrame(Rect outFrame) {
outFrame.set(mTmpFrames.displayFrame);
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
}
/**
@@ -8428,6 +8462,69 @@ public final class ViewRootImpl implements ViewParent,
outFrame.top += insets.top;
outFrame.right -= insets.right;
outFrame.bottom -= insets.bottom;
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
+ }
+
+ /**
+ * Offset outRect to make it sandboxed within Window's bounds.
+ *
+ *
This is used by {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.ViewRootImpl#getDisplayFrame} and
+ * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
+ * {@link android.view.ViewDebug#captureLayers} for debugging.
+ */
+ void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ inOutRect.offset(-bounds.left, -bounds.top);
+ }
+ }
+
+ /**
+ * Offset outLocation to make it sandboxed within Window's bounds.
+ *
+ *
This is used by {@link android.view.View#getLocationOnScreen(int[])}
+ */
+ public void applyViewLocationSandboxingIfNeeded(@Size(2) int[] outLocation) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ outLocation[0] -= bounds.left;
+ outLocation[1] -= bounds.top;
+ }
+ }
+
+ private boolean getViewBoundsSandboxingEnabled() {
+ // System dialogs (e.g. ANR) can be created within System process, so handleBindApplication
+ // may be never called. This results into all app compat changes being enabled
+ // (see b/268007823) because AppCompatCallbacks.install() is never called with non-empty
+ // array.
+ // With ActivityThread.isSystem we verify that it is not the system process,
+ // then this CompatChange can take effect.
+ if (ActivityThread.isSystem()
+ || !CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
+ // It is a system process or OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
+ return false;
+ }
+
+ // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
+ try {
+ final List properties = mContext.getPackageManager()
+ .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
+
+ final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
+ if (isOptedOut) {
+ // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
+ return false;
+ }
+ } catch (RuntimeException e) {
+ // remote exception.
+ }
+
+ return true;
}
/**
@@ -8618,6 +8715,8 @@ public final class ViewRootImpl implements ViewParent,
mInsetsController.dump(prefix, writer);
+ mOnBackInvokedDispatcher.dump(prefix, writer);
+
writer.println(prefix + "View Hierarchy:");
dumpViewHierarchy(innerPrefix, writer, mView);
}
@@ -8749,6 +8848,10 @@ public final class ViewRootImpl implements ViewParent,
mAdded = false;
AnimationHandler.removeRequestor(this);
}
+ if (mSyncBufferCallback != null) {
+ mSyncBufferCallback.onBufferReady(null);
+ mSyncBufferCallback = null;
+ }
WindowManagerGlobal.getInstance().doRemoveView(this);
}
@@ -9863,9 +9966,12 @@ public final class ViewRootImpl implements ViewParent,
}
void checkThread() {
- if (mThread != Thread.currentThread()) {
+ Thread current = Thread.currentThread();
+ if (mThread != current) {
throw new CalledFromWrongThreadException(
- "Only the original thread that created a view hierarchy can touch its views.");
+ "Only the original thread that created a view hierarchy can touch its views."
+ + " Expected: " + mThread.getName()
+ + " Calling: " + current.getName());
}
}
diff --git a/core/java/android/view/ViewTraversalTracingStrings.java b/core/java/android/view/ViewTraversalTracingStrings.java
new file mode 100644
index 0000000000000000000000000000000000000000..7dde87bad26e38983ae6dd9805522935cb443d06
--- /dev/null
+++ b/core/java/android/view/ViewTraversalTracingStrings.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Trace;
+
+/**
+ * Keeps and caches strings used to trace {@link View} traversals.
+ *
+ * This is done to avoid expensive computations of them every time, which can improve performance.
+ */
+class ViewTraversalTracingStrings {
+
+ /** {@link Trace} tag used to mark {@link View#onMeasure(int, int)}. */
+ public final String onMeasure;
+
+ /** {@link Trace} tag used to mark {@link View#onLayout(boolean, int, int, int, int)}. */
+ public final String onLayout;
+
+ /** Caches the view simple name to avoid re-computations. */
+ public final String classSimpleName;
+
+ /** Prefix for request layout stacktraces output in logs. */
+ public final String requestLayoutStacktracePrefix;
+
+ /** {@link Trace} tag used to mark {@link View#onMeasure(int, int)} happening before layout. */
+ public final String onMeasureBeforeLayout;
+
+ /**
+ * @param v {@link View} from where to get the class name.
+ */
+ ViewTraversalTracingStrings(View v) {
+ String className = v.getClass().getSimpleName();
+ classSimpleName = className;
+ onMeasureBeforeLayout = getTraceName("onMeasureBeforeLayout", className, v);
+ onMeasure = getTraceName("onMeasure", className, v);
+ onLayout = getTraceName("onLayout", className, v);
+ requestLayoutStacktracePrefix = "requestLayout " + className;
+ }
+
+ private String getTraceName(String sectionName, String className, View v) {
+ StringBuilder out = new StringBuilder();
+ out.append(sectionName);
+ out.append(" ");
+ out.append(className);
+ v.appendId(out);
+ return out.substring(0, Math.min(out.length() - 1, Trace.MAX_SECTION_NAME_LEN - 1));
+ }
+}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 02027e4a39693280de3cfa8f608217ae3d03db9b..293f9082670d7cf33163822657164e8466e2d570 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -823,6 +823,11 @@ public abstract class Window {
/** @hide */
public final void destroy() {
mDestroyed = true;
+ onDestroy();
+ }
+
+ /** @hide */
+ protected void onDestroy() {
}
/** @hide */
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index ed9cb00db290d447eb1c35d3cd11d0f3d24d7b4b..033f7263bfc6e2d4063bcdd20aaa4bec10bbd9ad 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -814,8 +814,8 @@ public interface WindowManager extends ViewManager {
}
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity can be opted-in or opted-out
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app can be opted-in or opted-out
* from the compatibility treatment that avoids {@link
* android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
* ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
@@ -833,17 +833,17 @@ public interface WindowManager extends ViewManager {
*
Camera compatibility force rotation treatment is active for the package.
*
*
- *
Setting this property to {@code false} informs the system that the activity must be
+ *
Setting this property to {@code false} informs the system that the app must be
* opted-out from the compatibility treatment even if the device manufacturer has opted the app
* into the treatment.
*
*
*
* @hide
@@ -852,6 +852,323 @@ public interface WindowManager extends ViewManager {
String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app can be opted-out from the compatibility
+ * treatment that avoids {@link android.app.Activity#setRequestedOrientation} loops. The loop
+ * can be trigerred by ignoreRequestedOrientation display setting enabled on the device or
+ * by the landscape natural orientation of the device.
+ *
+ *
The system could ignore {@link android.app.Activity#setRequestedOrientation}
+ * call from an app if both of the following conditions are true:
+ *
+ *
Activity has requested orientation more than 2 times within 1-second timer
+ *
Activity is not letterboxed for fixed orientation
+ *
+ *
+ *
Setting this property to {@code false} informs the system that the app must be
+ * opted-out from the compatibility treatment even if the device manufacturer has opted the app
+ * into the treatment.
+ *
+ *
Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ *
+ *
+ * @hide
+ */
+ // TODO(b/274924641): Make this public API.
+ String PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED =
+ "android.window.PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that it needs to be opted-out from the
+ * compatibility treatment that sandboxes {@link android.view.View} API.
+ *
+ *
The treatment can be enabled by device manufacturers for applications which misuse
+ * {@link android.view.View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View#getWindowDisplayFrame}
+ * return coordinates as if an activity is positioned in the top-left corner of the screen, with
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * letterbox modes.
+ *
+ *
Setting this property to {@code false} informs the system that the application must be
+ * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
+ * if the device manufacturer has opted the app into the treatment.
+ *
+ *
Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ *
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
+ "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the application can be opted-in or opted-out
+ * from the compatibility treatment that enables sending a fake focus event for unfocused
+ * resumed split screen activities. This is needed because some game engines wait to get
+ * focus before drawing the content of the app which isn't guaranteed by default in multi-window
+ * modes.
+ *
+ *
Device manufacturers can enable this treatment using their discretion on a per-device
+ * basis to improve display compatibility. The treatment also needs to be specifically enabled
+ * on a per-app basis afterwards. This can either be done by device manufacturers or developers.
+ *
+ *
With this property set to {@code true}, the system will apply the treatment only if the
+ * device manufacturer had previously enabled it on the device. A fake focus event will be sent
+ * to the app after it is resumed only if the app is in split-screen.
+ *
+ *
Setting this property to {@code false} informs the system that the activity must be
+ * opted-out from the compatibility treatment even if the device manufacturer has opted the app
+ * into the treatment.
+ *
+ *
If the property remains unset the system will apply the treatment only if it had
+ * previously been enabled both at the device and app level by the device manufacturer.
+ *
+ *
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be excluded from the
+ * camera compatibility force rotation treatment.
+ *
+ *
The camera compatibility treatment aligns orientations of portrait app window and natural
+ * orientation of the device and set opposite to natural orientation for a landscape app
+ * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * viewfinder since this is one of the strongest assumptions that apps make when they implement
+ * camera previews. Since app and natural display orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera and is removed once camera is closed.
+ *
+ *
The camera compatibility can be enabled by device manufacturers on the displays that have
+ * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+ * orientation, see Enhanced letterboxing
+ * for more details).
+ *
+ *
With this property set to {@code true} or unset, the system may apply the force rotation
+ * treatment to fixed orientation activities. Device manufacturers can exclude packages from the
+ * treatment using their discretion to improve display compatibility.
+ *
+ *
With this property set to {@code false}, the system will not apply the force rotation
+ * treatment.
+ *
+ *
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
+ "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be excluded
+ * from the activity "refresh" after the camera compatibility force rotation treatment.
+ *
+ *
The camera compatibility treatment aligns orientations of portrait app window and natural
+ * orientation of the device and set opposite to natural orientation for a landscape app
+ * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * viewfinder since this is one of the strongest assumptions that apps make when they implement
+ * camera previews. Since app and natural display orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera and is removed once camera is closed.
+ *
+ *
Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
+ * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle
+ * (if overridden, see {@link #PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE} for context).
+ * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+ * camera preview and can lead to sideways or stretching issues persisting even after force
+ * rotation.
+ *
+ *
The camera compatibility can be enabled by device manufacturers on the displays that have
+ * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+ * orientation, see Enhanced letterboxing
+ * for more details).
+ *
+ *
With this property set to {@code true} or unset, the system may "refresh" activity after
+ * the force rotation treatment. Device manufacturers can exclude packages from the "refresh"
+ * using their discretion to improve display compatibility.
+ *
+ *
With this property set to {@code false}, the system will not "refresh" activity after the
+ * force rotation treatment.
+ *
+ *
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
+ "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the activity should be or shouldn't be
+ * "refreshed" after the camera compatibility force rotation treatment using "paused ->
+ * resumed" cycle rather than "stopped -> resumed".
+ *
+ *
The camera compatibility treatment aligns orientations of portrait app window and natural
+ * orientation of the device and set opposite to natural orientation for a landscape app
+ * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * viewfinder since this is one of the strongest assumptions that apps make when they implement
+ * camera previews. Since app and natural display orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera and is removed once camera is closed.
+ *
+ *
Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
+ * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle
+ * (if overridden by device manufacturers or using this property). This allows to clear cached
+ * values in apps (e.g., display or camera rotation) that influence camera preview and can lead
+ * to sideways or stretching issues persisting even after force rotation.
+ *
+ *
The camera compatibility can be enabled by device manufacturers on the displays that have
+ * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+ * orientation, see Enhanced letterboxing
+ * for more details).
+ *
+ *
Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed"
+ * cycle using their discretion to improve display compatibility.
+ *
+ *
With this property set to {@code true}, the system will "refresh" activity after the
+ * force rotation treatment using "resumed -> paused -> resumed" cycle.
+ *
+ *
With this property set to {@code false}, the system will not "refresh" activity after the
+ * force rotation treatment using "resumed -> paused -> resumed" cycle even if the device
+ * manufacturer adds the corresponding override.
+ *
+ *
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
+ "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be excluded from the
+ * compatibility override for orientation set by the device manufacturer.
+ *
+ *
With this property set to {@code true} or unset, device manufacturers can override
+ * orientation for the app using their discretion to improve display compatibility.
+ *
+ *
With this property set to {@code false}, device manufactured per-app override for
+ * orientation won't be applied.
+ *
+ *
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
+ "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be opted-out from the
+ * compatibility override that fixes display orientation to landscape natural orientation when
+ * an activity is fullscreen.
+ *
+ *
When this compat override is enabled and while display is fixed to the landscape natural
+ * orientation, the orientation requested by the activity will be still respected by bounds
+ * resolution logic. For instance, if an activity requests portrait orientation, then activity
+ * will appear in the letterbox mode for fixed orientation with the display rotated to the
+ * lanscape natural orientation.
+ *
+ *
The treatment is disabled by default but device manufacturers can enable the treatment
+ * using their discretion to improve display compatibility on the displays that have
+ * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+ * orientation, see Enhanced letterboxing
+ * for more details).
+ *
+ *
With this property set to {@code true} or unset, the system wiil use landscape display
+ * orientation when the following conditions are met:
+ *
+ *
Natural orientation of the display is landscape
+ *
ignoreOrientationRequest display setting is enabled
+ *
Activity is fullscreen.
+ *
Device manufacturer enabled the treatment.
+ *
+ *
+ *
With this property set to {@code false}, device manufactured per-app override for
+ * display orientation won't be applied.
+ *
+ *
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
+ "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
+
/**
* @hide
*/
@@ -2442,6 +2759,15 @@ public interface WindowManager extends ViewManager {
* {@hide} */
public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
+ /**
+ * Flag to indicate that the view hierarchy of the window can only be measured when
+ * necessary. If a window size can be known by the LayoutParams, we can use the size to
+ * relayout window, and we don't have to measure the view hierarchy before laying out the
+ * views. This reduces the chances to perform measure.
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 0x00000200;
+
/**
* Flag that prevents the wallpaper behind the current window from receiving touch events.
*
@@ -2644,6 +2970,7 @@ public interface WindowManager extends ViewManager {
PRIVATE_FLAG_NO_MOVE_ANIMATION,
PRIVATE_FLAG_COMPATIBLE_WINDOW,
PRIVATE_FLAG_SYSTEM_ERROR,
+ PRIVATE_FLAG_OPTIMIZE_MEASURE,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
@@ -2703,6 +3030,10 @@ public interface WindowManager extends ViewManager {
mask = PRIVATE_FLAG_SYSTEM_ERROR,
equals = PRIVATE_FLAG_SYSTEM_ERROR,
name = "SYSTEM_ERROR"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_OPTIMIZE_MEASURE,
+ equals = PRIVATE_FLAG_OPTIMIZE_MEASURE,
+ name = "OPTIMIZE_MEASURE"),
@ViewDebug.FlagToString(
mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 59b5286f6fc54998af960977312052991c98fe83..34c7b8b9889f5e9306afd9c7c92f890b4a422e41 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -82,11 +82,19 @@ public final class ContentCaptureContext implements Parcelable {
@SystemApi
public static final int FLAG_RECONNECTED = 0x4;
+ /**
+ * Flag used to disable flush when receiving a VIEW_TREE_APPEARING event.
+ *
+ * @hide
+ */
+ public static final int FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING = 1 << 3;
+
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_DISABLED_BY_APP,
FLAG_DISABLED_BY_FLAG_SECURE,
- FLAG_RECONNECTED
+ FLAG_RECONNECTED,
+ FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING
})
@Retention(RetentionPolicy.SOURCE)
@interface ContextCreationFlags{}
@@ -252,7 +260,8 @@ public final class ContentCaptureContext implements Parcelable {
* Gets the flags associated with this context.
*
* @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE},
- * {@link #FLAG_DISABLED_BY_APP} and {@link #FLAG_RECONNECTED}.
+ * {@link #FLAG_DISABLED_BY_APP}, {@link #FLAG_RECONNECTED} and {@link
+ * #FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING}.
*
* @hide
*/
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index d067d4bc366b1ccf0b70ec5d25dfa85df272cf00..668351b949c1b5def308c1287f7fcf00f0eebf4e 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -51,6 +51,7 @@ import android.view.WindowManager;
import android.view.contentcapture.ContentCaptureSession.FlushReason;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.SyncResultReceiver;
import java.io.PrintWriter;
@@ -66,8 +67,7 @@ import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
- *
The {@link ContentCaptureManager} provides additional ways for for apps to
- * integrate with the content capture subsystem.
+ *
Provides additional ways for apps to integrate with the content capture subsystem.
*
*
Content capture provides real-time, continuous capture of application activity, display and
* events to an intelligence service that is provided by the Android system. The intelligence
@@ -344,6 +344,14 @@ public final class ContentCaptureManager {
*/
public static final String DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT = "idle_unbind_timeout";
+ /**
+ * Sets to disable flush when receiving a VIEW_TREE_APPEARING event.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING =
+ "disable_flush_for_view_tree_appearing";
+
/** @hide */
@TestApi
public static final int LOGGING_LEVEL_OFF = 0;
@@ -374,6 +382,8 @@ public final class ContentCaptureManager {
public static final int DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS = 1_000;
/** @hide */
public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
+ /** @hide */
+ public static final boolean DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = false;
private final Object mLock = new Object();
@@ -449,6 +459,7 @@ public final class ContentCaptureManager {
mOptions = Objects.requireNonNull(options, "options cannot be null");
ContentCaptureHelper.setLoggingLevel(mOptions.loggingLevel);
+ setFlushViewTreeAppearingEventDisabled(mOptions.disableFlushForViewTreeAppearing);
if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName());
@@ -687,6 +698,38 @@ public final class ContentCaptureManager {
}
}
+ /**
+ * Explicitly sets enable or disable flush for view tree appearing event.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public void setFlushViewTreeAppearingEventDisabled(boolean disabled) {
+ if (sDebug) {
+ Log.d(TAG, "setFlushViewTreeAppearingEventDisabled(): setting to " + disabled);
+ }
+
+ synchronized (mLock) {
+ if (disabled) {
+ mFlags |= ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING;
+ } else {
+ mFlags &= ~ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING;
+ }
+ }
+ }
+
+ /**
+ * Gets whether content capture is needed to flush for view tree appearing event.
+ *
+ * @hide
+ */
+ public boolean getFlushViewTreeAppearingEventDisabled() {
+ synchronized (mLock) {
+ return (mFlags & ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING)
+ != 0;
+ }
+ }
+
/**
* Gets whether content capture is enabled for the given user.
*
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 2134d819943e31c744c8811eff8b886272601d99..bdec1970eda9f86058765762733326c6697db9e9 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -170,6 +170,12 @@ public abstract class ContentCaptureSession implements AutoCloseable {
public static final int FLUSH_REASON_TEXT_CHANGE_TIMEOUT = 6;
/** @hide */
public static final int FLUSH_REASON_SESSION_CONNECTED = 7;
+ /** @hide */
+ public static final int FLUSH_REASON_FORCE_FLUSH = 8;
+ /** @hide */
+ public static final int FLUSH_REASON_VIEW_TREE_APPEARING = 9;
+ /** @hide */
+ public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10;
/** @hide */
@IntDef(prefix = { "FLUSH_REASON_" }, value = {
@@ -179,7 +185,10 @@ public abstract class ContentCaptureSession implements AutoCloseable {
FLUSH_REASON_SESSION_FINISHED,
FLUSH_REASON_IDLE_TIMEOUT,
FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
- FLUSH_REASON_SESSION_CONNECTED
+ FLUSH_REASON_SESSION_CONNECTED,
+ FLUSH_REASON_FORCE_FLUSH,
+ FLUSH_REASON_VIEW_TREE_APPEARING,
+ FLUSH_REASON_VIEW_TREE_APPEARED
})
@Retention(RetentionPolicy.SOURCE)
public @interface FlushReason{}
@@ -614,6 +623,12 @@ public abstract class ContentCaptureSession implements AutoCloseable {
return "TEXT_CHANGE";
case FLUSH_REASON_SESSION_CONNECTED:
return "CONNECTED";
+ case FLUSH_REASON_FORCE_FLUSH:
+ return "FORCE_FLUSH";
+ case FLUSH_REASON_VIEW_TREE_APPEARING:
+ return "VIEW_TREE_APPEARING";
+ case FLUSH_REASON_VIEW_TREE_APPEARED:
+ return "VIEW_TREE_APPEARED";
default:
return "UNKOWN-" + reason;
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 1f5e462d71fd81291d1fb5453d495963285eac59..9848acd8dfcc57409dce3e1bcc4b9a696e9050e5 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -458,6 +458,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
case ContentCaptureEvent.TYPE_SESSION_FINISHED:
flushReason = FLUSH_REASON_SESSION_FINISHED;
break;
+ case ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING:
+ flushReason = FLUSH_REASON_VIEW_TREE_APPEARING;
+ break;
+ case ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED:
+ flushReason = FLUSH_REASON_VIEW_TREE_APPEARED;
+ break;
default:
flushReason = FLUSH_REASON_FULL;
}
@@ -764,7 +770,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
/** Public because is also used by ViewRootImpl */
public void notifyViewTreeEvent(int sessionId, boolean started) {
final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
- mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH));
+ final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled();
+
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, type),
+ disableFlush ? !started : FORCE_FLUSH));
}
void notifySessionResumed(int sessionId) {
diff --git a/core/java/android/webkit/WebResourceError.java b/core/java/android/webkit/WebResourceError.java
index 11f1b6f175669b6f2336b97b15d67621495c7d42..4c874892d57633d97a5187d3982474c6347566ae 100644
--- a/core/java/android/webkit/WebResourceError.java
+++ b/core/java/android/webkit/WebResourceError.java
@@ -19,7 +19,7 @@ package android.webkit;
import android.annotation.SystemApi;
/**
- * Encapsulates information about errors occured during loading of web resources. See
+ * Encapsulates information about errors that occurred during loading of web resources. See
* {@link WebViewClient#onReceivedError(WebView, WebResourceRequest, WebResourceError) WebViewClient.onReceivedError(WebView, WebResourceRequest, WebResourceError)}
*/
public abstract class WebResourceError {
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 1024e2e50c3e2f5e106fd68a8aa2c1c82b8074bc..940b133eb16963912f0559e16b7a178f1373126f 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -18,10 +18,8 @@ package android.window;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-import android.view.RemoteAnimationTarget;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -52,8 +50,6 @@ public class BackEvent implements Parcelable {
@SwipeEdge
private final int mSwipeEdge;
- @Nullable
- private final RemoteAnimationTarget mDepartingAnimationTarget;
/**
* Creates a new {@link BackEvent} instance.
@@ -62,16 +58,12 @@ public class BackEvent implements Parcelable {
* @param touchY Absolute Y location of the touch point of this event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param swipeEdge Indicates which edge the swipe starts from.
- * @param departingAnimationTarget The remote animation target of the departing application
- * window.
*/
- public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
- @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) {
mTouchX = touchX;
mTouchY = touchY;
mProgress = progress;
mSwipeEdge = swipeEdge;
- mDepartingAnimationTarget = departingAnimationTarget;
}
private BackEvent(@NonNull Parcel in) {
@@ -79,7 +71,6 @@ public class BackEvent implements Parcelable {
mTouchY = in.readFloat();
mProgress = in.readFloat();
mSwipeEdge = in.readInt();
- mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
}
public static final Creator CREATOR = new Creator() {
@@ -105,11 +96,24 @@ public class BackEvent implements Parcelable {
dest.writeFloat(mTouchY);
dest.writeFloat(mProgress);
dest.writeInt(mSwipeEdge);
- dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
/**
- * Returns a value between 0 and 1 on how far along the back gesture is.
+ * Returns a value between 0 and 1 on how far along the back gesture is. This value is
+ * driven by the horizontal location of the touch point, and should be used as the fraction to
+ * seek the predictive back animation with. Specifically,
+ *
+ *
The progress is 0 when the touch is at the starting edge of the screen (left or right),
+ * and animation should seek to its start state.
+ *
The progress is approximately 1 when the touch is at the opposite side of the screen,
+ * and animation should seek to its end state. Exact end value may vary depending on
+ * screen size.
+ *
+ *
After the gesture finishes in cancel state, this method keeps getting invoked until the
+ * progress value animates back to 0.
+ *
+ * In-between locations are linearly interpolated based on horizontal distance from the starting
+ * edge and smooth clamped to 1 when the distance exceeds a system-wide threshold.
*/
public float getProgress() {
return mProgress;
@@ -136,16 +140,6 @@ public class BackEvent implements Parcelable {
return mSwipeEdge;
}
- /**
- * Returns the {@link RemoteAnimationTarget} of the top departing application window,
- * or {@code null} if the top window should not be moved for the current type of back
- * destination.
- */
- @Nullable
- public RemoteAnimationTarget getDepartingAnimationTarget() {
- return mDepartingAnimationTarget;
- }
-
@Override
public String toString() {
return "BackEvent{"
@@ -153,7 +147,6 @@ public class BackEvent implements Parcelable {
+ ", mTouchY=" + mTouchY
+ ", mProgress=" + mProgress
+ ", mSwipeEdge" + mSwipeEdge
- + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ "}";
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/core/java/android/window/BackMotionEvent.aidl
similarity index 66%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
rename to core/java/android/window/BackMotionEvent.aidl
index 67733e90526837fb9b32200637d14704b583ff80..7c675c35c073ed2c2ff9f3df53d80ac1f1e433df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
+++ b/core/java/android/window/BackMotionEvent.aidl
@@ -11,15 +11,12 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.keyguard.shared.model
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.milliseconds
+package android.window;
-/** Animation parameters */
-data class AnimationParams(
- val startTime: Duration = 0.milliseconds,
- val duration: Duration,
-)
+/**
+ * @hide
+ */
+parcelable BackMotionEvent;
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..8012a1c26bacaf5ffcd71ac70209b505e16272ff
--- /dev/null
+++ b/core/java/android/window/BackMotionEvent.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.RemoteAnimationTarget;
+
+/**
+ * Object used to report back gesture progress. Holds information about a {@link BackEvent} plus
+ * any {@link RemoteAnimationTarget} the gesture manipulates.
+ *
+ * @see BackEvent
+ * @hide
+ */
+public final class BackMotionEvent implements Parcelable {
+ private final float mTouchX;
+ private final float mTouchY;
+ private final float mProgress;
+
+ @BackEvent.SwipeEdge
+ private final int mSwipeEdge;
+ @Nullable
+ private final RemoteAnimationTarget mDepartingAnimationTarget;
+
+ /**
+ * Creates a new {@link BackMotionEvent} instance.
+ *
+ * @param touchX Absolute X location of the touch point of this event.
+ * @param touchY Absolute Y location of the touch point of this event.
+ * @param progress Value between 0 and 1 on how far along the back gesture is.
+ * @param swipeEdge Indicates which edge the swipe starts from.
+ * @param departingAnimationTarget The remote animation target of the departing
+ * application window.
+ */
+ public BackMotionEvent(float touchX, float touchY, float progress,
+ @BackEvent.SwipeEdge int swipeEdge,
+ @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ mTouchX = touchX;
+ mTouchY = touchY;
+ mProgress = progress;
+ mSwipeEdge = swipeEdge;
+ mDepartingAnimationTarget = departingAnimationTarget;
+ }
+
+ private BackMotionEvent(@NonNull Parcel in) {
+ mTouchX = in.readFloat();
+ mTouchY = in.readFloat();
+ mProgress = in.readFloat();
+ mSwipeEdge = in.readInt();
+ mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
+ }
+
+ @NonNull
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public BackMotionEvent createFromParcel(Parcel in) {
+ return new BackMotionEvent(in);
+ }
+
+ @Override
+ public BackMotionEvent[] newArray(int size) {
+ return new BackMotionEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeFloat(mTouchX);
+ dest.writeFloat(mTouchY);
+ dest.writeFloat(mProgress);
+ dest.writeInt(mSwipeEdge);
+ dest.writeTypedObject(mDepartingAnimationTarget, flags);
+ }
+
+ /**
+ * Returns the progress of a {@link BackEvent}.
+ *
+ * @see BackEvent#getProgress()
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Returns the absolute X location of the touch point.
+ */
+ public float getTouchX() {
+ return mTouchX;
+ }
+
+ /**
+ * Returns the absolute Y location of the touch point.
+ */
+ public float getTouchY() {
+ return mTouchY;
+ }
+
+ /**
+ * Returns the screen edge that the swipe starts from.
+ */
+ @BackEvent.SwipeEdge
+ public int getSwipeEdge() {
+ return mSwipeEdge;
+ }
+
+ /**
+ * Returns the {@link RemoteAnimationTarget} of the top departing application window,
+ * or {@code null} if the top window should not be moved for the current type of back
+ * destination.
+ */
+ @Nullable
+ public RemoteAnimationTarget getDepartingAnimationTarget() {
+ return mDepartingAnimationTarget;
+ }
+
+ @Override
+ public String toString() {
+ return "BackMotionEvent{"
+ + "mTouchX=" + mTouchX
+ + ", mTouchY=" + mTouchY
+ + ", mProgress=" + mProgress
+ + ", mSwipeEdge" + mSwipeEdge
+ + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ + "}";
+ }
+}
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index dd4385c8f50c3c8827a0e15f0496dbbe1b1fe869..b22f967e9e2a9eb19d39fe64d7945bea8acabfbc 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -16,8 +16,10 @@
package android.window;
+import android.annotation.NonNull;
import android.util.FloatProperty;
+import com.android.internal.dynamicanimation.animation.DynamicAnimation;
import com.android.internal.dynamicanimation.animation.SpringAnimation;
import com.android.internal.dynamicanimation.animation.SpringForce;
@@ -40,7 +42,7 @@ public class BackProgressAnimator {
private final SpringAnimation mSpring;
private ProgressCallback mCallback;
private float mProgress = 0;
- private BackEvent mLastBackEvent;
+ private BackMotionEvent mLastBackEvent;
private boolean mStarted = false;
private void setProgress(float progress) {
@@ -82,9 +84,9 @@ public class BackProgressAnimator {
/**
* Sets a new target position for the back progress.
*
- * @param event the {@link BackEvent} containing the latest target progress.
+ * @param event the {@link BackMotionEvent} containing the latest target progress.
*/
- public void onBackProgressed(BackEvent event) {
+ public void onBackProgressed(BackMotionEvent event) {
if (!mStarted) {
return;
}
@@ -95,11 +97,11 @@ public class BackProgressAnimator {
/**
* Starts the back progress animation.
*
- * @param event the {@link BackEvent} that started the gesture.
+ * @param event the {@link BackMotionEvent} that started the gesture.
* @param callback the back callback to invoke for the gesture. It will receive back progress
* dispatches as the progress animation updates.
*/
- public void onBackStarted(BackEvent event, ProgressCallback callback) {
+ public void onBackStarted(BackMotionEvent event, ProgressCallback callback) {
reset();
mLastBackEvent = event;
mCallback = callback;
@@ -123,14 +125,34 @@ public class BackProgressAnimator {
mProgress = 0;
}
+ /**
+ * Animate the back progress animation from current progress to start position.
+ * This should be called when back is cancelled.
+ *
+ * @param finishCallback the callback to be invoked when the progress is reach to 0.
+ */
+ public void onBackCancelled(@NonNull Runnable finishCallback) {
+ final DynamicAnimation.OnAnimationEndListener listener =
+ new DynamicAnimation.OnAnimationEndListener() {
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+ float velocity) {
+ mSpring.removeEndListener(this);
+ finishCallback.run();
+ reset();
+ }
+ };
+ mSpring.addEndListener(listener);
+ mSpring.animateToFinalPosition(0);
+ }
+
private void updateProgressValue(float progress) {
if (mLastBackEvent == null || mCallback == null || !mStarted) {
return;
}
mCallback.onProgressUpdate(
new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
- progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(),
- mLastBackEvent.getDepartingAnimationTarget()));
+ progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge()));
}
}
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index 6af8ddda3a62e30ca24d4aa301c05a8e4cc16279..159c0e8afed00b4378528fcd10a8655e67295a33 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -17,7 +17,7 @@
package android.window;
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
/**
* Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager
@@ -30,18 +30,19 @@ oneway interface IOnBackInvokedCallback {
* Called when a back gesture has been started, or back button has been pressed down.
* Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}.
*
- * @param backEvent The {@link BackEvent} containing information about the touch or button press.
+ * @param backMotionEvent The {@link BackMotionEvent} containing information about the touch
+ * or button press.
*/
- void onBackStarted(in BackEvent backEvent);
+ void onBackStarted(in BackMotionEvent backMotionEvent);
/**
* Called on back gesture progress.
* Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
*
- * @param backEvent The {@link BackEvent} containing information about the latest touch point
- * and the progress that the back animation should seek to.
+ * @param backMotionEvent The {@link BackMotionEvent} containing information about the latest
+ * touch point and the progress that the back animation should seek to.
*/
- void onBackProgressed(in BackEvent backEvent);
+ void onBackProgressed(in BackMotionEvent backMotionEvent);
/**
* Called when a back gesture or back button press has been cancelled.
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index e6bb1f64ad861b09eca164fa7a5c9d7f746422ee..e10f7c838c744241e13adb5b677c5d5e9f3485e3 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -40,7 +40,8 @@ interface ITaskOrganizerController {
void unregisterTaskOrganizer(ITaskOrganizer organizer);
/** Creates a persistent root task in WM for a particular windowing-mode. */
- void createRootTask(int displayId, int windowingMode, IBinder launchCookie);
+ void createRootTask(int displayId, int windowingMode, IBinder launchCookie,
+ boolean removeWithTaskOrganizer);
/** Deletes a persistent root task in WM */
boolean deleteRootTask(in WindowContainerToken task);
@@ -72,11 +73,17 @@ interface ITaskOrganizerController {
/**
* Controls whether ignore orientation request logic in {@link
- * com.android.server.wm.DisplayArea} is disabled at runtime.
+ * com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some
+ * requested orientations to others.
*
* @param isDisabled when {@code true}, the system always ignores the value of {@link
* com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app
* requested orientation is respected.
+ * @param fromOrientations The orientations we want to map to the correspondent orientations
+ * in toOrientation.
+ * @param toOrientations The orientations we map to the ones in fromOrientations at the same
+ * index
*/
- void setIsIgnoreOrientationRequestDisabled(boolean isDisabled);
+ void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
+ in int[] fromOrientations, in int[] toOrientations);
}
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index a0bd7f70ca585747482e5c80be62631e2eeec44e..34b75a4788c40c9746df7e869d2c6f44ede00776 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -211,6 +211,12 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
IOnBackInvokedCallback getIOnBackInvokedCallback() {
return mIOnBackInvokedCallback;
}
+
+ @Override
+ public String toString() {
+ return "ImeCallback=ImeOnBackInvokedCallback@" + mId
+ + " Callback=" + mIOnBackInvokedCallback;
+ }
}
/**
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 49acde9dc2953201d20bfa552a91852bebb28ba7..eb3bcaec003a380410c416363a9b813f97759fd1 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -179,16 +179,7 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
return;
}
clearCallbacksOnDispatcher();
- if (actualDispatcher instanceof ProxyOnBackInvokedDispatcher) {
- // We don't want to nest ProxyDispatchers, so if we are given on, we unwrap its
- // actual dispatcher.
- // This can happen when an Activity is recreated but the Window is preserved (e.g.
- // when going from split-screen back to single screen)
- mActualDispatcher =
- ((ProxyOnBackInvokedDispatcher) actualDispatcher).mActualDispatcher;
- } else {
- mActualDispatcher = actualDispatcher;
- }
+ mActualDispatcher = actualDispatcher;
transferCallbacksToDispatcher();
}
}
diff --git a/core/java/android/window/TaskFragmentAnimationParams.java b/core/java/android/window/TaskFragmentAnimationParams.java
index 12ad914986264d38f55cebd58c6cbec0a436122a..c8f632707966b03cefe6c163f646d77c6cd2a38c 100644
--- a/core/java/android/window/TaskFragmentAnimationParams.java
+++ b/core/java/android/window/TaskFragmentAnimationParams.java
@@ -33,6 +33,13 @@ public final class TaskFragmentAnimationParams implements Parcelable {
public static final TaskFragmentAnimationParams DEFAULT =
new TaskFragmentAnimationParams.Builder().build();
+ /**
+ * The default value for animation background color, which means to use the theme window
+ * background color.
+ */
+ @ColorInt
+ public static final int DEFAULT_ANIMATION_BACKGROUND_COLOR = 0;
+
@ColorInt
private final int mAnimationBackgroundColor;
@@ -104,12 +111,13 @@ public final class TaskFragmentAnimationParams implements Parcelable {
public static final class Builder {
@ColorInt
- private int mAnimationBackgroundColor = 0;
+ private int mAnimationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR;
/**
* Sets the {@link ColorInt} to use for the background during the animation with this
* TaskFragment if the animation requires a background. The default value is
- * {@code 0}, which is to use the theme window background.
+ * {@link #DEFAULT_ANIMATION_BACKGROUND_COLOR}, which is to use the theme window background
+ * color.
*
* @param color a packed color int, {@code AARRGGBB}, for the animation background color.
* @return this {@link Builder}.
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index c9ddf92d37407cbc90dd0e7a8536bd1d03e8ca3e..203d79aad7a37327b83893d9610064387133557e 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -71,20 +71,42 @@ public final class TaskFragmentCreationParams implements Parcelable {
*
* This is needed in case we need to launch a placeholder Activity to split below a transparent
* always-expand Activity.
+ *
+ * This should not be used with {@link #mPairedActivityToken}.
*/
@Nullable
private final IBinder mPairedPrimaryFragmentToken;
+ /**
+ * The Activity token to place the new TaskFragment on top of.
+ * When it is set, the new TaskFragment will be positioned right above the target Activity.
+ * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
+ *
+ * This is needed in case we need to place an Activity into TaskFragment to launch placeholder
+ * below a transparent always-expand Activity, or when there is another Intent being started in
+ * a TaskFragment above.
+ *
+ * This should not be used with {@link #mPairedPrimaryFragmentToken}.
+ */
+ @Nullable
+ private final IBinder mPairedActivityToken;
+
private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialBounds,
- @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken) {
+ @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
+ @Nullable IBinder pairedActivityToken) {
+ if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
+ throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ + " pairedActivityToken should not be set at the same time.");
+ }
mOrganizer = organizer;
mFragmentToken = fragmentToken;
mOwnerToken = ownerToken;
mInitialBounds.set(initialBounds);
mWindowingMode = windowingMode;
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
+ mPairedActivityToken = pairedActivityToken;
}
@NonNull
@@ -121,6 +143,15 @@ public final class TaskFragmentCreationParams implements Parcelable {
return mPairedPrimaryFragmentToken;
}
+ /**
+ * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+ * @hide
+ */
+ @Nullable
+ public IBinder getPairedActivityToken() {
+ return mPairedActivityToken;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
@@ -128,6 +159,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
mInitialBounds.readFromParcel(in);
mWindowingMode = in.readInt();
mPairedPrimaryFragmentToken = in.readStrongBinder();
+ mPairedActivityToken = in.readStrongBinder();
}
/** @hide */
@@ -139,6 +171,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
mInitialBounds.writeToParcel(dest, flags);
dest.writeInt(mWindowingMode);
dest.writeStrongBinder(mPairedPrimaryFragmentToken);
+ dest.writeStrongBinder(mPairedActivityToken);
}
@NonNull
@@ -164,6 +197,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
+ " initialBounds=" + mInitialBounds
+ " windowingMode=" + mWindowingMode
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ + " pairedActivityToken=" + mPairedActivityToken
+ "}";
}
@@ -194,6 +228,9 @@ public final class TaskFragmentCreationParams implements Parcelable {
@Nullable
private IBinder mPairedPrimaryFragmentToken;
+ @Nullable
+ private IBinder mPairedActivityToken;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -224,6 +261,8 @@ public final class TaskFragmentCreationParams implements Parcelable {
* This is needed in case we need to launch a placeholder Activity to split below a
* transparent always-expand Activity.
*
+ * This should not be used with {@link #setPairedActivityToken}.
+ *
* TODO(b/232476698): remove the hide with adding CTS for this in next release.
* @hide
*/
@@ -233,11 +272,32 @@ public final class TaskFragmentCreationParams implements Parcelable {
return this;
}
+ /**
+ * Sets the Activity token to place the new TaskFragment on top of.
+ * When it is set, the new TaskFragment will be positioned right above the target Activity.
+ * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
+ *
+ * This is needed in case we need to place an Activity into TaskFragment to launch
+ * placeholder below a transparent always-expand Activity, or when there is another Intent
+ * being started in a TaskFragment above.
+ *
+ * This should not be used with {@link #setPairedPrimaryFragmentToken}.
+ *
+ * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+ * @hide
+ */
+ @NonNull
+ public Builder setPairedActivityToken(@Nullable IBinder activityToken) {
+ mPairedActivityToken = activityToken;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
- mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken);
+ mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken,
+ mPairedActivityToken);
}
}
}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index bffd4e437dfac18f8deca7c87e28da867d2784c1..3aa9941d24b79f895948ce7a06f20e2a7586074f 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -152,17 +152,33 @@ public class TaskOrganizer extends WindowOrganizer {
* @param windowingMode Windowing mode to put the root task in.
* @param launchCookie Launch cookie to associate with the task so that is can be identified
* when the {@link ITaskOrganizer#onTaskAppeared} callback is called.
+ * @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed.
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- @Nullable
- public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) {
+ public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie,
+ boolean removeWithTaskOrganizer) {
try {
- mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie);
+ mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie,
+ removeWithTaskOrganizer);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ * @param displayId The display to create the root task on.
+ * @param windowingMode Windowing mode to put the root task in.
+ * @param launchCookie Launch cookie to associate with the task so that is can be identified
+ * when the {@link ITaskOrganizer#onTaskAppeared} callback is called.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ @Nullable
+ public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) {
+ createRootTask(displayId, windowingMode, launchCookie, false /* removeWithTaskOrganizer */);
+ }
+
/** Deletes a persistent root task in WM */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
public boolean deleteRootTask(@NonNull WindowContainerToken task) {
@@ -254,17 +270,24 @@ public class TaskOrganizer extends WindowOrganizer {
/**
* Controls whether ignore orientation request logic in {@link
- * com.android.server.wm.DisplayArea} is disabled at runtime.
+ * com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some
+ * requested orientation to others.
*
- * @param isDisabled when {@code true}, the system always ignores the value of {@link
- * com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app
- * requested orientation is respected.
+ * @param isIgnoreOrientationRequestDisabled when {@code true}, the system always ignores the
+ * value of {@link com.android.server.wm.DisplayArea#getIgnoreOrientationRequest}
+ * and app requested orientation is respected.
+ * @param fromOrientations The orientations we want to map to the correspondent orientations
+ * in toOrientation.
+ * @param toOrientations The orientations we map to the ones in fromOrientations at the same
+ * index
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- public void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) {
+ public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
+ @Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
try {
- mTaskOrganizerController.setIsIgnoreOrientationRequestDisabled(isDisabled);
+ mTaskOrganizerController.setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled,
+ fromOrientations, toOrientations);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 9aba5a49129575d354ffa7c59281e07fd39b81ea..257c225e3386e232e8739350b8c0c0c08df3c869 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -121,6 +121,19 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
+ /**
+ * Sets the densityDpi value in the configuration for the given container.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setDensityDpi(@NonNull WindowContainerToken container,
+ int densityDpi) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mConfiguration.densityDpi = densityDpi;
+ chg.mConfigSetMask |= ActivityInfo.CONFIG_DENSITY;
+ return this;
+ }
+
/**
* Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task
* has finished the enter animation with the given bounds.
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index fda39c14dac74c4af23c9d5825456a80af8517aa..caec4bca9469b1d76e9de995b0852b3e33577ba0 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -27,6 +27,7 @@ import android.util.Log;
import android.view.IWindow;
import android.view.IWindowSession;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
@@ -221,6 +222,26 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@NonNull
private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ /**
+ * Dump information about this WindowOnBackInvokedDispatcher
+ * @param prefix the prefix that will be prepended to each line of the produced output
+ * @param writer the writer that will receive the resulting text
+ */
+ public void dump(String prefix, PrintWriter writer) {
+ String innerPrefix = prefix + " ";
+ writer.println(prefix + "WindowOnBackDispatcher:");
+ if (mAllCallbacks.isEmpty()) {
+ writer.println(prefix + "");
+ return;
+ }
+
+ writer.println(innerPrefix + "Top Callback: " + getTopCallback());
+ writer.println(innerPrefix + "Callbacks: ");
+ mAllCallbacks.forEach((callback, priority) -> {
+ writer.println(innerPrefix + " Callback: " + callback + " Priority=" + priority);
+ });
+ }
+
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference mCallback;
@@ -229,19 +250,21 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
mProgressAnimator.onBackStarted(backEvent, event ->
callback.onBackProgressed(event));
- callback.onBackStarted(backEvent);
+ callback.onBackStarted(new BackEvent(
+ backEvent.getTouchX(), backEvent.getTouchY(),
+ backEvent.getProgress(), backEvent.getSwipeEdge()));
}
});
}
@Override
- public void onBackProgressed(BackEvent backEvent) {
+ public void onBackProgressed(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
@@ -253,11 +276,12 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void onBackCancelled() {
Handler.getMain().post(() -> {
- mProgressAnimator.reset();
- final OnBackAnimationCallback callback = getBackAnimationCallback();
- if (callback != null) {
- callback.onBackCancelled();
- }
+ mProgressAnimator.onBackCancelled(() -> {
+ final OnBackAnimationCallback callback = getBackAnimationCallback();
+ if (callback != null) {
+ callback.onBackCancelled();
+ }
+ });
});
}
@@ -267,6 +291,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
mProgressAnimator.reset();
final OnBackInvokedCallback callback = mCallback.get();
if (callback == null) {
+ Log.d(TAG, "Trying to call onBackInvoked() on a null callback reference.");
return;
}
callback.onBackInvoked();
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 43be0312245e8abfd52e00f661938a92d6f7a78e..1b901f5fd09c5d8a0d62fc88ea31723170c81fca 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -21,6 +21,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
+import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
import static com.android.internal.util.ArrayUtils.convertToLongArray;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -147,11 +148,13 @@ public class AccessibilityShortcutController {
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
"1" /* Value to enable */, "0" /* Value to disable */,
R.string.color_correction_feature_name));
- featuresMap.put(ONE_HANDED_COMPONENT_NAME,
- new ToggleableFrameworkFeatureInfo(
- Settings.Secure.ONE_HANDED_MODE_ACTIVATED,
- "1" /* Value to enable */, "0" /* Value to disable */,
- R.string.one_handed_mode_feature_name));
+ if (SUPPORT_ONE_HANDED_MODE) {
+ featuresMap.put(ONE_HANDED_COMPONENT_NAME,
+ new ToggleableFrameworkFeatureInfo(
+ Settings.Secure.ONE_HANDED_MODE_ACTIVATED,
+ "1" /* Value to enable */, "0" /* Value to disable */,
+ R.string.one_handed_mode_feature_name));
+ }
featuresMap.put(REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
new ToggleableFrameworkFeatureInfo(
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index fc2c8cca9796bc2d53c55567e84038df16f1a135..2d87745fcadc7277c8194284561cbb54f75cd849 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -25,6 +25,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
+import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityShortcutInfo;
@@ -209,6 +210,7 @@ public final class AccessibilityTargetHelper {
context.getString(R.string.accessibility_magnification_chooser_text),
context.getDrawable(R.drawable.ic_accessibility_magnification),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+ targets.add(magnification);
final ToggleAllowListingFeatureTarget daltonizer =
new ToggleAllowListingFeatureTarget(context,
@@ -219,6 +221,7 @@ public final class AccessibilityTargetHelper {
context.getString(R.string.color_correction_feature_name),
context.getDrawable(R.drawable.ic_accessibility_color_correction),
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
+ targets.add(daltonizer);
final ToggleAllowListingFeatureTarget colorInversion =
new ToggleAllowListingFeatureTarget(context,
@@ -229,16 +232,20 @@ public final class AccessibilityTargetHelper {
context.getString(R.string.color_inversion_feature_name),
context.getDrawable(R.drawable.ic_accessibility_color_inversion),
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+ targets.add(colorInversion);
- final ToggleAllowListingFeatureTarget oneHandedMode =
- new ToggleAllowListingFeatureTarget(context,
- shortcutType,
- isShortcutContained(context, shortcutType,
- ONE_HANDED_COMPONENT_NAME.flattenToString()),
- ONE_HANDED_COMPONENT_NAME.flattenToString(),
- context.getString(R.string.one_handed_mode_feature_name),
- context.getDrawable(R.drawable.ic_accessibility_one_handed),
- Settings.Secure.ONE_HANDED_MODE_ACTIVATED);
+ if (SUPPORT_ONE_HANDED_MODE) {
+ final ToggleAllowListingFeatureTarget oneHandedMode =
+ new ToggleAllowListingFeatureTarget(context,
+ shortcutType,
+ isShortcutContained(context, shortcutType,
+ ONE_HANDED_COMPONENT_NAME.flattenToString()),
+ ONE_HANDED_COMPONENT_NAME.flattenToString(),
+ context.getString(R.string.one_handed_mode_feature_name),
+ context.getDrawable(R.drawable.ic_accessibility_one_handed),
+ Settings.Secure.ONE_HANDED_MODE_ACTIVATED);
+ targets.add(oneHandedMode);
+ }
final ToggleAllowListingFeatureTarget reduceBrightColors =
new ToggleAllowListingFeatureTarget(context,
@@ -249,11 +256,6 @@ public final class AccessibilityTargetHelper {
context.getString(R.string.reduce_bright_colors_feature_name),
context.getDrawable(R.drawable.ic_accessibility_reduce_bright_colors),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
-
- targets.add(magnification);
- targets.add(daltonizer);
- targets.add(colorInversion);
- targets.add(oneHandedMode);
targets.add(reduceBrightColors);
return targets;
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 1fcfe7dd5b6fdb9e26962147382a896f159a9aa2..0a778a6538c34d8bbe97fe452048a9d3f149c0ac 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
+import static android.content.ContentProvider.getUserIdFromUri;
import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
@@ -161,6 +162,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
/**
* The Chooser Activity handles intent resolution specifically for sharing intents -
@@ -1395,7 +1397,7 @@ public class ChooserActivity extends ResolverActivity implements
ImageView previewThumbnailView = contentPreviewLayout.findViewById(
R.id.content_preview_thumbnail);
- if (previewThumbnail == null) {
+ if (!validForContentPreview(previewThumbnail)) {
previewThumbnailView.setVisibility(View.GONE);
} else {
mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
@@ -1425,6 +1427,10 @@ public class ChooserActivity extends ResolverActivity implements
String action = targetIntent.getAction();
if (Intent.ACTION_SEND.equals(action)) {
Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
+ if (!validForContentPreview(uri)) {
+ contentPreviewLayout.setVisibility(View.GONE);
+ return contentPreviewLayout;
+ }
imagePreview.findViewById(R.id.content_preview_image_1_large)
.setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
@@ -1434,7 +1440,7 @@ public class ChooserActivity extends ResolverActivity implements
List uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
List imageUris = new ArrayList<>();
for (Uri uri : uris) {
- if (isImageType(resolver.getType(uri))) {
+ if (validForContentPreview(uri) && isImageType(resolver.getType(uri))) {
imageUris.add(uri);
}
}
@@ -1544,9 +1550,16 @@ public class ChooserActivity extends ResolverActivity implements
String action = targetIntent.getAction();
if (Intent.ACTION_SEND.equals(action)) {
Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
+ if (!validForContentPreview(uri)) {
+ contentPreviewLayout.setVisibility(View.GONE);
+ return contentPreviewLayout;
+ }
loadFileUriIntoView(uri, contentPreviewLayout);
} else {
List uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ uris = uris.stream()
+ .filter(ChooserActivity::validForContentPreview)
+ .collect(Collectors.toList());
int uriCount = uris.size();
if (uriCount == 0) {
@@ -1605,6 +1618,24 @@ public class ChooserActivity extends ResolverActivity implements
}
}
+ /**
+ * Indicate if the incoming content URI should be allowed.
+ *
+ * @param uri the uri to test
+ * @return true if the URI is allowed for content preview
+ */
+ private static boolean validForContentPreview(Uri uri) throws SecurityException {
+ if (uri == null) {
+ return false;
+ }
+ int userId = getUserIdFromUri(uri, UserHandle.USER_CURRENT);
+ if (userId != UserHandle.USER_CURRENT && userId != UserHandle.myUserId()) {
+ Log.e(TAG, "dropped invalid content URI belonging to user " + userId);
+ return false;
+ }
+ return true;
+ }
+
@VisibleForTesting
protected boolean isImageType(String mimeType) {
return mimeType != null && mimeType.startsWith("image/");
@@ -2953,11 +2984,23 @@ public class ChooserActivity extends ResolverActivity implements
private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
return shouldShowTabs()
- && mMultiProfilePagerAdapter.getListAdapterForUserHandle(
- UserHandle.of(UserHandle.myUserId())).getCount() > 0
+ && (mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+ UserHandle.of(UserHandle.myUserId())).getCount() > 0
+ || shouldShowContentPreviewWhenEmpty())
&& shouldShowContentPreview();
}
+ /**
+ * This method could be used to override the default behavior when we hide the preview area
+ * when the current tab doesn't have any items.
+ *
+ * @return true if we want to show the content preview area even if the tab for the current
+ * user is empty
+ */
+ protected boolean shouldShowContentPreviewWhenEmpty() {
+ return false;
+ }
+
/**
* @return true if we want to show the content preview area
*/
diff --git a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
index e3cc4f12fcc670a34c627e5f53dfa72de2159886..d0b581158614de10219b30ccfa5db3a2ef7d797d 100644
--- a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
+++ b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
@@ -47,7 +47,9 @@ public class ChooserActivityLoggerImpl implements ChooserActivityLogger {
/* num_app_provided_app_targets = 6 */ appProvidedApp,
/* is_workprofile = 7 */ isWorkprofile,
/* previewType = 8 */ typeFromPreviewInt(previewType),
- /* intentType = 9 */ typeFromIntentString(intent));
+ /* intentType = 9 */ typeFromIntentString(intent),
+ /* num_provided_custom_actions = 10 */ 0,
+ /* reselection_action_provided = 11 */ false);
}
@Override
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f8b764be582bc6226afbf828c71dc39ea45e137e..19e4ba405feb85ab06f256b35162515f59218df1 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -209,7 +209,7 @@ public class ResolverActivity extends Activity implements
*
Can only be used if there is a work profile.
*
Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}.
*/
- static final String EXTRA_SELECTED_PROFILE =
+ protected static final String EXTRA_SELECTED_PROFILE =
"com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE";
/**
@@ -224,8 +224,8 @@ public class ResolverActivity extends Activity implements
static final String EXTRA_CALLING_USER =
"com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER";
- static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL;
- static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK;
+ protected static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL;
+ protected static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK;
private BroadcastReceiver mWorkProfileStateReceiver;
private UserHandle mHeaderCreatorUser;
diff --git a/core/java/com/android/internal/app/procstats/DumpUtils.java b/core/java/com/android/internal/app/procstats/DumpUtils.java
index bce0d6076d24a79f72eca89892d791831c7c116c..f6bcc4661fd6e19577554a139119e91749e5037f 100644
--- a/core/java/com/android/internal/app/procstats/DumpUtils.java
+++ b/core/java/com/android/internal/app/procstats/DumpUtils.java
@@ -27,12 +27,12 @@ import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_MOD;
import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_OFF;
import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_ON;
import static com.android.internal.app.procstats.ProcessStats.STATE_BACKUP;
-import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP_OR_FGS;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY_CLIENT;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP;
+import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_FROZEN;
import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
import static com.android.internal.app.procstats.ProcessStats.STATE_HOME;
import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_BACKGROUND;
@@ -72,7 +72,8 @@ public final class DumpUtils {
STATE_NAMES = new String[STATE_COUNT];
STATE_NAMES[STATE_PERSISTENT] = "Persist";
STATE_NAMES[STATE_TOP] = "Top";
- STATE_NAMES[STATE_BOUND_TOP_OR_FGS] = "BTopFgs";
+ STATE_NAMES[STATE_BOUND_FGS] = "BFgs";
+ STATE_NAMES[STATE_BOUND_TOP] = "BTop";
STATE_NAMES[STATE_FGS] = "Fgs";
STATE_NAMES[STATE_IMPORTANT_FOREGROUND] = "ImpFg";
STATE_NAMES[STATE_IMPORTANT_BACKGROUND] = "ImpBg";
@@ -83,14 +84,14 @@ public final class DumpUtils {
STATE_NAMES[STATE_HEAVY_WEIGHT] = "HeavyWt";
STATE_NAMES[STATE_HOME] = "Home";
STATE_NAMES[STATE_LAST_ACTIVITY] = "LastAct";
- STATE_NAMES[STATE_CACHED_ACTIVITY] = "CchAct";
- STATE_NAMES[STATE_CACHED_ACTIVITY_CLIENT] = "CchCAct";
- STATE_NAMES[STATE_CACHED_EMPTY] = "CchEmty";
+ STATE_NAMES[STATE_CACHED] = "Cached";
+ STATE_NAMES[STATE_FROZEN] = "Frozen";
STATE_LABELS = new String[STATE_COUNT];
STATE_LABELS[STATE_PERSISTENT] = "Persistent";
STATE_LABELS[STATE_TOP] = " Top";
- STATE_LABELS[STATE_BOUND_TOP_OR_FGS] = "Bnd TopFgs";
+ STATE_LABELS[STATE_BOUND_FGS] = " Bnd Fgs";
+ STATE_LABELS[STATE_BOUND_TOP] = " Bnd Top";
STATE_LABELS[STATE_FGS] = " Fgs";
STATE_LABELS[STATE_IMPORTANT_FOREGROUND] = " Imp Fg";
STATE_LABELS[STATE_IMPORTANT_BACKGROUND] = " Imp Bg";
@@ -101,16 +102,16 @@ public final class DumpUtils {
STATE_LABELS[STATE_HEAVY_WEIGHT] = " Heavy Wgt";
STATE_LABELS[STATE_HOME] = " (Home)";
STATE_LABELS[STATE_LAST_ACTIVITY] = "(Last Act)";
- STATE_LABELS[STATE_CACHED_ACTIVITY] = " (Cch Act)";
- STATE_LABELS[STATE_CACHED_ACTIVITY_CLIENT] = "(Cch CAct)";
- STATE_LABELS[STATE_CACHED_EMPTY] = "(Cch Emty)";
+ STATE_LABELS[STATE_CACHED] = " (Cached)";
+ STATE_LABELS[STATE_FROZEN] = " Frozen";
STATE_LABEL_CACHED = " (Cached)";
STATE_LABEL_TOTAL = " TOTAL";
STATE_NAMES_CSV = new String[STATE_COUNT];
STATE_NAMES_CSV[STATE_PERSISTENT] = "pers";
STATE_NAMES_CSV[STATE_TOP] = "top";
- STATE_NAMES_CSV[STATE_BOUND_TOP_OR_FGS] = "btopfgs";
+ STATE_NAMES_CSV[STATE_BOUND_FGS] = "bfgs";
+ STATE_NAMES_CSV[STATE_BOUND_TOP] = "btop";
STATE_NAMES_CSV[STATE_FGS] = "fgs";
STATE_NAMES_CSV[STATE_IMPORTANT_FOREGROUND] = "impfg";
STATE_NAMES_CSV[STATE_IMPORTANT_BACKGROUND] = "impbg";
@@ -121,14 +122,14 @@ public final class DumpUtils {
STATE_NAMES_CSV[STATE_HEAVY_WEIGHT] = "heavy";
STATE_NAMES_CSV[STATE_HOME] = "home";
STATE_NAMES_CSV[STATE_LAST_ACTIVITY] = "lastact";
- STATE_NAMES_CSV[STATE_CACHED_ACTIVITY] = "cch-activity";
- STATE_NAMES_CSV[STATE_CACHED_ACTIVITY_CLIENT] = "cch-aclient";
- STATE_NAMES_CSV[STATE_CACHED_EMPTY] = "cch-empty";
+ STATE_NAMES_CSV[STATE_CACHED] = "cached";
+ STATE_NAMES_CSV[STATE_FROZEN] = "frzn";
STATE_TAGS = new String[STATE_COUNT];
STATE_TAGS[STATE_PERSISTENT] = "p";
STATE_TAGS[STATE_TOP] = "t";
- STATE_TAGS[STATE_BOUND_TOP_OR_FGS] = "d";
+ STATE_TAGS[STATE_BOUND_FGS] = "y";
+ STATE_TAGS[STATE_BOUND_TOP] = "z";
STATE_TAGS[STATE_FGS] = "g";
STATE_TAGS[STATE_IMPORTANT_FOREGROUND] = "f";
STATE_TAGS[STATE_IMPORTANT_BACKGROUND] = "b";
@@ -139,15 +140,14 @@ public final class DumpUtils {
STATE_TAGS[STATE_HEAVY_WEIGHT] = "w";
STATE_TAGS[STATE_HOME] = "h";
STATE_TAGS[STATE_LAST_ACTIVITY] = "l";
- STATE_TAGS[STATE_CACHED_ACTIVITY] = "a";
- STATE_TAGS[STATE_CACHED_ACTIVITY_CLIENT] = "c";
- STATE_TAGS[STATE_CACHED_EMPTY] = "e";
+ STATE_TAGS[STATE_CACHED] = "a";
+ STATE_TAGS[STATE_FROZEN] = "e";
STATE_PROTO_ENUMS = new int[STATE_COUNT];
STATE_PROTO_ENUMS[STATE_PERSISTENT] = ProcessStatsEnums.PROCESS_STATE_PERSISTENT;
STATE_PROTO_ENUMS[STATE_TOP] = ProcessStatsEnums.PROCESS_STATE_TOP;
- STATE_PROTO_ENUMS[STATE_BOUND_TOP_OR_FGS] =
- ProcessStatsEnums.PROCESS_STATE_BOUND_TOP_OR_FGS;
+ STATE_PROTO_ENUMS[STATE_BOUND_FGS] = ProcessStatsEnums.PROCESS_STATE_BOUND_FGS;
+ STATE_PROTO_ENUMS[STATE_BOUND_TOP] = ProcessStatsEnums.PROCESS_STATE_BOUND_TOP;
STATE_PROTO_ENUMS[STATE_FGS] = ProcessStatsEnums.PROCESS_STATE_FGS;
STATE_PROTO_ENUMS[STATE_IMPORTANT_FOREGROUND] =
ProcessStatsEnums.PROCESS_STATE_IMPORTANT_FOREGROUND;
@@ -161,10 +161,8 @@ public final class DumpUtils {
STATE_PROTO_ENUMS[STATE_HEAVY_WEIGHT] = ProcessStatsEnums.PROCESS_STATE_HEAVY_WEIGHT;
STATE_PROTO_ENUMS[STATE_HOME] = ProcessStatsEnums.PROCESS_STATE_HOME;
STATE_PROTO_ENUMS[STATE_LAST_ACTIVITY] = ProcessStatsEnums.PROCESS_STATE_LAST_ACTIVITY;
- STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY] = ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY;
- STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY_CLIENT] =
- ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
- STATE_PROTO_ENUMS[STATE_CACHED_EMPTY] = ProcessStatsEnums.PROCESS_STATE_CACHED_EMPTY;
+ STATE_PROTO_ENUMS[STATE_CACHED] = ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY;
+ STATE_PROTO_ENUMS[STATE_FROZEN] = ProcessStatsEnums.PROCESS_STATE_FROZEN;
// Remap states, as defined by ProcessStats.java, to a reduced subset of states for data
// aggregation / size reduction purposes.
@@ -173,7 +171,9 @@ public final class DumpUtils {
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_PERSISTENT;
PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_TOP] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_TOP;
- PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_TOP_OR_FGS] =
+ PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_FGS] =
+ ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BOUND_TOP_OR_FGS;
+ PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_TOP] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BOUND_TOP_OR_FGS;
PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_FGS] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_FGS;
@@ -196,11 +196,9 @@ public final class DumpUtils {
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_LAST_ACTIVITY] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
- PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY] =
- ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
- PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY_CLIENT] =
+ PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
- PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_EMPTY] =
+ PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_FROZEN] =
ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
}
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 72b9cd272d02667772ebeb7676bc4f3ecc28a792..fff778c616eeca10eca4ce9fd08b4ae2af93f23b 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -28,10 +28,9 @@ import static com.android.internal.app.procstats.ProcessStats.PSS_USS_AVERAGE;
import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MAXIMUM;
import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MINIMUM;
import static com.android.internal.app.procstats.ProcessStats.STATE_BACKUP;
-import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP_OR_FGS;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY_CLIENT;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP;
+import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
@@ -73,6 +72,7 @@ import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection;
import java.io.PrintWriter;
import java.util.Comparator;
+import java.util.concurrent.TimeUnit;
public final class ProcessState {
private static final String TAG = "ProcessStats";
@@ -84,9 +84,9 @@ public final class ProcessState {
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
STATE_TOP, // ActivityManager.PROCESS_STATE_TOP
- STATE_BOUND_TOP_OR_FGS, // ActivityManager.PROCESS_STATE_BOUND_TOP
+ STATE_BOUND_TOP, // ActivityManager.PROCESS_STATE_BOUND_TOP
STATE_FGS, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- STATE_BOUND_TOP_OR_FGS, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+ STATE_BOUND_FGS, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
@@ -97,10 +97,10 @@ public final class ProcessState {
STATE_HEAVY_WEIGHT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
STATE_HOME, // ActivityManager.PROCESS_STATE_HOME
STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
- STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
- STATE_CACHED_ACTIVITY_CLIENT, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
- STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_RECENT
- STATE_CACHED_EMPTY, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+ STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+ STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT
+ STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
public static final Comparator COMPARATOR = new Comparator() {
@@ -925,8 +925,11 @@ public final class ProcessState {
screenStates, memStates, new int[] { STATE_PERSISTENT }, now, totalTime, true);
dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_TOP],
screenStates, memStates, new int[] {STATE_TOP}, now, totalTime, true);
- dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_TOP_OR_FGS],
- screenStates, memStates, new int[] { STATE_BOUND_TOP_OR_FGS}, now, totalTime,
+ dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_TOP],
+ screenStates, memStates, new int[] { STATE_BOUND_TOP }, now, totalTime,
+ true);
+ dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_FGS],
+ screenStates, memStates, new int[] { STATE_BOUND_FGS }, now, totalTime,
true);
dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_FGS],
screenStates, memStates, new int[] { STATE_FGS}, now, totalTime,
@@ -952,9 +955,6 @@ public final class ProcessState {
screenStates, memStates, new int[] {STATE_HOME}, now, totalTime, true);
dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_LAST_ACTIVITY],
screenStates, memStates, new int[] {STATE_LAST_ACTIVITY}, now, totalTime, true);
- dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABEL_CACHED,
- screenStates, memStates, new int[] {STATE_CACHED_ACTIVITY,
- STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY}, now, totalTime, true);
}
public void dumpProcessState(PrintWriter pw, String prefix,
@@ -1542,6 +1542,75 @@ public final class ProcessState {
proto.write(fieldId, procName);
}
+ /** Dumps the duration of each state to statsEventOutput. */
+ public void dumpStateDurationToStatsd(
+ int atomTag, ProcessStats processStats, StatsEventOutput statsEventOutput) {
+ long topMs = 0;
+ long fgsMs = 0;
+ long boundTopMs = 0;
+ long boundFgsMs = 0;
+ long importantForegroundMs = 0;
+ long cachedMs = 0;
+ long frozenMs = 0;
+ long otherMs = 0;
+ for (int i = 0, size = mDurations.getKeyCount(); i < size; i++) {
+ final int key = mDurations.getKeyAt(i);
+ final int type = SparseMappingTable.getIdFromKey(key);
+ int procStateIndex = type % STATE_COUNT;
+ long duration = mDurations.getValue(key);
+ switch (procStateIndex) {
+ case STATE_TOP:
+ topMs += duration;
+ break;
+ case STATE_BOUND_FGS:
+ boundFgsMs += duration;
+ break;
+ case STATE_BOUND_TOP:
+ boundTopMs += duration;
+ break;
+ case STATE_FGS:
+ fgsMs += duration;
+ break;
+ case STATE_IMPORTANT_FOREGROUND:
+ case STATE_IMPORTANT_BACKGROUND:
+ importantForegroundMs += duration;
+ break;
+ case STATE_BACKUP:
+ case STATE_SERVICE:
+ case STATE_SERVICE_RESTARTING:
+ case STATE_RECEIVER:
+ case STATE_HEAVY_WEIGHT:
+ case STATE_HOME:
+ case STATE_LAST_ACTIVITY:
+ case STATE_PERSISTENT:
+ otherMs += duration;
+ break;
+ case STATE_CACHED:
+ cachedMs += duration;
+ break;
+ // TODO (b/261910877) Add support for tracking frozenMs.
+ }
+ }
+ statsEventOutput.write(
+ atomTag,
+ getUid(),
+ getName(),
+ (int) TimeUnit.MILLISECONDS.toSeconds(processStats.mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(processStats.mTimePeriodEndUptime),
+ (int)
+ TimeUnit.MILLISECONDS.toSeconds(
+ processStats.mTimePeriodEndUptime
+ - processStats.mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(topMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(fgsMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(boundTopMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(boundFgsMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(importantForegroundMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(cachedMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(frozenMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(otherMs));
+ }
+
/** Similar to {@code #dumpDebug}, but with a reduced/aggregated subset of states. */
public void dumpAggregatedProtoForStatsd(ProtoOutputStream proto, long fieldId,
String procName, int uid, long now,
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index d2b2f0a2b8947426784ae04e7d48bd5d385f2666..3ce234b4167b305884bb9bb3fb038260563681de 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -43,6 +43,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.procstats.AssociationState.SourceKey;
import com.android.internal.app.procstats.AssociationState.SourceState;
+import com.android.internal.util.function.QuintConsumer;
import dalvik.system.VMRuntime;
@@ -56,6 +57,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -78,21 +81,21 @@ public final class ProcessStats implements Parcelable {
public static final int STATE_NOTHING = -1;
public static final int STATE_PERSISTENT = 0;
public static final int STATE_TOP = 1;
- public static final int STATE_BOUND_TOP_OR_FGS = 2;
+ public static final int STATE_BOUND_TOP = 2;
public static final int STATE_FGS = 3;
- public static final int STATE_IMPORTANT_FOREGROUND = 4;
- public static final int STATE_IMPORTANT_BACKGROUND = 5;
- public static final int STATE_BACKUP = 6;
- public static final int STATE_SERVICE = 7;
- public static final int STATE_SERVICE_RESTARTING = 8;
- public static final int STATE_RECEIVER = 9;
- public static final int STATE_HEAVY_WEIGHT = 10;
- public static final int STATE_HOME = 11;
- public static final int STATE_LAST_ACTIVITY = 12;
- public static final int STATE_CACHED_ACTIVITY = 13;
- public static final int STATE_CACHED_ACTIVITY_CLIENT = 14;
- public static final int STATE_CACHED_EMPTY = 15;
- public static final int STATE_COUNT = STATE_CACHED_EMPTY+1;
+ public static final int STATE_BOUND_FGS = 4;
+ public static final int STATE_IMPORTANT_FOREGROUND = 5;
+ public static final int STATE_IMPORTANT_BACKGROUND = 6;
+ public static final int STATE_BACKUP = 7;
+ public static final int STATE_SERVICE = 8;
+ public static final int STATE_SERVICE_RESTARTING = 9;
+ public static final int STATE_RECEIVER = 10;
+ public static final int STATE_HEAVY_WEIGHT = 11;
+ public static final int STATE_HOME = 12;
+ public static final int STATE_LAST_ACTIVITY = 13;
+ public static final int STATE_CACHED = 14;
+ public static final int STATE_FROZEN = 15;
+ public static final int STATE_COUNT = STATE_FROZEN + 1;
public static final int PSS_SAMPLE_COUNT = 0;
public static final int PSS_MINIMUM = 1;
@@ -151,9 +154,10 @@ public final class ProcessStats implements Parcelable {
public static final int[] ALL_SCREEN_ADJ = new int[] { ADJ_SCREEN_OFF, ADJ_SCREEN_ON };
public static final int[] NON_CACHED_PROC_STATES = new int[] {
- STATE_PERSISTENT, STATE_TOP, STATE_BOUND_TOP_OR_FGS, STATE_FGS,
+ STATE_PERSISTENT, STATE_TOP, STATE_FGS,
STATE_IMPORTANT_FOREGROUND, STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
- STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT
+ STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT,
+ STATE_BOUND_TOP, STATE_BOUND_FGS
};
public static final int[] BACKGROUND_PROC_STATES = new int[] {
@@ -162,11 +166,11 @@ public final class ProcessStats implements Parcelable {
};
public static final int[] ALL_PROC_STATES = new int[] { STATE_PERSISTENT,
- STATE_TOP, STATE_BOUND_TOP_OR_FGS, STATE_FGS, STATE_IMPORTANT_FOREGROUND,
+ STATE_TOP, STATE_FGS, STATE_IMPORTANT_FOREGROUND,
STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER,
- STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED_ACTIVITY,
- STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY
+ STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED,
+ STATE_BOUND_TOP, STATE_BOUND_FGS, STATE_FROZEN
};
// Should report process stats.
@@ -2389,6 +2393,79 @@ public final class ProcessStats implements Parcelable {
}
}
+ void forEachProcess(Consumer consumer) {
+ final ArrayMap> procMap = mProcesses.getMap();
+ for (int ip = 0, size = procMap.size(); ip < size; ip++) {
+ final SparseArray uids = procMap.valueAt(ip);
+ for (int iu = 0, uidsSize = uids.size(); iu < uidsSize; iu++) {
+ final ProcessState processState = uids.valueAt(iu);
+ consumer.accept(processState);
+ }
+ }
+ }
+
+ void forEachAssociation(
+ QuintConsumer consumer) {
+ final ArrayMap>> pkgMap =
+ mPackages.getMap();
+ for (int ip = 0, size = pkgMap.size(); ip < size; ip++) {
+ final SparseArray> uids = pkgMap.valueAt(ip);
+ for (int iu = 0, uidsSize = uids.size(); iu < uidsSize; iu++) {
+ final int uid = uids.keyAt(iu);
+ final LongSparseArray versions = uids.valueAt(iu);
+ for (int iv = 0, versionsSize = versions.size(); iv < versionsSize; iv++) {
+ final PackageState state = versions.valueAt(iv);
+ for (int iasc = 0, ascSize = state.mAssociations.size();
+ iasc < ascSize;
+ iasc++) {
+ final String serviceName = state.mAssociations.keyAt(iasc);
+ final AssociationState asc = state.mAssociations.valueAt(iasc);
+ for (int is = 0, sourcesSize = asc.mSources.size();
+ is < sourcesSize;
+ is++) {
+ final SourceState src = asc.mSources.valueAt(is);
+ final SourceKey key = asc.mSources.keyAt(is);
+ consumer.accept(asc, uid, serviceName, key, src);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** Dumps the stats of all processes to statsEventOutput. */
+ public void dumpProcessState(int atomTag, StatsEventOutput statsEventOutput) {
+ forEachProcess(
+ (processState) -> {
+ if (processState.isMultiPackage()
+ && processState.getCommonProcess() != processState) {
+ return;
+ }
+ processState.dumpStateDurationToStatsd(atomTag, this, statsEventOutput);
+ });
+ }
+
+ /** Dumps all process association data to statsEventOutput. */
+ public void dumpProcessAssociation(int atomTag, StatsEventOutput statsEventOutput) {
+ forEachAssociation(
+ (asc, serviceUid, serviceName, key, src) -> {
+ statsEventOutput.write(
+ atomTag,
+ key.mUid,
+ key.mProcess,
+ serviceUid,
+ serviceName,
+ (int) TimeUnit.MILLISECONDS.toSeconds(mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(mTimePeriodEndUptime),
+ (int)
+ TimeUnit.MILLISECONDS.toSeconds(
+ mTimePeriodEndUptime - mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(src.mDuration),
+ src.mActiveCount,
+ asc.getProcessName());
+ });
+ }
+
private void dumpProtoPreamble(ProtoOutputStream proto) {
proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime);
proto.write(ProcessStatsSectionProto.END_REALTIME_MS,
diff --git a/core/java/com/android/internal/app/procstats/StatsEventOutput.java b/core/java/com/android/internal/app/procstats/StatsEventOutput.java
new file mode 100644
index 0000000000000000000000000000000000000000..b2e405475a4e4a3fa56478a553c212bd7a04e5fa
--- /dev/null
+++ b/core/java/com/android/internal/app/procstats/StatsEventOutput.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app.procstats;
+
+import android.util.StatsEvent;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+
+/**
+ * A simple wrapper of FrameworkStatsLog.buildStatsEvent. This allows unit tests to mock out the
+ * dependency.
+ */
+public class StatsEventOutput {
+
+ List mOutput;
+
+ public StatsEventOutput(List output) {
+ mOutput = output;
+ }
+
+ /** Writes the data to the output. */
+ public void write(
+ int atomTag,
+ int uid,
+ String processName,
+ int measurementStartUptimeSecs,
+ int measurementEndUptimeSecs,
+ int measurementDurationUptimeSecs,
+ int topSeconds,
+ int fgsSeconds,
+ int boundTopSeconds,
+ int boundFgsSeconds,
+ int importantForegroundSeconds,
+ int cachedSeconds,
+ int frozenSeconds,
+ int otherSeconds) {
+ mOutput.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ uid,
+ processName,
+ measurementStartUptimeSecs,
+ measurementEndUptimeSecs,
+ measurementDurationUptimeSecs,
+ topSeconds,
+ fgsSeconds,
+ boundTopSeconds,
+ boundFgsSeconds,
+ importantForegroundSeconds,
+ cachedSeconds,
+ frozenSeconds,
+ otherSeconds));
+ }
+
+ /** Writes the data to the output. */
+ public void write(
+ int atomTag,
+ int clientUid,
+ String processName,
+ int serviceUid,
+ String serviceName,
+ int measurementStartUptimeSecs,
+ int measurementEndUptimeSecs,
+ int measurementDurationUptimeSecs,
+ int activeDurationUptimeSecs,
+ int activeCount,
+ String serviceProcessName) {
+ mOutput.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ clientUid,
+ processName,
+ serviceUid,
+ serviceName,
+ measurementStartUptimeSecs,
+ measurementEndUptimeSecs,
+ measurementDurationUptimeSecs,
+ activeDurationUptimeSecs,
+ activeCount,
+ serviceProcessName));
+ }
+}
diff --git a/core/java/com/android/internal/app/procstats/UidState.java b/core/java/com/android/internal/app/procstats/UidState.java
index 8761b7470cd33aceff67910bfe9397201adbb645..49113465c26afcf5e1d42a8c37996a831cd821c2 100644
--- a/core/java/com/android/internal/app/procstats/UidState.java
+++ b/core/java/com/android/internal/app/procstats/UidState.java
@@ -150,6 +150,7 @@ public final class UidState {
public void resetSafely(long now) {
mDurations.resetTable();
mStartTime = now;
+ mProcesses.removeIf(p -> !p.isInUse());
}
/**
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index b916878ff461274af0b1ae2dcc494d0b47ef399a..3303c0e73e076538275da685fdcd14f5ccd83c67 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -562,9 +562,9 @@ public final class SystemUiDeviceConfigFlags {
"task_manager_show_user_visible_jobs";
/**
- * (boolean) Whether the clipboard overlay is enabled.
+ * (boolean) Whether to show notification volume control slider separate from ring.
*/
- public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
+ public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
/**
* (boolean) Whether widget provider info would be saved to / loaded from system persistence
@@ -572,13 +572,6 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String PERSISTS_WIDGET_PROVIDER_INFO = "persists_widget_provider_info";
- /**
- * (boolean) Whether the clipboard overlay shows an edit button (as opposed to requiring tapping
- * the preview to send an edit intent).
- */
- public static final String CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON =
- "clipboard_overlay_show_edit_button";
-
/**
* (boolean) Whether to show smart chips (based on TextClassifier) in the clipboard overlay.
*/
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
new file mode 100644
index 0000000000000000000000000000000000000000..c946db1dbda97fdf5493495f8ab2664aac595ada
--- /dev/null
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -0,0 +1,188 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.config.sysui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provides a central definition of debug SystemUI's SystemProperties flags, and their defaults.
+ *
+ * The main feature of this class is that it encodes a system-wide default for each flag which can
+ * be updated by engineers with a single-line CL.
+ *
+ * NOTE: Because flag values returned by this class are not cached, it is important that developers
+ * understand the intricacies of changing values and how that applies to their own code.
+ * Generally, the best practice is to set the property, and then restart the device so that any
+ * processes with stale state can be updated. However, if your code has no state derived from the
+ * flag value and queries it any time behavior is relevant, then it may be safe to change the flag
+ * and not immediately reboot.
+ *
+ * To enable flags in debuggable builds, use the following commands:
+ *
+ * $ adb shell setprop persist.sysui.whatever_the_flag true
+ * $ adb reboot
+ *
+ * @hide
+ */
+public class SystemUiSystemPropertiesFlags {
+
+ /** The teamfood flag allows multiple features to be opted into at once. */
+ public static final Flag TEAMFOOD = devFlag("persist.sysui.teamfood");
+
+ /**
+ * Flags related to notification features
+ */
+ public static final class NotificationFlags {
+
+ /**
+ * FOR DEVELOPMENT / TESTING ONLY!!!
+ * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
+ * NOTE: enabling this implies SHOW_STICKY_HUN_FOR_DENIED_FSI in SystemUI
+ */
+ public static final Flag FSI_FORCE_DEMOTE =
+ devFlag("persist.sysui.notification.fsi_force_demote");
+
+ /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */
+ public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
+ devFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
+
+ /** Gating the ability for users to dismiss ongoing event notifications */
+ public static final Flag ALLOW_DISMISS_ONGOING =
+ devFlag("persist.sysui.notification.ongoing_dismissal");
+
+ /** Gating the redaction of OTP notifications on the lockscreen */
+ public static final Flag OTP_REDACTION =
+ devFlag("persist.sysui.notification.otp_redaction");
+
+ }
+
+ //// == End of flags. Everything below this line is the implementation. == ////
+
+ /** The interface used for resolving SystemUI SystemProperties Flags to booleans. */
+ public interface FlagResolver {
+ /** Is the flag enabled? */
+ boolean isEnabled(Flag flag);
+ }
+
+ /** The primary, immutable resolver returned by getResolver() */
+ private static final FlagResolver
+ MAIN_RESOLVER =
+ Build.IS_DEBUGGABLE ? new DebugResolver() : new ProdResolver();
+
+ /**
+ * On debuggable builds, this can be set to override the resolver returned by getResolver().
+ * This can be useful to override flags when testing components that do not allow injecting the
+ * SystemUiPropertiesFlags resolver they use.
+ * Always set this to null when tests tear down.
+ */
+ @VisibleForTesting
+ public static FlagResolver TEST_RESOLVER = null;
+
+ /** Get the resolver for this device configuration. */
+ public static FlagResolver getResolver() {
+ if (Build.IS_DEBUGGABLE && TEST_RESOLVER != null) {
+ Log.i("SystemUiSystemPropertiesFlags", "Returning debug resolver " + TEST_RESOLVER);
+ return TEST_RESOLVER;
+ }
+ return MAIN_RESOLVER;
+ }
+
+ /**
+ * Creates a flag that is enabled by default in debuggable builds.
+ * It can be enabled by setting this flag's SystemProperty to 1.
+ *
+ * This flag is ALWAYS disabled in release builds.
+ */
+ @VisibleForTesting
+ public static Flag devFlag(String name) {
+ return new Flag(name, false, null);
+ }
+
+ /**
+ * Creates a flag that is disabled by default in debuggable builds.
+ * It can be enabled or force-disabled by setting this flag's SystemProperty to 1 or 0.
+ * If this flag's SystemProperty is not set, the flag can be enabled by setting the
+ * TEAMFOOD flag's SystemProperty to 1.
+ *
+ * This flag is ALWAYS disabled in release builds.
+ */
+ @VisibleForTesting
+ public static Flag teamfoodFlag(String name) {
+ return new Flag(name, false, TEAMFOOD);
+ }
+
+ /**
+ * Creates a flag that is enabled by default in debuggable builds.
+ * It can be enabled by setting this flag's SystemProperty to 0.
+ *
+ * This flag is ALWAYS enabled in release builds.
+ */
+ @VisibleForTesting
+ public static Flag releasedFlag(String name) {
+ return new Flag(name, true, null);
+ }
+
+ /** Represents a developer-switchable gate for a feature. */
+ public static final class Flag {
+ public final String mSysPropKey;
+ public final boolean mDefaultValue;
+ @Nullable
+ public final Flag mDebugDefault;
+
+ /** constructs a new flag. only visible for testing the class */
+ @VisibleForTesting
+ public Flag(@NonNull String sysPropKey, boolean defaultValue, @Nullable Flag debugDefault) {
+ mSysPropKey = sysPropKey;
+ mDefaultValue = defaultValue;
+ mDebugDefault = debugDefault;
+ }
+ }
+
+ /** Implementation of the interface used in release builds. */
+ @VisibleForTesting
+ public static final class ProdResolver implements
+ FlagResolver {
+ @Override
+ public boolean isEnabled(Flag flag) {
+ return flag.mDefaultValue;
+ }
+ }
+
+ /** Implementation of the interface used in debuggable builds. */
+ @VisibleForTesting
+ public static class DebugResolver implements FlagResolver {
+ @Override
+ public final boolean isEnabled(Flag flag) {
+ if (flag.mDebugDefault == null) {
+ return getBoolean(flag.mSysPropKey, flag.mDefaultValue);
+ }
+ return getBoolean(flag.mSysPropKey, isEnabled(flag.mDebugDefault));
+ }
+
+ /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
+ @VisibleForTesting
+ public boolean getBoolean(String key, boolean defaultValue) {
+ return SystemProperties.getBoolean(key, defaultValue);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index d8afe50d3af3f12e8f8fd4ed352c421050e90037..475f7fd2bdae1c22221fb3185b056dbeb649155b 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -16,6 +16,10 @@
package com.android.internal.jank;
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.provider.DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR;
+
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
@@ -33,6 +37,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
@@ -93,6 +98,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
+import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
@@ -231,6 +237,7 @@ public class InteractionJankMonitor {
public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
+ public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
private static final int NO_STATSD_LOGGING = -1;
@@ -308,6 +315,8 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
+ NO_STATSD_LOGGING,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION,
};
private static volatile InteractionJankMonitor sInstance;
@@ -396,7 +405,8 @@ public class InteractionJankMonitor {
CUJ_RECENTS_SCROLLING,
CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
- CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME
+ CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
+ CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -431,18 +441,37 @@ public class InteractionJankMonitor {
mWorker = worker;
mWorker.start();
mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
+ mEnabled = DEFAULT_ENABLED;
+
+ final Context context = ActivityThread.currentApplication();
+ if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
+ if (DEBUG) {
+ Log.d(TAG, "Initialized the InteractionJankMonitor."
+ + " (No READ_DEVICE_CONFIG permission to change configs)"
+ + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
+ + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
+ + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
+ + ", package=" + context.getPackageName());
+ }
+ return;
+ }
- // Post initialization to the background in case we're running on the main
- // thread.
+ // Post initialization to the background in case we're running on the main thread.
mWorker.getThreadHandler().post(
- () -> mPropertiesChangedListener.onPropertiesChanged(
- DeviceConfig.getProperties(
- DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
- DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
- new HandlerExecutor(mWorker.getThreadHandler()),
- mPropertiesChangedListener);
- mEnabled = DEFAULT_ENABLED;
+ () -> {
+ try {
+ mPropertiesChangedListener.onPropertiesChanged(
+ DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_INTERACTION_JANK_MONITOR,
+ new HandlerExecutor(mWorker.getThreadHandler()),
+ mPropertiesChangedListener);
+ } catch (SecurityException ex) {
+ Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
+ + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+ + ", package=" + context.getPackageName());
+ }
+ });
}
/**
@@ -917,6 +946,8 @@ public class InteractionJankMonitor {
return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
+ case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
+ return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 05c6842784278bed71c7f5f902c102e765318d30..852cfe32cc902cc899ea3385fe28fa6a5d0eb730 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -167,7 +167,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version. Must be updated when the format of the parcelable changes
- public static final int VERSION = 210;
+ public static final int VERSION = 211;
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -6514,6 +6514,9 @@ public class BatteryStatsImpl extends BatteryStats {
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
mPhoneOn = true;
mPhoneOnTimer.startRunningLocked(elapsedRealtimeMs);
+ if (mConstants.PHONE_ON_EXTERNAL_STATS_COLLECTION) {
+ scheduleSyncExternalStatsLocked("phone-on", ExternalStatsSync.UPDATE_RADIO);
+ }
}
}
@@ -6532,6 +6535,7 @@ public class BatteryStatsImpl extends BatteryStats {
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
mPhoneOn = false;
mPhoneOnTimer.stopRunningLocked(elapsedRealtimeMs);
+ scheduleSyncExternalStatsLocked("phone-off", ExternalStatsSync.UPDATE_RADIO);
}
}
@@ -8507,6 +8511,12 @@ public class BatteryStatsImpl extends BatteryStats {
return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO);
}
+ @GuardedBy("this")
+ @Override
+ public long getPhoneEnergyConsumptionUC() {
+ return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_PHONE);
+ }
+
@GuardedBy("this")
@Override
public long getScreenOnMeasuredBatteryConsumptionUC() {
@@ -13751,18 +13761,36 @@ public class BatteryStatsImpl extends BatteryStats {
}
synchronized (this) {
+ final long totalRadioDurationMs =
+ mMobileRadioActiveTimer.getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000) / 1000;
+ mMobileRadioActiveTimer.setMark(elapsedRealtimeMs);
+ final long phoneOnDurationMs = Math.min(totalRadioDurationMs,
+ mPhoneOnTimer.getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000);
+ mPhoneOnTimer.setMark(elapsedRealtimeMs);
+
if (!mOnBatteryInternal || mIgnoreNextExternalStats) {
return;
}
final SparseDoubleArray uidEstimatedConsumptionMah;
+ final long dataConsumedChargeUC;
if (consumedChargeUC > 0 && mMobileRadioPowerCalculator != null
&& mGlobalMeasuredEnergyStats != null) {
+ // Crudely attribute power consumption. Added (totalRadioDurationMs / 2) to the
+ // numerator for long rounding.
+ final long phoneConsumedChargeUC =
+ (consumedChargeUC * phoneOnDurationMs + totalRadioDurationMs / 2)
+ / totalRadioDurationMs;
+ dataConsumedChargeUC = consumedChargeUC - phoneConsumedChargeUC;
mGlobalMeasuredEnergyStats.updateStandardBucket(
- MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, consumedChargeUC);
+ MeasuredEnergyStats.POWER_BUCKET_PHONE, phoneConsumedChargeUC);
+ mGlobalMeasuredEnergyStats.updateStandardBucket(
+ MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, dataConsumedChargeUC);
uidEstimatedConsumptionMah = new SparseDoubleArray();
} else {
uidEstimatedConsumptionMah = null;
+ dataConsumedChargeUC = POWER_DATA_UNAVAILABLE;
}
if (deltaInfo != null) {
@@ -13922,14 +13950,9 @@ public class BatteryStatsImpl extends BatteryStats {
// Update the MeasuredEnergyStats information.
if (uidEstimatedConsumptionMah != null) {
double totalEstimatedConsumptionMah = 0.0;
-
- // Estimate total active radio power consumption since last mark.
- final long totalRadioTimeMs = mMobileRadioActiveTimer.getTimeSinceMarkLocked(
- elapsedRealtimeMs * 1000) / 1000;
- mMobileRadioActiveTimer.setMark(elapsedRealtimeMs);
totalEstimatedConsumptionMah +=
mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
- totalRadioTimeMs);
+ totalRadioDurationMs);
// Estimate idle power consumption at each signal strength level
final int numSignalStrengthLevels = mPhoneSignalStrengthsTimer.length;
@@ -13953,7 +13976,7 @@ public class BatteryStatsImpl extends BatteryStats {
mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs);
distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
- consumedChargeUC, uidEstimatedConsumptionMah,
+ dataConsumedChargeUC, uidEstimatedConsumptionMah,
totalEstimatedConsumptionMah, elapsedRealtimeMs);
}
@@ -16685,6 +16708,8 @@ public class BatteryStatsImpl extends BatteryStats {
public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb";
public static final String KEY_BATTERY_CHARGED_DELAY_MS =
"battery_charged_delay_ms";
+ public static final String KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION =
+ "phone_on_external_stats_collection";
private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
@@ -16697,6 +16722,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE = 64;
private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
+ private static final boolean DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION = true;
public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
/* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
@@ -16712,6 +16738,8 @@ public class BatteryStatsImpl extends BatteryStats {
public int MAX_HISTORY_FILES;
public int MAX_HISTORY_BUFFER; /*Bytes*/
public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS;
+ public boolean PHONE_ON_EXTERNAL_STATS_COLLECTION =
+ DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -16788,6 +16816,11 @@ public class BatteryStatsImpl extends BatteryStats {
DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
: DEFAULT_MAX_HISTORY_BUFFER_KB)
* 1024;
+
+ PHONE_ON_EXTERNAL_STATS_COLLECTION = mParser.getBoolean(
+ KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION,
+ DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION);
+
updateBatteryChargedDelayMsLocked();
}
}
@@ -16842,6 +16875,8 @@ public class BatteryStatsImpl extends BatteryStats {
pw.println(MAX_HISTORY_BUFFER/1024);
pw.print(KEY_BATTERY_CHARGED_DELAY_MS); pw.print("=");
pw.println(BATTERY_CHARGED_DELAY_MS);
+ pw.print(KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION); pw.print("=");
+ pw.println(PHONE_ON_EXTERNAL_STATS_COLLECTION);
}
}
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 09e409bd934ef6bf3a58af8a85e9a7f489b26247..ac4976f2a46a4af815ce63986df2acd02286292d 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -298,18 +298,16 @@ public class BatteryUsageStatsProvider {
BatteryStats.Uid.PROCESS_STATE_FOREGROUND, realtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
- totalForegroundDurationUs += uid.getProcessStateTime(
- BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE, realtimeUs,
- BatteryStats.STATS_SINCE_CHARGED);
-
return totalForegroundDurationUs / 1000;
}
private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs) {
- return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, realtimeUs,
- BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ return (uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
+ realtimeUs, BatteryStats.STATS_SINCE_CHARGED)
+ + uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
+ realtimeUs, BatteryStats.STATS_SINCE_CHARGED))
+ / 1000;
}
-
private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) {
final boolean includePowerModels = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index cb893defab14238611223db79322e6533661d484..f1c4ffe077883967dd30ba5568406c4c5bb5344c 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -40,14 +40,27 @@ public class PhonePowerCalculator extends PowerCalculator {
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ final long energyConsumerUC = batteryStats.getPhoneEnergyConsumptionUC();
+ final int powerModel = getPowerModel(energyConsumerUC, query);
+
final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
- final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
- if (phoneOnPower != 0) {
- builder.getAggregateBatteryConsumerBuilder(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnPower)
- .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnTimeMs);
+ final double phoneOnPower;
+ switch (powerModel) {
+ case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
+ phoneOnPower = uCtoMah(energyConsumerUC);
+ break;
+ case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
+ default:
+ phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
}
+
+ if (phoneOnPower == 0.0) return;
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnPower, powerModel)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnTimeMs);
+
}
}
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index 98d81c9598b8fa8c92dedebfa53d22b7c6aed4c8..cccd80e824209019c4a022e27ac8e722a0f07503 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -31,6 +31,8 @@ public class RoSystemProperties {
SystemProperties.getInt("ro.factorytest", 0);
public static final String CONTROL_PRIVAPP_PERMISSIONS =
SystemProperties.get("ro.control_privapp_permissions");
+ public static final boolean SUPPORT_ONE_HANDED_MODE =
+ SystemProperties.getBoolean("ro.support_one_handed_mode", /* def= */ false);
// ------ ro.hdmi.* -------- //
/**
diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
index 205c5fd735eac795225373acb63da57f4672dc6a..f1ed3bed5d8921d808b0908cbba421335f478349 100644
--- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
+++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
@@ -56,6 +56,9 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
}
};
+ /**
+ * Registers the observer for all users.
+ */
public void register() {
ContentResolver r = mContext.getContentResolver();
r.registerContentObserver(
@@ -73,6 +76,26 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
mOnPropertiesChangedListener);
}
+ /**
+ * Registers the observer for the calling user.
+ */
+ public void registerForCallingUser() {
+ ContentResolver r = mContext.getContentResolver();
+ r.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT),
+ false, this);
+ r.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT),
+ false, this);
+ r.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE),
+ false, this);
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ runnable -> mMainHandler.post(runnable),
+ mOnPropertiesChangedListener);
+ }
+
public void unregister() {
mContext.getContentResolver().unregisterContentObserver(this);
DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
@@ -86,12 +109,46 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
}
}
+ /**
+ * Returns the left sensitivity for the current user. To be used in code that runs primarily
+ * in one user's process.
+ */
public int getLeftSensitivity(Resources userRes) {
- return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT);
+ final float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f, UserHandle.USER_CURRENT);
+ return (int) (getUnscaledInset(userRes) * scale);
}
+ /**
+ * Returns the left sensitivity for the calling user. To be used in code that runs in a
+ * per-user process.
+ */
+ @SuppressWarnings("NonUserGetterCalled")
+ public int getLeftSensitivityForCallingUser(Resources userRes) {
+ final float scale = Settings.Secure.getFloat(mContext.getContentResolver(),
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f);
+ return (int) (getUnscaledInset(userRes) * scale);
+ }
+
+ /**
+ * Returns the right sensitivity for the current user. To be used in code that runs primarily
+ * in one user's process.
+ */
public int getRightSensitivity(Resources userRes) {
- return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT);
+ final float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f, UserHandle.USER_CURRENT);
+ return (int) (getUnscaledInset(userRes) * scale);
+ }
+
+ /**
+ * Returns the right sensitivity for the calling user. To be used in code that runs in a
+ * per-user process.
+ */
+ @SuppressWarnings("NonUserGetterCalled")
+ public int getRightSensitivityForCallingUser(Resources userRes) {
+ final float scale = Settings.Secure.getFloat(mContext.getContentResolver(),
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f);
+ return (int) (getUnscaledInset(userRes) * scale);
}
public boolean areNavigationButtonForcedVisible() {
@@ -99,7 +156,7 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) == 0;
}
- private int getSensitivity(Resources userRes, String side) {
+ private float getUnscaledInset(Resources userRes) {
final DisplayMetrics dm = userRes.getDisplayMetrics();
final float defaultInset = userRes.getDimension(
com.android.internal.R.dimen.config_backGestureInset) / dm.density;
@@ -110,8 +167,6 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
: defaultInset;
final float inset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, backGestureInset,
dm);
- final float scale = Settings.Secure.getFloatForUser(
- mContext.getContentResolver(), side, 1.0f, UserHandle.USER_CURRENT);
- return (int) (inset * scale);
+ return inset;
}
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index fb38bba8ee16700438350c306210de9a95ac690f..e603e2ed57f1aef4c648562e595fc7473134e059 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -295,6 +295,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
private boolean mClosingActionMenu;
private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
+ private int mAudioMode = AudioManager.MODE_NORMAL;
private MediaController mMediaController;
private AudioManager mAudioManager;
@@ -317,6 +318,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
};
+ private AudioManager.OnModeChangedListener mOnModeChangedListener;
+
private Transition mEnterTransition = null;
private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
private Transition mExitTransition = null;
@@ -379,8 +382,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
// window, as we'll be skipping the addView in handleResumeActivity(), and
// the token will not be updated as for a new window.
getAttributes().token = preservedWindow.getAttributes().token;
- mProxyOnBackInvokedDispatcher.setActualDispatcher(
- preservedWindow.getOnBackInvokedDispatcher());
+ final ViewRootImpl viewRoot = mDecor.getViewRootImpl();
+ if (viewRoot != null) {
+ // Clear the old callbacks and attach to the new window.
+ viewRoot.getOnBackInvokedDispatcher().clear();
+ onViewRootImplSet(viewRoot);
+ }
}
// Even though the device doesn't support picture-in-picture mode,
// an user can force using it through developer options.
@@ -1946,9 +1953,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
- // If we have a session send it the volume command, otherwise
- // use the suggested stream.
- if (mMediaController != null) {
+ // If we have a session and no active phone call send it the volume command,
+ // otherwise use the suggested stream.
+ if (mMediaController != null && !isActivePhoneCallOngoing()) {
getMediaSessionManager().dispatchVolumeKeyEventToSessionAsSystemService(event,
mMediaController.getSessionToken());
} else {
@@ -1999,6 +2006,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
return false;
}
+ private boolean isActivePhoneCallOngoing() {
+ return mAudioMode == AudioManager.MODE_IN_CALL
+ || mAudioMode == AudioManager.MODE_IN_COMMUNICATION;
+ }
+
private KeyguardManager getKeyguardManager() {
if (mKeyguardManager == null) {
mKeyguardManager = (KeyguardManager) getContext().getSystemService(
@@ -2322,6 +2334,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
}
+ @Override
+ protected void onDestroy() {
+ if (mOnModeChangedListener != null) {
+ getAudioManager().removeOnModeChangedListener(mOnModeChangedListener);
+ mOnModeChangedListener = null;
+ }
+ }
+
private class PanelMenuPresenterCallback implements MenuPresenter.Callback {
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
@@ -3204,6 +3224,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setMediaController(MediaController controller) {
mMediaController = controller;
+ if (controller != null && mOnModeChangedListener == null) {
+ mAudioMode = getAudioManager().getMode();
+ mOnModeChangedListener = mode -> mAudioMode = mode;
+ getAudioManager().addOnModeChangedListener(getContext().getMainExecutor(),
+ mOnModeChangedListener);
+ } else if (mOnModeChangedListener != null) {
+ getAudioManager().removeOnModeChangedListener(mOnModeChangedListener);
+ mOnModeChangedListener = null;
+ }
}
@Override
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index 7fb8696a217d2f58fd0248d28f2781e2d97f7ee2..5bfdd62592c8b957a5c445274438cedc4eab11fe 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -59,7 +59,9 @@ public class MeasuredEnergyStats {
public static final int POWER_BUCKET_BLUETOOTH = 5;
public static final int POWER_BUCKET_GNSS = 6;
public static final int POWER_BUCKET_MOBILE_RADIO = 7;
- public static final int NUMBER_STANDARD_POWER_BUCKETS = 8; // Buckets above this are custom.
+ public static final int POWER_BUCKET_CAMERA = 8;
+ public static final int POWER_BUCKET_PHONE = 9;
+ public static final int NUMBER_STANDARD_POWER_BUCKETS = 10; // Buckets above this are custom.
@IntDef(prefix = {"POWER_BUCKET_"}, value = {
POWER_BUCKET_UNKNOWN,
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 017bf3f3dd6c5b3b448509138067f2a5b8ca6326..04fc4a6fd81dea3f1e1c81839ddbfc1911edeb99 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -338,4 +338,11 @@ oneway interface IStatusBar
* @param leftOrTop indicates where the stage split is.
*/
void enterStageSplitFromRunningApp(boolean leftOrTop);
+
+ /**
+ * Shows the media output switcher dialog.
+ *
+ * @param packageName of the session for which the output switcher is shown.
+ */
+ void showMediaOutputSwitcher(String packageName);
}
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index afb526aeea6fb2843cbe9a320c6e2877787da078..c8a8b586590b2e507fa79a04178084b6c05e4f01 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -24,12 +24,15 @@ import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPOR
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SMARTSPACE_DOORBELL;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS;
@@ -187,6 +190,25 @@ public class LatencyTracker {
*/
public static final int ACTION_SHOW_VOICE_INTERACTION = 19;
+ /**
+ * Time it takes to request IME shown animation.
+ */
+ public static final int ACTION_REQUEST_IME_SHOWN = 20;
+
+ /**
+ * Time it takes to request IME hidden animation.
+ */
+ public static final int ACTION_REQUEST_IME_HIDDEN = 21;
+
+ /**
+ * Time it takes to load the animation frames in smart space doorbell card.
+ * It measures the duration from the images uris are passed into the view
+ * to all the frames are loaded.
+ *
+ * A long latency makes the doorbell animation looks janky until all the frames are loaded.
+ */
+ public static final int ACTION_SMARTSPACE_DOORBELL = 22;
+
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -208,6 +230,9 @@ public class LatencyTracker {
ACTION_SHOW_SELECTION_TOOLBAR,
ACTION_FOLD_TO_AOD,
ACTION_SHOW_VOICE_INTERACTION,
+ ACTION_REQUEST_IME_SHOWN,
+ ACTION_REQUEST_IME_HIDDEN,
+ ACTION_SMARTSPACE_DOORBELL,
};
/** @hide */
@@ -232,6 +257,9 @@ public class LatencyTracker {
ACTION_SHOW_SELECTION_TOOLBAR,
ACTION_FOLD_TO_AOD,
ACTION_SHOW_VOICE_INTERACTION,
+ ACTION_REQUEST_IME_SHOWN,
+ ACTION_REQUEST_IME_HIDDEN,
+ ACTION_SMARTSPACE_DOORBELL,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -259,6 +287,9 @@ public class LatencyTracker {
UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION,
+ UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN,
+ UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN,
+ UIACTION_LATENCY_REPORTED__ACTION__ACTION_SMARTSPACE_DOORBELL,
};
private static LatencyTracker sLatencyTracker;
@@ -368,6 +399,12 @@ public class LatencyTracker {
return "ACTION_FOLD_TO_AOD";
case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION:
return "ACTION_SHOW_VOICE_INTERACTION";
+ case UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN:
+ return "ACTION_REQUEST_IME_SHOWN";
+ case UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN:
+ return "ACTION_REQUEST_IME_HIDDEN";
+ case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SMARTSPACE_DOORBELL:
+ return "ACTION_SMARTSPACE_DOORBELL";
default:
throw new IllegalArgumentException("Invalid action");
}
@@ -434,7 +471,7 @@ public class LatencyTracker {
*/
public void onActionStart(@Action int action, String tag) {
synchronized (mLock) {
- if (!isEnabled()) {
+ if (!isEnabled(action)) {
return;
}
// skip if the action is already instrumenting.
@@ -458,7 +495,7 @@ public class LatencyTracker {
*/
public void onActionEnd(@Action int action) {
synchronized (mLock) {
- if (!isEnabled()) {
+ if (!isEnabled(action)) {
return;
}
Session session = mSessions.get(action);
@@ -568,23 +605,27 @@ public class LatencyTracker {
void begin(@NonNull Runnable timeoutAction) {
mStartRtc = SystemClock.elapsedRealtime();
- Trace.asyncTraceBegin(TRACE_TAG_APP, traceName(), 0);
+ Trace.asyncTraceForTrackBegin(TRACE_TAG_APP, traceName(), traceName(), 0);
// start counting timeout.
- mTimeoutRunnable = timeoutAction;
+ mTimeoutRunnable = () -> {
+ Trace.instantForTrack(TRACE_TAG_APP, traceName(), "timeout");
+ timeoutAction.run();
+ };
BackgroundThread.getHandler()
.postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(15));
}
void end() {
mEndRtc = SystemClock.elapsedRealtime();
- Trace.asyncTraceEnd(TRACE_TAG_APP, traceName(), 0);
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_APP, traceName(), "end", 0);
BackgroundThread.getHandler().removeCallbacks(mTimeoutRunnable);
mTimeoutRunnable = null;
}
void cancel() {
- Trace.asyncTraceEnd(TRACE_TAG_APP, traceName(), 0);
+ Trace.instantForTrack(TRACE_TAG_APP, traceName(), "cancel");
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_APP, traceName(), "cancel", 0);
BackgroundThread.getHandler().removeCallbacks(mTimeoutRunnable);
mTimeoutRunnable = null;
}
diff --git a/core/java/com/android/internal/util/ObservableServiceConnection.java b/core/java/com/android/internal/util/ObservableServiceConnection.java
index 3165d293bd91e831718e8cc3e3f5e6958edf4b81..45256fd26cba69904e5d0a9d70e472cb2b379d0a 100644
--- a/core/java/com/android/internal/util/ObservableServiceConnection.java
+++ b/core/java/com/android/internal/util/ObservableServiceConnection.java
@@ -164,6 +164,13 @@ public class ObservableServiceConnection implements ServiceConnection {
mFlags = flags;
}
+ /**
+ * Executes code on the executor specified at construction.
+ */
+ public void execute(Runnable runnable) {
+ mExecutor.execute(runnable);
+ }
+
/**
* Initiate binding to the service.
*
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 79c519645a24dd92e608cdde3e0e546c42b5a2c4..3a393b689717538100faab167d499cf35b6530e3 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,7 +1,7 @@
package com.android.internal.util;
import static android.content.Intent.ACTION_USER_SWITCHED;
-import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -11,29 +11,18 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.graphics.Bitmap;
-import android.graphics.ColorSpace;
-import android.graphics.Insets;
-import android.graphics.ParcelableColorSpace;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
import android.net.Uri;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.WindowManager.ScreenshotSource;
-import android.view.WindowManager.ScreenshotType;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.Objects;
import java.util.function.Consumer;
public class ScreenshotHelper {
@@ -41,212 +30,6 @@ public class ScreenshotHelper {
public static final int SCREENSHOT_MSG_URI = 1;
public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
- /**
- * Describes a screenshot request.
- */
- public static class ScreenshotRequest implements Parcelable {
- @ScreenshotType
- private final int mType;
-
- @ScreenshotSource
- private final int mSource;
-
- private final Bundle mBitmapBundle;
- private final Rect mBoundsInScreen;
- private final Insets mInsets;
- private final int mTaskId;
- private final int mUserId;
- private final ComponentName mTopComponent;
-
-
- public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source) {
- this(type, source, /* topComponent */ null);
- }
-
- public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
- ComponentName topComponent) {
- this(type,
- source,
- /* bitmapBundle*/ null,
- /* boundsInScreen */ null,
- /* insets */ null,
- /* taskId */ -1,
- /* userId */ -1,
- topComponent);
- }
-
- public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
- Bundle bitmapBundle, Rect boundsInScreen, Insets insets, int taskId, int userId,
- ComponentName topComponent) {
- mType = type;
- mSource = source;
- mBitmapBundle = bitmapBundle;
- mBoundsInScreen = boundsInScreen;
- mInsets = insets;
- mTaskId = taskId;
- mUserId = userId;
- mTopComponent = topComponent;
- }
-
- ScreenshotRequest(Parcel in) {
- mType = in.readInt();
- mSource = in.readInt();
- if (in.readInt() == 1) {
- mBitmapBundle = in.readBundle(getClass().getClassLoader());
- mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
- mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
- mTaskId = in.readInt();
- mUserId = in.readInt();
- mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
- ComponentName.class);
- } else {
- mBitmapBundle = null;
- mBoundsInScreen = null;
- mInsets = null;
- mTaskId = -1;
- mUserId = -1;
- mTopComponent = null;
- }
- }
-
- @ScreenshotType
- public int getType() {
- return mType;
- }
-
- @ScreenshotSource
- public int getSource() {
- return mSource;
- }
-
- public Bundle getBitmapBundle() {
- return mBitmapBundle;
- }
-
- public Rect getBoundsInScreen() {
- return mBoundsInScreen;
- }
-
- public Insets getInsets() {
- return mInsets;
- }
-
- public int getTaskId() {
- return mTaskId;
- }
-
- public int getUserId() {
- return mUserId;
- }
-
- public ComponentName getTopComponent() {
- return mTopComponent;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mType);
- dest.writeInt(mSource);
- if (mBitmapBundle == null) {
- dest.writeInt(0);
- } else {
- dest.writeInt(1);
- dest.writeBundle(mBitmapBundle);
- dest.writeParcelable(mBoundsInScreen, 0);
- dest.writeParcelable(mInsets, 0);
- dest.writeInt(mTaskId);
- dest.writeInt(mUserId);
- dest.writeParcelable(mTopComponent, 0);
- }
- }
-
- @NonNull
- public static final Parcelable.Creator CREATOR =
- new Parcelable.Creator() {
-
- @Override
- public ScreenshotRequest createFromParcel(Parcel source) {
- return new ScreenshotRequest(source);
- }
-
- @Override
- public ScreenshotRequest[] newArray(int size) {
- return new ScreenshotRequest[size];
- }
- };
- }
-
- /**
- * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
- * content. This is expected to be used together with {@link #provideScreenshot} to handle a
- * hardware bitmap as a screenshot.
- */
- public static final class HardwareBitmapBundler {
- private static final String KEY_BUFFER = "bitmap_util_buffer";
- private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
-
- private HardwareBitmapBundler() {
- }
-
- /**
- * Creates a Bundle that represents the given Bitmap.
- *
The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid
- * copies when passing across processes, only pass to processes you trust.
- *
- *
Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the
- * returned Bundle should be treated as a standalone object.
- *
- * @param bitmap to convert to bundle
- * @return a Bundle representing the bitmap, should only be parsed by
- * {@link #bundleToHardwareBitmap(Bundle)}
- */
- public static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
- if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
- throw new IllegalArgumentException(
- "Passed bitmap must have hardware config, found: " + bitmap.getConfig());
- }
-
- // Bitmap assumes SRGB for null color space
- ParcelableColorSpace colorSpace =
- bitmap.getColorSpace() == null
- ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
- : new ParcelableColorSpace(bitmap.getColorSpace());
-
- Bundle bundle = new Bundle();
- bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
- bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
-
- return bundle;
- }
-
- /**
- * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
- *
- *
This Bitmap contains the HardwareBuffer from the original caller, be careful passing
- * this Bitmap on to any other source.
- *
- * @param bundle containing the bitmap
- * @return a hardware Bitmap
- */
- public static Bitmap bundleToHardwareBitmap(Bundle bundle) {
- if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
- throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
- }
-
- HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
- ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
- ParcelableColorSpace.class);
-
- return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
- colorSpace.getColorSpace());
- }
- }
-
private static final String TAG = "ScreenshotHelper";
// Time until we give up on the screenshot & show an error instead.
@@ -277,68 +60,53 @@ public class ScreenshotHelper {
/**
* Request a screenshot be taken.
*
- * Added to support reducing unit test duration; the method variant without a timeout argument
- * is recommended for general use.
+ * Convenience method for taking a full screenshot with provided source.
*
- * @param type The type of screenshot, defined by {@link ScreenshotType}
- * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
- * @param handler used to process messages received from the screenshot service
+ * @param source source of the screenshot request, defined by {@link
+ * ScreenshotSource}
+ * @param handler used to process messages received from the screenshot service
* @param completionConsumer receives the URI of the captured screenshot, once saved or
- * null if no screenshot was saved
+ * null if no screenshot was saved
*/
- public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
- @NonNull Handler handler, @Nullable Consumer completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
- takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
- completionConsumer);
+ public void takeScreenshot(@ScreenshotSource int source, @NonNull Handler handler,
+ @Nullable Consumer completionConsumer) {
+ ScreenshotRequest request =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, source).build();
+ takeScreenshot(request, handler, completionConsumer);
}
/**
* Request a screenshot be taken.
*
- * Added to support reducing unit test duration; the method variant without a timeout argument
- * is recommended for general use.
*
- * @param type The type of screenshot, defined by {@link ScreenshotType}
- * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
- * @param handler used to process messages received from the screenshot service
- * @param timeoutMs time limit for processing, intended only for testing
+ * @param request description of the screenshot request, either for taking a
+ * screenshot or
+ * providing a bitmap
+ * @param handler used to process messages received from the screenshot service
* @param completionConsumer receives the URI of the captured screenshot, once saved or
- * null if no screenshot was saved
+ * null if no screenshot was saved
*/
- @VisibleForTesting
- public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
- @NonNull Handler handler, long timeoutMs, @Nullable Consumer completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
- takeScreenshot(handler, screenshotRequest, timeoutMs, completionConsumer);
+ public void takeScreenshot(ScreenshotRequest request, @NonNull Handler handler,
+ @Nullable Consumer completionConsumer) {
+ takeScreenshotInternal(request, handler, completionConsumer, SCREENSHOT_TIMEOUT_MS);
}
/**
- * Request that provided image be handled as if it was a screenshot.
+ * Request a screenshot be taken.
+ *
+ * Added to support reducing unit test duration; the method variant without a timeout argument
+ * is recommended for general use.
*
- * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
- * @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
- * @param insets The insets that the image was shown with, inside the screen bounds.
- * @param taskId The taskId of the task that the screen shot was taken of.
- * @param userId The userId of user running the task provided in taskId.
- * @param topComponent The component name of the top component running in the task.
- * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
- * @param handler A handler used in case the screenshot times out
+ * @param request description of the screenshot request, either for taking a
+ * screenshot or providing a bitmap
+ * @param handler used to process messages received from the screenshot service
+ * @param timeoutMs time limit for processing, intended only for testing
* @param completionConsumer receives the URI of the captured screenshot, once saved or
- * null if no screenshot was saved
+ * null if no screenshot was saved
*/
- public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
- @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
- @ScreenshotSource int source, @NonNull Handler handler,
- @Nullable Consumer completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE,
- source, screenshotBundle, boundsInScreen, insets, taskId, userId, topComponent);
- takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS, completionConsumer);
- }
-
- private void takeScreenshot(@NonNull Handler handler,
- ScreenshotRequest screenshotRequest, long timeoutMs,
- @Nullable Consumer completionConsumer) {
+ @VisibleForTesting
+ public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
+ @Nullable Consumer completionConsumer, long timeoutMs) {
synchronized (mScreenshotLock) {
final Runnable mScreenshotTimeout = () -> {
@@ -354,7 +122,7 @@ public class ScreenshotHelper {
}
};
- Message msg = Message.obtain(null, 0, screenshotRequest);
+ Message msg = Message.obtain(null, 0, request);
Handler h = new Handler(handler.getLooper()) {
@Override
@@ -471,5 +239,4 @@ public class ScreenshotHelper {
Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
}
-
}
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.aidl b/core/java/com/android/internal/util/ScreenshotRequest.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..b08905dd0c9a5b69e6f576d649ad64dabb18a500
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+parcelable ScreenshotRequest;
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.java b/core/java/com/android/internal/util/ScreenshotRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c8b7defb5276e84003e3a5f079c4e7d5b15d35f9
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotRequest.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Insets;
+import android.graphics.ParcelableColorSpace;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.util.Objects;
+
+/**
+ * Describes a screenshot request.
+ */
+public class ScreenshotRequest implements Parcelable {
+ private static final String TAG = "ScreenshotRequest";
+
+ @WindowManager.ScreenshotType
+ private final int mType;
+ @WindowManager.ScreenshotSource
+ private final int mSource;
+ private final ComponentName mTopComponent;
+ private final int mTaskId;
+ private final int mUserId;
+ private final Bitmap mBitmap;
+ private final Rect mBoundsInScreen;
+ private final Insets mInsets;
+
+ private ScreenshotRequest(
+ @WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source,
+ ComponentName topComponent, int taskId, int userId,
+ Bitmap bitmap, Rect boundsInScreen, Insets insets) {
+ mType = type;
+ mSource = source;
+ mTopComponent = topComponent;
+ mTaskId = taskId;
+ mUserId = userId;
+ mBitmap = bitmap;
+ mBoundsInScreen = boundsInScreen;
+ mInsets = insets;
+ }
+
+ ScreenshotRequest(Parcel in) {
+ mType = in.readInt();
+ mSource = in.readInt();
+ mTopComponent = in.readTypedObject(ComponentName.CREATOR);
+ mTaskId = in.readInt();
+ mUserId = in.readInt();
+ mBitmap = HardwareBitmapBundler.bundleToHardwareBitmap(in.readTypedObject(Bundle.CREATOR));
+ mBoundsInScreen = in.readTypedObject(Rect.CREATOR);
+ mInsets = in.readTypedObject(Insets.CREATOR);
+ }
+
+ @WindowManager.ScreenshotType
+ public int getType() {
+ return mType;
+ }
+
+ @WindowManager.ScreenshotSource
+ public int getSource() {
+ return mSource;
+ }
+
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ public Rect getBoundsInScreen() {
+ return mBoundsInScreen;
+ }
+
+ public Insets getInsets() {
+ return mInsets;
+ }
+
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public ComponentName getTopComponent() {
+ return mTopComponent;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mSource);
+ dest.writeTypedObject(mTopComponent, 0);
+ dest.writeInt(mTaskId);
+ dest.writeInt(mUserId);
+ dest.writeTypedObject(HardwareBitmapBundler.hardwareBitmapToBundle(mBitmap), 0);
+ dest.writeTypedObject(mBoundsInScreen, 0);
+ dest.writeTypedObject(mInsets, 0);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+
+ @Override
+ public ScreenshotRequest createFromParcel(Parcel source) {
+ return new ScreenshotRequest(source);
+ }
+
+ @Override
+ public ScreenshotRequest[] newArray(int size) {
+ return new ScreenshotRequest[size];
+ }
+ };
+
+ /**
+ * Builder class for {@link ScreenshotRequest} objects.
+ */
+ public static class Builder {
+ @WindowManager.ScreenshotType
+ private final int mType;
+
+ @WindowManager.ScreenshotSource
+ private final int mSource;
+
+ private Bitmap mBitmap;
+ private Rect mBoundsInScreen;
+ private Insets mInsets = Insets.NONE;
+ private int mTaskId = INVALID_TASK_ID;
+ private int mUserId = USER_NULL;
+ private ComponentName mTopComponent;
+
+ /**
+ * Begin building a ScreenshotRequest.
+ *
+ * @param type The type of the screenshot request, defined by {@link
+ * WindowManager.ScreenshotType}
+ * @param source The source of the screenshot request, defined by {@link
+ * WindowManager.ScreenshotSource}
+ */
+ public Builder(
+ @WindowManager.ScreenshotType int type,
+ @WindowManager.ScreenshotSource int source) {
+ if (type != TAKE_SCREENSHOT_FULLSCREEN && type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ throw new IllegalArgumentException("Invalid screenshot type requested!");
+ }
+ mType = type;
+ mSource = source;
+ }
+
+ /**
+ * Construct a new {@link ScreenshotRequest} with the set parameters.
+ */
+ public ScreenshotRequest build() {
+ if (mType == TAKE_SCREENSHOT_FULLSCREEN && mBitmap != null) {
+ Log.w(TAG, "Bitmap provided, but request is fullscreen. Bitmap will be ignored.");
+ }
+ if (mType == TAKE_SCREENSHOT_PROVIDED_IMAGE && mBitmap == null) {
+ throw new IllegalStateException(
+ "Request is PROVIDED_IMAGE, but no bitmap is provided!");
+ }
+
+ return new ScreenshotRequest(mType, mSource, mTopComponent, mTaskId, mUserId, mBitmap,
+ mBoundsInScreen, mInsets);
+ }
+
+ /**
+ * Set the top component associated with this request.
+ *
+ * @param topComponent The component name of the top component running in the task.
+ */
+ public Builder setTopComponent(ComponentName topComponent) {
+ mTopComponent = topComponent;
+ return this;
+ }
+
+ /**
+ * Set the task id associated with this request.
+ *
+ * @param taskId The taskId of the task that the screenshot was taken of.
+ */
+ public Builder setTaskId(int taskId) {
+ mTaskId = taskId;
+ return this;
+ }
+
+ /**
+ * Set the user id associated with this request.
+ *
+ * @param userId The userId of user running the task provided in taskId.
+ */
+ public Builder setUserId(int userId) {
+ mUserId = userId;
+ return this;
+ }
+
+ /**
+ * Set the bitmap associated with this request.
+ *
+ * @param bitmap The provided screenshot.
+ */
+ public Builder setBitmap(Bitmap bitmap) {
+ mBitmap = bitmap;
+ return this;
+ }
+
+ /**
+ * Set the bounds for the provided bitmap.
+ *
+ * @param bounds The bounds in screen coordinates that the bitmap originated from.
+ */
+ public Builder setBoundsOnScreen(Rect bounds) {
+ mBoundsInScreen = bounds;
+ return this;
+ }
+
+ /**
+ * Set the insets for the provided bitmap.
+ *
+ * @param insets The insets that the image was shown with, inside the screen bounds.
+ */
+ public Builder setInsets(@NonNull Insets insets) {
+ mInsets = insets;
+ return this;
+ }
+ }
+
+ /**
+ * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
+ * content. This is used together with a fully-defined ScreenshotRequest to handle a hardware
+ * bitmap as a screenshot.
+ */
+ private static final class HardwareBitmapBundler {
+ private static final String KEY_BUFFER = "bitmap_util_buffer";
+ private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
+
+ private HardwareBitmapBundler() {
+ }
+
+ /**
+ * Creates a Bundle that represents the given Bitmap.
+ *
The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will
+ * avoid
+ * copies when passing across processes, only pass to processes you trust.
+ *
+ *
Returns a new Bundle rather than modifying an exiting one to avoid key collisions,
+ * the
+ * returned Bundle should be treated as a standalone object.
+ *
+ * @param bitmap to convert to bundle
+ * @return a Bundle representing the bitmap, should only be parsed by
+ * {@link #bundleToHardwareBitmap(Bundle)}
+ */
+ private static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
+ if (bitmap == null) {
+ return null;
+ }
+ if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
+ throw new IllegalArgumentException(
+ "Passed bitmap must have hardware config, found: "
+ + bitmap.getConfig());
+ }
+
+ // Bitmap assumes SRGB for null color space
+ ParcelableColorSpace colorSpace =
+ bitmap.getColorSpace() == null
+ ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
+ : new ParcelableColorSpace(bitmap.getColorSpace());
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
+ bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
+
+ return bundle;
+ }
+
+ /**
+ * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)}.
+ *
+ *
This Bitmap contains the HardwareBuffer from the original caller, be careful
+ * passing
+ * this Bitmap on to any other source.
+ *
+ * @param bundle containing the bitmap
+ * @return a hardware Bitmap
+ */
+ private static Bitmap bundleToHardwareBitmap(Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
+ throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
+ }
+
+ HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
+ ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
+ ParcelableColorSpace.class);
+
+ return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
+ colorSpace.getColorSpace());
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java
index 869da1ffcb6ee00295962ce3c32721241cb6e4b5..058c6ec4d13c7b63ab0859e3cce75145b14a8fc9 100644
--- a/core/java/com/android/internal/view/RotationPolicy.java
+++ b/core/java/com/android/internal/view/RotationPolicy.java
@@ -106,7 +106,9 @@ public final class RotationPolicy {
* Enables or disables rotation lock from the system UI toggle.
*/
public static void setRotationLock(Context context, final boolean enabled) {
- final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION;
+ final int rotation = areAllRotationsAllowed(context)
+ || useCurrentRotationOnRotationLockChange(context) ? CURRENT_ROTATION
+ : NATURAL_ROTATION;
setRotationLockAtAngle(context, enabled, rotation);
}
@@ -139,6 +141,11 @@ public final class RotationPolicy {
return context.getResources().getBoolean(R.bool.config_allowAllRotations);
}
+ private static boolean useCurrentRotationOnRotationLockChange(Context context) {
+ return context.getResources().getBoolean(
+ R.bool.config_useCurrentRotationOnRotationLockChange);
+ }
+
private static void setRotationLock(final boolean enabled, final int rotation) {
AsyncTask.execute(new Runnable() {
@Override
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 2dfe89397ea5e5ec3f0cf588a6259b1f199ce5b8..5b2c441f95c9c982153fdf1f6b1b16cf934ac539 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -170,6 +170,8 @@ public class LockPatternUtils {
private static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+ private static final String LOCK_PIN_ENHANCED_PRIVACY = "pin_enhanced_privacy";
+
private static final String LOCK_SCREEN_DEVICE_OWNER_INFO = "lockscreen.device_owner_info";
private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
@@ -998,6 +1000,27 @@ public class LockPatternUtils {
return getString(Settings.Secure.LOCK_PATTERN_VISIBLE, userId) != null;
}
+ /**
+ * @return Whether enhanced pin privacy is enabled.
+ */
+ public boolean isPinEnhancedPrivacyEnabled(int userId) {
+ return getBoolean(LOCK_PIN_ENHANCED_PRIVACY, false, userId);
+ }
+
+ /**
+ * Set whether enhanced pin privacy is enabled.
+ */
+ public void setPinEnhancedPrivacyEnabled(boolean enabled, int userId) {
+ setBoolean(LOCK_PIN_ENHANCED_PRIVACY, enabled, userId);
+ }
+
+ /**
+ * @return Whether enhanced pin privacy was ever chosen.
+ */
+ public boolean isPinEnhancedPrivacyEverChosen(int userId) {
+ return getString(LOCK_PIN_ENHANCED_PRIVACY, userId) != null;
+ }
+
/**
* Set whether the visible password is enabled for cryptkeeper screen.
*/
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index a8abe50a97554ded682f189de4506014ae1b7560..2a670e865cedbf498076dd567e016243b2227d73 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -556,7 +556,8 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jin
// connect to camera service
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jint cameraId, jstring clientPackageName,
- jboolean overrideToPortrait) {
+ jboolean overrideToPortrait,
+ jboolean forceSlowJpegMode) {
// Convert jstring to String16
const char16_t *rawClientName = reinterpret_cast(
env->GetStringChars(clientPackageName, NULL));
@@ -568,7 +569,7 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobj
int targetSdkVersion = android_get_application_target_sdk_version();
sp camera =
Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID,
- targetSdkVersion, overrideToPortrait);
+ targetSdkVersion, overrideToPortrait, forceSlowJpegMode);
if (camera == NULL) {
return -EACCES;
}
@@ -1054,7 +1055,7 @@ static const JNINativeMethod camMethods[] = {
{"getNumberOfCameras", "()I", (void *)android_hardware_Camera_getNumberOfCameras},
{"_getCameraInfo", "(IZLandroid/hardware/Camera$CameraInfo;)V",
(void *)android_hardware_Camera_getCameraInfo},
- {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;Z)I",
+ {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;ZZ)I",
(void *)android_hardware_Camera_native_setup},
{"native_release", "()V", (void *)android_hardware_Camera_release},
{"setPreviewSurface", "(Landroid/view/Surface;)V",
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 8c23b214b8fe074d70b746ff2347dfa0267ad7dc..206ad17e3c4b9f6af8522e181fac48c9cae9f25c 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -93,6 +93,7 @@ static struct configuration_offsets_t {
jfieldID mScreenWidthDpOffset;
jfieldID mScreenHeightDpOffset;
jfieldID mScreenLayoutOffset;
+ jfieldID mUiMode;
} gConfigurationOffsets;
static struct arraymap_offsets_t {
@@ -1027,10 +1028,11 @@ static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config&
env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
env->SetIntField(result, gConfigurationOffsets.mScreenLayoutOffset, config.screenLayout);
+ env->SetIntField(result, gConfigurationOffsets.mUiMode, config.uiMode);
return result;
}
-static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+static jobjectArray GetSizeAndUiModeConfigurations(JNIEnv* env, jlong ptr) {
ScopedLock assetmanager(AssetManagerFromLong(ptr));
auto configurations = assetmanager->GetResourceConfigurations(true /*exclude_system*/,
false /*exclude_mipmap*/);
@@ -1057,6 +1059,14 @@ static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, j
return array;
}
+static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+ return GetSizeAndUiModeConfigurations(env, ptr);
+}
+
+static jobjectArray NativeGetSizeAndUiModeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+ return GetSizeAndUiModeConfigurations(env, ptr);
+}
+
static jintArray NativeAttributeResolutionStack(
JNIEnv* env, jclass /*clazz*/, jlong ptr,
jlong theme_ptr, jint xml_style_res,
@@ -1487,6 +1497,8 @@ static const JNINativeMethod gAssetManagerMethods[] = {
{"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
{"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
(void*)NativeGetSizeConfigurations},
+ {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
+ (void*)NativeGetSizeAndUiModeConfigurations},
// Style attribute related methods.
{"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
@@ -1565,6 +1577,7 @@ int register_android_content_AssetManager(JNIEnv* env) {
GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I");
gConfigurationOffsets.mScreenLayoutOffset =
GetFieldIDOrDie(env, configurationClass, "screenLayout", "I");
+ gConfigurationOffsets.mUiMode = GetFieldIDOrDie(env, configurationClass, "uiMode", "I");
jclass arrayMapClass = FindClassOrDie(env, "android/util/ArrayMap");
gArrayMapOffsets.classObject = MakeGlobalRefOrDie(env, arrayMapClass);
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b1610d790222592246a65bc499349ce83e873999..8952f37b14693b1a10313637932009e2f3042af9 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -428,7 +428,7 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return 0;
} else if (err != NO_ERROR) {
- jniThrowException(env, OutOfResourcesException, NULL);
+ jniThrowException(env, OutOfResourcesException, statusToString(err).c_str());
return 0;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 556636ddd210694a73c3bd71f9cbe7ef48702fe1..d443270575d3b67a4d0625a3aba50d68eea5a78a 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -89,6 +89,8 @@ message SecureSettingsProto {
// Setting for accessibility magnification for following typing.
optional SettingProto accessibility_magnification_follow_typing_enabled = 43 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto contrast_level = 44 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // Settings for font scaling
+ optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index 8e4006aa6861498ce70a069b31e1b19ad9133b2e..e029af4f9819c82b527fc74ba007e0fdecf17229 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -110,11 +110,20 @@ message ManagedServicesProto {
// All of this type/caption enabled for current profiles.
repeated android.content.ComponentNameProto enabled = 3;
-
repeated ManagedServiceInfoProto live_services = 4;
+ // Was: repeated ComponentNameProto, when snoozed services were not per-user-id.
+ reserved 5;
+
+ message SnoozedServices {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 user_id = 1;
+ repeated android.content.ComponentNameProto snoozed = 2;
+ }
+
// Snoozed for current profiles.
- repeated android.content.ComponentNameProto snoozed = 5;
+ repeated SnoozedServices snoozed = 6;
}
message RankingHelperProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 35dc8e37aaab92644623615d5ddff9d3b0fb1abe..c226112898a931ea495f4704295b363032011d6a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -315,6 +315,7 @@
+
@@ -3156,7 +3157,11 @@
android:protectionLevel="normal" />
Should only be requested by the System, should be required by
TileService declarations.-->
+ android:protectionLevel="signature|recents" />
+
+
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index 50e6f33f628a48169c8332c35acec027c708a3d1..a5ff4706c085900ee637a1119abd5d490443de1d 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -17,6 +17,7 @@
*/
-->
"Kan gebare vasvang wat op die toestel se vingerafdruksensor uitgevoer word."
"Neem skermkiekie""Kan \'n skermkiekie neem."
+ "Voorskou, %1$s""deaktiveer of verander statusbalk""Laat die program toe om die statusbalk te deaktiveer en stelselikone by te voeg of te verwyder.""wees die statusbalk"
@@ -1839,6 +1840,8 @@
"Bekyk tans volskerm""Swiep van bo na onder as jy wil uitgaan.""Het dit"
+ "Draai vir ’n beter aansig"
+ "Verlaat gedeelde skerm vir ’n beter aansig""Klaar""Ure se sirkelglyer""Minute se sirkelglyer"
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 5b64cb539a9d5bc7c5631386739fe279519daf3b..74d22604db9244d03813fd3dd84b0a2f15e08640 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -339,6 +339,7 @@
"በመሣሪያው የጣት አሻራ ዳሳሽ ላይ የተከናወኑ የጣት ምልክቶችን መያዝ ይችላል።""ቅጽበታዊ ገፅ እይታን ያነሳል""የማሳያው ቅጽበታዊ ገፅ እይታን ማንሳት ይችላል።"
+ "ቅድመ ዕይታ፣ %1$s""የሁኔቴ አሞሌ አቦዝን ወይም ቀይር""የስርዓት አዶዎችን ወደ ሁኔታ አሞሌ ላለማስቻል ወይም ለማከል እና ለማስወገድ ለመተግበሪያው ይፈቅዳሉ፡፡""የሁኔታ አሞሌ መሆን"
@@ -1839,6 +1840,8 @@
"ሙሉ ገፅ በማሳየት ላይ""ለመውጣት፣ ከላይ ወደታች ጠረግ ያድርጉ።""ገባኝ"
+ "ለተሻለ ዕይታ ያሽከርክሩ"
+ "ለተሻለ ዕይታ የተከፈለ ማያ ገጽን ትተው ይውጡ""ተከናውኗል""የሰዓታት ክብ ተንሸራታች""የደቂቃዎች ክብ ተንሸራታች"
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 007ae736798045f32052ae98040cdee430502fa3..c61acba2280f13a7199fead4df2c724ec70689b8 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -343,6 +343,7 @@
"يمكن أن تلتقط الإيماءات من أداة استشعار بصمة الإصبع في الجهاز.""أخذ لقطة شاشة""يمكن أخذ لقطة شاشة."
+ "نسخة حصرية، \"%1$s\"""إيقاف شريط الحالة أو تعديله""للسماح للتطبيق بإيقاف شريط الحالة أو إضافة رموز نظام وإزالتها.""العمل كشريط للحالة"
@@ -1843,6 +1844,8 @@
"جارٍ العرض بملء الشاشة""للخروج، مرر بسرعة من أعلى إلى أسفل.""حسنًا"
+ "يمكنك تدوير الجهاز لرؤية شاشة معاينة الكاميرا بشكل أوضح."
+ "يمكنك الخروج من وضع \"تقسيم الشاشة\" لرؤية شاشة معاينة الكاميرا بشكل أوضح.""تم""شريط التمرير الدائري للساعات""شريط التمرير الدائري للدقائق"
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 7c6cdea79668168ed8164deeb8c846e447e0a7b5..321623508a082cad4d5ecb84a43bd257f78edd95 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -339,6 +339,7 @@
"ডিভাইচটোৰ ফিংগাৰপ্ৰিণ্ট ছেন্সৰত দিয়া নিৰ্দেশ বুজিব পাৰে।""স্ক্ৰীনশ্বট লওক""ডিছপ্লে’খনৰ এটা স্ক্ৰীনশ্বট ল\'ব পাৰে।"
+ "পূৰ্বদৰ্শন কৰক, %1$s""স্থিতি দণ্ড অক্ষম কৰক বা সলনি কৰক""স্থিতি দণ্ড অক্ষম কৰিবলৈ বা ছিষ্টেম আইকন আঁতৰাবলৈ এপ্টোক অনুমতি দিয়ে।""স্থিতি দণ্ড হ\'ব পাৰে"
@@ -1839,6 +1840,8 @@
"স্ক্ৰীন পূৰ্ণৰূপত চাই আছে""বাহিৰ হ\'বলৈ ওপৰৰপৰা তললৈ ছোৱাইপ কৰক।""বুজি পালোঁ"
+ "ভালকৈ চাবলৈ ঘূৰাওক"
+ "ভালকৈ চাবলৈ বিভাজিত স্ক্ৰীনৰ পৰা বাহিৰ হওক""সম্পন্ন কৰা হ’ল""ঘড়ীৰ বৃত্তাকাৰ শ্লাইডাৰ""মিনিটৰ বৃত্তাকাৰ শ্লাইডাৰ"
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index b6df531c5da43f0fe954e72de8c853a975aa882e..50de22a2487390990f15859eaa57e06d7f744f75 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -339,6 +339,7 @@
"Cihazların barmaq izi sensorunda olan işarələri əldə edə bilər.""Ekran şəkli çəkin""Ekran şəkli çəkilə bilər."
+ "Önizləmə, %1$s""status panelini deaktivləşdir və ya dəyişdir""Tətbiqə status panelini deaktiv etməyə və ya sistem ikonalarını əlavə etmək və ya silmək imkanı verir.""status paneli edin"
@@ -1839,6 +1840,8 @@
"Tam ekrana baxış""Çıxmaq üçün yuxarıdan aşağı sürüşdürün.""Anladım"
+ "Daha yaxşı görünüş üçün fırladın"
+ "Daha yaxşı görünüş üçün bölünmüş ekrandan çıxın""Hazırdır""Dairəvi saat slayderi""Dairəvi dəqiqə slayderi"
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index c7ea7703d56ac645f11a5dfd7cef1c9a6d68a84b..487e9aa0e6ce8917fb8819716fd2ff617774971f 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -340,6 +340,7 @@
"Može da registruje pokrete na senzoru za otisak prsta na uređaju.""Napravi snimak ekrana""Može da napravi snimak ekrana."
+ "Pregled, %1$s""onemogućavanje ili izmena statusne trake""Dozvoljava aplikaciji da onemogući statusnu traku ili da dodaje i uklanja sistemske ikone.""funkcionisanje kao statusna traka"
@@ -1840,6 +1841,8 @@
"Prikazuje se ceo ekran""Da biste izašli, prevucite nadole odozgo.""Važi"
+ "Rotirajte radi boljeg prikaza"
+ "Izađite iz podeljenog ekrana radi boljeg prikaza""Gotovo""Kružni klizač za sate""Kružni klizač za minute"
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 387cc7fc789770a5af3f0b8ef332632fc15c5364..4d655c323716735cafcc27e5a4680ede5b91fa17 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -341,6 +341,7 @@
"Можа распазнаваць жэсты на сканеры адбіткаў пальцаў прылады.""Зрабіць здымак экрана""Можна зрабіць здымак экрана."
+ "Перадпрагляд, %1$s""адключаць ці змяняць радок стану""Дазваляе прыкладанням адключаць радок стану або дадаваць і выдаляць сістэмныя значкі.""быць панэллю стану"
@@ -1841,6 +1842,8 @@
"Прагляд у поўнаэкранным рэжыме""Для выхаду правядзіце зверху ўніз.""Зразумела"
+ "Павярнуць для лепшага прагляду"
+ "Выйсці з рэжыму падзеленага экрана для лепшага прагляду""Гатова""Кругавы паўзунок гадзін""Кругавы паўзунок хвілін"
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 23b052881532f8f18174a22b515426aabea2c02a..fe56b3d58dd27538405ca0975725be3a151c9536 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -339,6 +339,7 @@
"Може да улавя жестовете, извършени върху сензора за отпечатъци на устройството.""Създаване на екранна снимка""Може да създава екранни снимки."
+ "Визуализация на „%1$s“""деактивиране или промяна на лентата на състоянието""Разрешава на приложението да деактивира лентата на състоянието или да добавя и премахва системни икони.""изпълняване на ролята на лента на състоянието"
@@ -1839,6 +1840,8 @@
"Изглед на цял екран""За изход плъзнете пръст надолу от горната част.""Разбрах"
+ "Завъртете за по-добър изглед"
+ "Излезте от разделения екран за по-добър изглед""Готово""Кръгов плъзгач за часовете""Кръгов плъзгач за минутите"
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 55cb8a6488419fb99c6cbe12025a75b061519f81..e34e10b6749c2da466cad3524cab735dfb81e1a2 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -339,6 +339,7 @@
"ডিভাইসের আঙ্গুলের ছাপের সেন্সরের উপরে ইঙ্গিত করলে বুঝতে পারে।""স্ক্রিনশট নিন""ডিসপ্লের একটি স্ক্রিনশট নিতে পারেন।"
+ "প্রিভিউ, %1$s""স্ট্যাটাস বার নিষ্ক্রিয় অথবা সংশোধন করে""অ্যাপ্লিকেশনকে স্ট্যাটাস বার অক্ষম করতে এবং সিস্টেম আইকনগুলি সরাতে দেয়৷""স্থিতি দন্ডে থাকুন"
@@ -1839,6 +1840,8 @@
"পূর্ণ স্ক্রিনে দেখা হচ্ছে""প্রস্থান করতে উপর থেকে নিচের দিকে সোয়াইপ করুন""বুঝেছি"
+ "আরও ভাল ক্যামেরা ভিউ পাওয়ার জন্য ঘোরান"
+ "আরও ভাল ক্যামেরা ভিউ পাওয়ার জন্য স্প্লিট স্ক্রিন থেকে বেরিয়ে আসুন""সম্পন্ন হয়েছে""বৃত্তাকার ঘণ্টা নির্বাচকের স্লাইডার""বৃত্তাকার মিনিট নির্বাচকের স্লাইডার"
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 7ef64075447984930f3478da2f181a930afdb5d6..499eaa728293ebc9a97bc5ec5ccc5a886eefe141 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -340,6 +340,7 @@
"Može zabilježiti pokrete na senzoru za otisak prsta uređaja.""praviti snimke ekrana""Može napraviti snimak ekrana."
+ "Pregled, %1$s""onemogućavanje ili mijenjanje statusne trake""Dozvoljava aplikaciji onemogućavanje statusne trake ili dodavanje i uklanjanje sistemskih ikona.""funkcioniranje u vidu statusne trake"
@@ -1840,6 +1841,8 @@
"Prikazuje se cijeli ekran""Da izađete, prevucite odozgo nadolje.""Razumijem"
+ "Rotirajte za bolji prikaz"
+ "Izađite iz podijeljenog ekrana za bolji prikaz""Gotovo""Kružni klizač za odabir sata""Kružni klizač za minute"
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index a0541c8945bff0c773d7c058c325c44fe11e1aee..5a655638d221bde73e26118764368bc23ef564ea 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -340,6 +340,7 @@
"Pot capturar els gestos fets en el sensor d\'empremtes digitals del dispositiu.""Fer una captura de pantalla""Pot fer una captura de la pantalla."
+ "Previsualitza, %1$s""desactivar o modificar la barra d\'estat""Permet que l\'aplicació desactivi la barra d\'estat o afegeixi i elimini icones del sistema.""aparèixer a la barra d\'estat"
@@ -1840,6 +1841,8 @@
"Mode de pantalla completa""Per sortir, llisca cap avall des de la part superior.""Entesos"
+ "Gira per a una millor visualització"
+ "Surt de la pantalla dividida per a una millor visualització""Fet""Control circular de les hores""Control circular dels minuts"
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 24cc2a8a446f27986f08a5bd878f44dba65b9706..74d487d988c74d57cf095516fe535c96de8255c0 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -341,6 +341,7 @@
"Dokáže rozpoznat gesta zadaná na snímači otisků prstů.""Pořídit snímek obrazovky""Může pořídit snímek obrazovky."
+ "Náhled, %1$s""zakázání či změny stavového řádku""Umožňuje aplikaci zakázat stavový řádek nebo přidat či odebrat systémové ikony.""vydávání se za stavový řádek"
@@ -1841,6 +1842,8 @@
"Zobrazení celé obrazovky""Režim ukončíte přejetím prstem shora dolů.""Rozumím"
+ "Otočte obrazovku, abyste lépe viděli"
+ "Ukončete režim rozdělené obrazovky, abyste lépe viděli""Hotovo""Kruhový posuvník hodin""Kruhový posuvník minut"
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 5152854df825ed07aca619ed0852bf4542fb9f1c..72ca9f067f6ca7342a79675360b248188feea9c2 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -339,6 +339,7 @@
"Kan registrere bevægelser, der foretages på enhedens fingeraftrykssensor.""Tag screenshot""Kan tage et screenshot af skærmen."
+ "Preview, %1$s""deaktivere eller redigere statuslinje""Tillader, at appen kan deaktivere statusbjælken eller tilføje og fjerne systemikoner.""vær statusbjælken"
@@ -1839,6 +1840,8 @@
"Visning i fuld skærm""Stryg ned fra toppen for at afslutte.""OK, det er forstået"
+ "Roter, og få en bedre visning"
+ "Afslut opdelt skærm, og få en bedre visning""Udfør""Cirkulær timevælger""Cirkulær minutvælger"
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 462ca3f49732bd13247022248773fe0257ba6d99..f9d7ffb208c7474a1bde12d22795eb846cd30e2d 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -339,6 +339,7 @@
"Erfasst Touch-Gesten auf dem Fingerabdrucksensor des Geräts.""Screenshot erstellen""Es kann ein Screenshot des Displays erstellt werden."
+ "Vorschau – %1$s""Statusleiste deaktivieren oder ändern""Ermöglicht der App, die Statusleiste zu deaktivieren oder Systemsymbole hinzuzufügen oder zu entfernen""Statusleiste darstellen"
@@ -1839,6 +1840,8 @@
"Vollbildmodus wird aktiviert""Zum Beenden von oben nach unten wischen""Ok"
+ "Drehen, um die Ansicht zu verbessern"
+ "Splitscreen-Modus beenden, um die Ansicht zu verbessern""Fertig""Kreisförmiger Schieberegler für Stunden""Kreisförmiger Schieberegler für Minuten"
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index c8743c7bd25bbf199b2c4b18970782e1edf13e4d..d002d3931b7e41a898473f419096aa459235d6d0 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -339,6 +339,7 @@
"Μπορεί να αναγνωρίσει κινήσεις που εκτελούνται στον αισθητήρα δακτυλικού αποτυπώματος της συσκευής.""Λήψη στιγμιότυπου οθόνης""Μπορεί να τραβήξει στιγμιότυπο της οθόνης."
+ "Προεπισκόπηση, %1$s""απενεργοποιεί ή να τροποποιεί την γραμμή κατάστασης""Επιτρέπει στην εφαρμογή να απενεργοποιεί τη γραμμή κατάστασης ή να προσθέτει και να αφαιρεί εικονίδια συστήματος.""ορίζεται ως γραμμή κατάστασης"
@@ -1839,6 +1840,8 @@
"Προβολή σε πλήρη οθόνη""Για έξοδο, σύρετε προς τα κάτω από το επάνω μέρος.""Το κατάλαβα"
+ "Περιστρέψτε την οθόνη για καλύτερη προβολή"
+ "Εξέλθετε από τον διαχωρισμό οθόνης για καλύτερη προβολή""Τέλος""Κυκλικό ρυθμιστικό ωρών""Κυκλικό ρυθμιστικό λεπτών"
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index c16471ec471c990f5f69c13f062ad4138834ca6f..d2a2a880fb76fbd812533bcb96ca04db2104b9bc 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -339,6 +339,7 @@
"Can capture gestures performed on the device\'s fingerprint sensor.""Take screenshot""Can take a screenshot of the display."
+ "Preview, %1$s""disable or modify status bar""Allows the app to disable the status bar or add and remove system icons.""be the status bar"
@@ -1839,6 +1840,8 @@
"Viewing full screen""To exit, swipe down from the top.""Got it"
+ "Rotate for a better view"
+ "Exit split screen for a better view""Done""Hours circular slider""Minutes circular slider"
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index b56dc75c99a9e004f446cbdce876fab67c990819..4916056c1ada3a5226ecf526ce5886b0bfbd2901 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -339,6 +339,7 @@
"Can capture gestures performed on the device\'s fingerprint sensor.""Take screenshot""Can take a screenshot of the display."
+ "Preview, %1$s""disable or modify status bar""Allows the app to disable the status bar or add and remove system icons.""be the status bar"
@@ -1839,6 +1840,8 @@
"Viewing full screen""To exit, swipe down from the top.""Got it"
+ "Rotate for a better view"
+ "Exit split screen for a better view""Done""Hours circular slider""Minutes circular slider"
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 95e234bf41bbb21814311492c80305392cd7bf89..f65b407c5e6ffbd70ffe9680dc2f43c83820a276 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -339,6 +339,7 @@
"Can capture gestures performed on the device\'s fingerprint sensor.""Take screenshot""Can take a screenshot of the display."
+ "Preview, %1$s""disable or modify status bar""Allows the app to disable the status bar or add and remove system icons.""be the status bar"
@@ -1839,6 +1840,8 @@
"Viewing full screen""To exit, swipe down from the top.""Got it"
+ "Rotate for a better view"
+ "Exit split screen for a better view""Done""Hours circular slider""Minutes circular slider"
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 7658f16f8ef9044a5dc6474781579c841e0d41ac..64cc0f66789e3127f42b4ddc689cdb96372e99bd 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -339,6 +339,7 @@
"Can capture gestures performed on the device\'s fingerprint sensor.""Take screenshot""Can take a screenshot of the display."
+ "Preview, %1$s""disable or modify status bar""Allows the app to disable the status bar or add and remove system icons.""be the status bar"
@@ -1839,6 +1840,8 @@
"Viewing full screen""To exit, swipe down from the top.""Got it"
+ "Rotate for a better view"
+ "Exit split screen for a better view""Done""Hours circular slider""Minutes circular slider"
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 46cbcf0b0f5f0ce72ba4b86098264c5cd396475c..601a0f3fa4ecaf1becff54659a28a4709d2b96a3 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -339,6 +339,7 @@
"Can capture gestures performed on the device\'s fingerprint sensor.""Take screenshot""Can take a screenshot of the display."
+ "Preview, %1$s""disable or modify status bar""Allows the app to disable the status bar or add and remove system icons.""be the status bar"
@@ -1839,6 +1840,8 @@
"Viewing full screen""To exit, swipe down from the top.""Got it"
+ "Rotate for a better view"
+ "Exit split screen for a better view""Done""Hours circular slider""Minutes circular slider"
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index e71e537a20cd2ea9a34c18f36e24c66af80a176e..f30e2e35906a77ee9209a1dc210a651d25a99318 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -340,6 +340,7 @@
"Capturará los gestos que se hacen en el sensor de huellas dactilares del dispositivo.""Tomar captura de pantalla""Puede tomar capturas de pantalla."
+ "Vista previa, %1$s""desactivar o modificar la barra de estado""Permite que la aplicación inhabilite la barra de estado o que agregue y elimine íconos del sistema.""aparecer en la barra de estado"
@@ -1840,6 +1841,8 @@
"Visualización en pantalla completa""Para salir, desliza el dedo hacia abajo desde la parte superior.""Entendido"
+ "Gira la pantalla para obtener una mejor vista"
+ "Sal de la pantalla dividida para obtener una mejor vista""Listo""Control deslizante circular de horas""Control deslizante circular de minutos"
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index beb1181ebb0a09199d4477eaffe52f9d1af451b6..c7163883d1c43050126ecf5df7290aea3bd39b18 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -340,6 +340,7 @@
"Puede capturar los gestos realizados en el sensor de huellas digitales del dispositivo.""Hacer captura""Puede hacer capturas de la pantalla."
+ "Vista previa, %1$s""inhabilitar o modificar la barra de estado""Permite que la aplicación inhabilite la barra de estado o añada y elimine iconos del sistema.""aparecer en la barra de estado"
@@ -1840,6 +1841,8 @@
"Modo de pantalla completa""Para salir, desliza el dedo de arriba abajo.""Entendido"
+ "Gira la pantalla para verlo mejor"
+ "Sal de la pantalla dividida para verlo mejor""Hecho""Control deslizante circular de horas""Control deslizante circular de minutos"
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 44a18b8383a2249c8c8ab4069316cd1a5fe579bd..9a05ead2b3f16f7e2b3fd4f4109dc478de0e83dc 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -339,6 +339,7 @@
"Teil on võimalik jäädvustada seadme sõrmejäljeanduril tehtud liigutused.""Jäädvusta ekraanipilt""Saab jäädvustada ekraanipildi."
+ "Eelvaade, %1$s""keela või muuda olekuriba""Võimaldab rakendusel keelata olekuriba või lisada ja eemaldada süsteemiikoone.""olekuribana kuvamine"
@@ -1839,6 +1840,8 @@
"Kuvamine täisekraanil""Väljumiseks pühkige ülevalt alla.""Selge"
+ "Pöörake parema vaate jaoks"
+ "Parema vaate jaoks väljuge jagatud ekraanikuvast""Valmis""Ringikujuline tunniliugur""Ringikujuline minutiliugur"
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index e86f6e34c9ff5e52e8221948bcb6787b4d9847ef..1313278d76576a622888e0833ca4252a185dd619 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -339,6 +339,7 @@
"Gailuaren hatz-marken sentsorean egindako keinuak atzeman ditzake.""Pantaila-argazkiak atera.""Pantaila-argazkiak atera ditzake."
+ "Aurrebista, %1$s""desgaitu edo aldatu egoera-barra""Egoera-barra desgaitzea edo sistema-ikonoak gehitzea edo kentzea baimentzen die aplikazioei.""bihurtu egoera-barra"
@@ -1839,6 +1840,8 @@
"Pantaila osoko ikuspegia""Irteteko, pasatu hatza goitik behera.""Ados"
+ "Biratu pantaila ikuspegi hobea lortzeko"
+ "Irten pantaila zatitutik ikuspegi hobea lortzeko""Eginda""Ordua aukeratzeko ikuspegi zirkularra""Minutuak aukeratzeko ikuspegi zirkularra"
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index b24a9f13235c707067466ad9f68c4a1417c6f785..dbbe21de6070e3d2521659ab71b0fadc5bae245c 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -339,6 +339,7 @@
"میتواند اشارههای اجراشده روی حسگر اثرانگشت دستگاه را ثبت کند.""گرفتن نماگرفت""میتواند از نمایشگر نماگرفت بگیرد."
+ "پیشنما، %1$s""غیرفعال کردن یا تغییر نوار وضعیت""به برنامه اجازه میدهد تا نوار وضعیت را غیرفعال کند یا نمادهای سیستم را اضافه یا حذف کند.""نوار وضعیت باشد"
@@ -1839,6 +1840,8 @@
"مشاهده در حالت تمام صفحه""برای خروج، انگشتتان را از بالای صفحه به پایین بکشید.""متوجه شدم"
+ "برای دید بهتر، دستگاه را بچرخانید"
+ "برای دید بهتر، از صفحهٔ دونیمه خارج شوید""تمام""لغزنده دایرهای ساعت""لغزنده دایرهای دقیقه"
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 5842ca66342211d538a31fb59c28a146e92b540b..2a0b5137a5a5de8709f7a8f46441440c29b4ca36 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -339,6 +339,7 @@
"Voi tallentaa laitteen sormenjälkitunnistimelle tehtyjä eleitä.""Ota kuvakaappaus""Voi ottaa kuvakaappauksen näytöstä."
+ "Esikatselu, %1$s""poista tilapalkki käytöstä tai muokkaa tilapalkkia""Antaa sovelluksen poistaa tilapalkin käytöstä ja lisätä tai poistaa järjestelmäkuvakkeita.""sijaita tilapalkissa"
@@ -1839,6 +1840,8 @@
"Koko ruudun tilassa""Sulje palkki pyyhkäisemällä alas ruudun ylälaidasta.""Selvä"
+ "Kierrä, niin saat paremman näkymän"
+ "Poistu jaetulta näytöltä, niin saat paremman näkymän""Valmis""Tuntien ympyränmuotoinen liukusäädin""Minuuttien ympyränmuotoinen liukusäädin"
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 7d31f77cbf44e596e74a6c82e64751ebc93b5472..ac3500ff02975670e0fac07664561e2ea9beccd1 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -340,6 +340,7 @@
"Peut capturer des gestes effectués sur le capteur d\'empreintes digitales de l\'appareil.""Prendre une capture d\'écran""Peut prendre une capture de l\'écran."
+ "Aperçu, %1$s""désactiver ou modifier la barre d\'état""Permet à l\'application de désactiver la barre d\'état, ou d\'ajouter et de supprimer des icônes système.""servir de barre d\'état"
@@ -1840,6 +1841,8 @@
"Affichage plein écran""Pour quitter, balayez vers le bas à partir du haut.""OK"
+ "Faire pivoter pour obtenir un meilleur affichage"
+ "Quitter l\'écran partagé pour obtenir un meilleur affichage""Terminé""Curseur circulaire des heures""Curseur circulaire des minutes"
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 5418762932bf049ded7d081d312dd5ef460ce301..9650987af80d7a53b0c1752887bb3269b1273e0a 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -340,6 +340,7 @@
"Peut enregistrer des gestes effectués sur le lecteur d\'empreinte digitale de l\'appareil.""Prendre une capture d\'écran""Peut prendre des captures d\'écran."
+ "Aperçu, %1$s""Désactivation ou modification de la barre d\'état""Permet à l\'application de désactiver la barre d\'état, ou d\'ajouter et de supprimer des icônes système.""remplacer la barre d\'état"
@@ -1840,6 +1841,8 @@
"Affichage en plein écran""Pour quitter, balayez l\'écran du haut vers le bas.""OK"
+ "Faites pivoter pour mieux voir"
+ "Quittez l\'écran partagé pour mieux voir""OK""Curseur circulaire des heures""Curseur circulaire des minutes"
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 8bb9b8e77673ffaf1d25b2f118ce83e9855dd2b7..dca5c25759df78f79828a10e5c126af8ac01721e 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -339,6 +339,7 @@
"Pode rexistrar os xestos realizados no sensor de impresión dixital do dispositivo.""Facer captura de pantalla""Pode facer capturas de pantalla."
+ "Vista previa, %1$s""desactivar ou modificar a barra de estado""Permite á aplicación desactivar a barra de estado ou engadir e quitar as iconas do sistema.""actuar como a barra de estado"
@@ -1839,6 +1840,8 @@
"Vendo pantalla completa""Para saír, pasa o dedo cara abaixo desde a parte superior.""Entendido"
+ "Xira a pantalla para que se vexa mellor"
+ "Sae da pantalla dividida para que se vexa mellor""Feito""Control desprazable circular das horas""Control desprazable circular dos minutos"
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 34f0617af34bbeaeed80589c07c585c89ed3d923..7d02dd2bde8531c82576120bcdacfb288fba3811 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -339,6 +339,7 @@
"ડિવાઇસના ફિંગરપ્રિન્ટ સેન્સર પર કરવામાં આવેલા સંકેતો કૅપ્ચર કરી શકે છે.""સ્ક્રીનશૉટ લો""ડિસ્પ્લેનો સ્ક્રીનશૉટ લઈ શકે છે."
+ "પ્રીવ્યૂ, %1$s""સ્ટેટસ બારને અક્ષમ કરો અથવા તેમાં ફેરફાર કરો""ઍપ્લિકેશનને સ્ટેટસ બાર અક્ષમ કરવાની અથવા સિસ્ટમ આયકન્સ ઉમેરવા અને દૂર કરવાની મંજૂરી આપે છે.""સ્ટેટસ બારમાં બતાવો"
@@ -1839,6 +1840,8 @@
"પૂર્ણ સ્ક્રીન પર જુઓ""બહાર નીકળવા માટે, ટોચ પરથી નીચે સ્વાઇપ કરો.""સમજાઈ ગયું"
+ "બહેતર વ્યૂ માટે ફેરવો"
+ "બહેતર વ્યૂ માટે, વિભાજિત સ્ક્રીનમાંથી બહાર નીકળો""થઈ ગયું""કલાકનું વર્તુળાકાર સ્લાઇડર""મિનિટનું વર્તુળાકાર સ્લાઇડર"
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 185b95e2e6be893df36f581ec94daf27f48edf63..695405b46863c20f5a23936104247b872072dfae 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -339,6 +339,7 @@
"डिवाइस के फ़िंगरप्रिंट सेंसर पर किए गए हाथ के जेस्चर कैप्चर किए जा सकते हैं.""स्क्रीनशॉट लें""डिसप्ले का स्क्रीनशॉट लिया जा सकता है."
+ "%1$s की झलक""स्टेटस बार को अक्षम करें या बदलें""ऐप को, स्टेटस बार को बंद करने या सिस्टम आइकॉन को जोड़ने और निकालने की अनुमति देता है.""स्टेटस बार को रहने दें"
@@ -1839,6 +1840,8 @@
"आप पूरे स्क्रीन पर देख रहे हैं""बाहर निकलने के लिए, ऊपर से नीचे स्वाइप करें.""ठीक है"
+ "बेहतर व्यू पाने के लिए, डिवाइस की स्क्रीन को घुमाएं"
+ "बेहतर व्यू पाने के लिए, स्प्लिट स्क्रीन मोड बंद करें""हो गया""घंटो का चक्राकार स्लाइडर""मिनटों का चक्राकार स्लाइडर"
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index d19e963d709d06e93a1388f01ede969d0071919a..29ea8b985d7d456e709b129299534ddd3dcde529 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -340,6 +340,7 @@
"Može snimati pokrete izvršene na senzoru otiska prsta na uređaju.""Snimi zaslon""Možete napraviti snimku zaslona."
+ "Pregled, %1$s""onemogućavanje ili izmjena trake statusa""Aplikaciji omogućuje onemogućavanje trake statusa ili dodavanje i uklanjanje sistemskih ikona.""biti traka statusa"
@@ -1840,6 +1841,8 @@
"Gledanje preko cijelog zaslona""Za izlaz prijeđite prstom od vrha prema dolje.""Shvaćam"
+ "Zakrenite kako biste bolje vidjeli"
+ "Zatvorite podijeljeni zaslon kako biste bolje vidjeli""Gotovo""Kružni klizač sati""Kružni klizač minuta"
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 4e0616cc4299525dd6cc42c27b8e339e29637d86..b82064e5a349cae6fb1a7de5cf675d56b2bcc868 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -339,6 +339,7 @@
"Érzékeli az eszköz ujjlenyomat-érzékelőjén végzett kézmozdulatokat.""Képernyőkép készítése""Készíthet képernyőképet a kijelzőről."
+ "Előnézet, %1$s""állapotsor kikapcsolása vagy módosítása""Lehetővé teszi az alkalmazás számára az állapotsor kikapcsolását, illetve rendszerikonok hozzáadását és eltávolítását.""az állapotsor szerepének átvétele"
@@ -1839,6 +1840,8 @@
"Megtekintése teljes képernyőn""Kilépéshez csúsztassa ujját fentről lefelé.""Értem"
+ "Forgassa el a jobb élmény érdekében"
+ "Lépjen ki az osztott képernyős módból a jobb élmény érdekében""Kész""Óra kör alakú csúszkája""Perc kör alakú csúszkája"
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 15565c069082c1e9c4c3dc4bbf19194543bd1441..8de2b7e6221ab07f68e5126393b8981ae1888f12 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -339,6 +339,7 @@
"Կարող է արձանագրել մատնահետքերի սկաների վրա կատարվող ժեստերը""Սքրինշոթի ստեղծում""Կարող է ստեղծել էկրանի սքրինշոթ։"
+ "Նախադիտում, %1$s""անջատել կամ փոփոխել կարգավիճակի գոտին""Թույլ է տալիս հավելվածին անջատել կարգավիճակի գոտին կամ ավելացնել ու հեռացնել համակարգի պատկերակները:""լինել կարգավիճակի գոտի"
@@ -1839,6 +1840,8 @@
"Լիաէկրան դիտում""Դուրս գալու համար վերևից սահահարվածեք դեպի ներքև:""Պարզ է"
+ "Պտտեք՝ դիտակերպը լավացնելու համար"
+ "Դուրս եկեք կիսված էկրանի ռեժիմից՝ դիտակերպը լավացնելու համար""Պատրաստ է""Ժամերի ընտրություն թվատախտակից""Րոպեների ընտրություն թվատախտակից"
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index f196186da82ea45816d8b2c7defe58f40967f67a..506fdf07a15684eb9001df893af90ae00abdfafc 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -339,6 +339,7 @@
"Dapat merekam gestur yang dilakukan di sensor sidik jari perangkat.""Ambil screenshot""Dapat mengambil screenshot tampilan."
+ "Pratinjau, %1$s""nonaktifkan atau ubah bilah status""Mengizinkan apl menonaktifkan bilah status atau menambah dan menghapus ikon sistem.""jadikan bilah status"
@@ -1839,6 +1840,8 @@
"Melihat layar penuh""Untuk keluar, geser layar ke bawah dari atas.""Mengerti"
+ "Putar posisi layar untuk mendapatkan tampilan yang lebih baik"
+ "Keluar dari layar terpisah untuk mendapatkan tampilan yang lebih baik""Selesai""Penggeser putar jam""Penggeser putar menit"
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index b580e571fb0389da8b1481c1a0415a2bae033c11..14575cb5ce4f0db6ca324ee7c1aca3ab087bd97b 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -339,6 +339,7 @@
"Getur fangað bendingar sem eru gerðar á fingrafaralesara tækisins.""Taka skjámynd""Getur tekið skjámynd af skjánum."
+ "Forskoðun, %1$s""slökkva á eða breyta stöðustiku""Leyfir forriti að slökkva á stöðustikunni eða bæta við og fjarlægja kerfistákn.""vera stöðustikan"
@@ -1839,6 +1840,8 @@
"Notar allan skjáinn""Strjúktu niður frá efri brún til að hætta.""Ég skil"
+ "Snúðu til að sjá betur"
+ "Lokaðu skjáskiptingu til að sjá betur""Lokið""Valskífa fyrir klukkustundir""Valskífa fyrir mínútur"
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 6a1ee575497e60a6b7afb7341fa31f7d3825d093..7997752c684f96580ce8dac5c4c28233014a9fdf 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -340,6 +340,7 @@
"È in grado di rilevare i gesti compiuti con il sensore di impronte dei dispositivi.""Acquisire screenshot""Può acquisire uno screenshot del display."
+ "%1$s in anteprima""disattivazione o modifica della barra di stato""Consente all\'applicazione di disattivare la barra di stato o di aggiungere e rimuovere icone di sistema.""ruolo di barra di stato"
@@ -1840,6 +1841,8 @@
"Visualizzazione a schermo intero""Per uscire, scorri dall\'alto verso il basso.""OK"
+ "Ruota per migliorare l\'anteprima"
+ "Esci dallo schermo diviso per migliorare l\'anteprima""Fine""Cursore circolare per le ore""Cursore circolare per i minuti"
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 84708b3df1979f053612fd9a67dc4da1b9691caa..79556938d609ac6ada395e3dd2262590ef19dc4f 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -340,6 +340,7 @@
"אפשרות לזהות תנועות בזמן נגיעה בחיישן טביעות האצבע של המכשיר.""צילום המסך""ניתן לצלם צילום מסך של התצוגה."
+ "תצוגה מקדימה, %1$s""השבתה או שינוי של שורת הסטטוס""מאפשרת לאפליקציה להשבית את שורת הסטטוס או להוסיף ולהסיר סמלי מערכת.""להיות שורת הסטטוס"
@@ -1840,6 +1841,8 @@
"צפייה במסך מלא""כדי לצאת, פשוט מחליקים אצבע מלמעלה למטה.""הבנתי"
+ "מסובבים כדי לראות טוב יותר"
+ "צריך לצאת מהמסך המפוצל כדי לראות טוב יותר""סיום""מחוון שעות מעגלי""מחוון דקות מעגלי"
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index c387d4820eb95662c44e7ea2c4f3ee637ca5c419..ccf6a2dcc9663bfd9e7313d9f025fbb1da541bc3 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -339,6 +339,7 @@
"デバイスの指紋認証センサーで行われた操作をキャプチャできます。""スクリーンショットの撮影""ディスプレイのスクリーンショットを撮影できます。"
+ "プレビュー - %1$s""ステータスバーの無効化や変更""ステータスバーの無効化、システムアイコンの追加や削除をアプリに許可します。""ステータスバーへの表示"
@@ -1839,6 +1840,8 @@
"全画面表示""終了するには、上から下にスワイプします。""OK"
+ "画面を回転させて見やすくしましょう"
+ "分割画面を終了して見やすくしましょう""完了""円形スライダー(時)""円形スライダー(分)"
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 0c8ae270db0a436dd58e3450af76774368b24308..3e695bf18f0ae5d1172c00034895a4011ffbc895 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -339,6 +339,7 @@
"შეუძლია აღბეჭდოს მოწყობილობის თითის ანაბეჭდის სენსორზე განხორციელებული ჟესტები.""ეკრანის ანაბეჭდის გადაღება""შეუძლია ეკრანის ანაბეჭდის გადაღება."
+ "გადახედვა, %1$s""სტატუსის ზოლის გათიშვა ან ცვლილება""აპს შეეძლება სტატუსების ზოლის გათიშვა და სისტემის ხატულების დამატება/წაშლა.""სტატუსის ზოლის ჩანაცვლება"
@@ -1839,6 +1840,8 @@
"სრულ ეკრანზე ნახვა""გამოსვლისათვის, გაასრიალეთ ზემოდან ქვემოთ.""გასაგებია"
+ "შეატრიალეთ უკეთესი ხედისთვის"
+ "უკეთესი ხედვისთვის გამოდით გაყოფილი ეკრანიდან""დასრულდა""საათების წრიული სლაიდერი""წუთების წრიული სლაიდერი"
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index bae2f8a20a4a855b08d8b6a2fee5d241659eb8b3..262d156a1bcf279a8f52f30e03b70242e47592c7 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -339,6 +339,7 @@
"Құрылғының саусақ ізі сенсорында орындалған қимылдарды сақтайды.""Скриншот жасау""Дисплейдің скриншотын жасай аласыз."
+ "Алғы көрініс, %1$s""күйін көрсету тақтасын өшіру немесе өзгерту""Қолданбаға күй жолағын өшіруге немесе жүйелік белгішелерді қосуға және жоюға рұқсат береді.""күй жолағы болу"
@@ -1839,6 +1840,8 @@
"Толық экранда көру""Шығу үшін жоғарыдан төмен қарай сырғытыңыз.""Түсінікті"
+ "Жақсырақ көру үшін бұрыңыз."
+ "Жақсырақ көру үшін экранды бөлу режимінен шығыңыз.""Дайын""Сағаттар айналымының қозғалтқышы""Минут айналымын қозғалтқыш"
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 8a00dbc63e72183b7b4245c6a5b63008abdde48b..191ddf70bdb88ca2f58a6f55dfd4c22d7f5c6de5 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -339,6 +339,7 @@
"អាចចាប់យកចលនាដែលធ្វើនៅលើនៅលើឧបករណ៍ចាប់ស្នាមម្រាមដៃរបស់ឧបករណ៍បាន។""ថតអេក្រង់""អាចថតអេក្រង់នៃផ្ទាំងអេក្រង់បាន។"
+ "មើលសាកល្បង %1$s""បិទ ឬកែរបារស្ថានភាព""ឲ្យកម្មវិធីបិទរបារស្ថានភាព ឬបន្ថែម និងលុបរូបតំណាងប្រព័ន្ធ។""ធ្វើជារបារស្ថានភាព"
@@ -1839,6 +1840,8 @@
"កំពុងមើលពេញអេក្រង់""ដើម្បីចាកចេញ សូមអូសពីលើចុះក្រោម។""យល់ហើយ"
+ "បង្វិលដើម្បីមើលបានកាន់តែច្បាស់"
+ "ចេញពីមុខងារបំបែកអេក្រង់ដើម្បីមើលបានកាន់តែច្បាស់""រួចរាល់""គ្រាប់រំកិលរង្វង់ម៉ោង""គ្រាប់រំកិលរង្វង់នាទី"
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index ec018026a38eed9f4cd73edbab4b4ec96bc7b407..084a8fe85ee573b820f62f61fd3281a4d0144421 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -339,6 +339,7 @@
"ಸಾಧನದ ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್ನಲ್ಲಿ ನಡೆಸಿದ ಗೆಶ್ಚರ್ಗಳನ್ನು ಕ್ಯಾಪ್ಚರ್ ಮಾಡಿ.""ಸ್ಕ್ರೀನ್ಶಾಟ್ ತೆಗೆದುಕೊಳ್ಳಿ""ಪ್ರದರ್ಶನದ ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ತೆಗೆದುಕೊಳ್ಳಬಲ್ಲದು."
+ "ಪೂರ್ವವೀಕ್ಷಣೆ, %1$s""ಸ್ಥಿತಿ ಪಟ್ಟಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ಇಲ್ಲವೇ ಮಾರ್ಪಡಿಸಿ""ಸ್ಥಿತಿ ಪಟ್ಟಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ಅಥವಾ ಸೇರಿಸಲು ಮತ್ತು ಸಿಸ್ಟಂ ಐಕಾನ್ಗಳನ್ನು ತೆಗೆದುಹಾಕಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ.""ಸ್ಥಿತಿ ಪಟ್ಟಿಯಾಗಿರಲು"
@@ -1839,6 +1840,8 @@
"ಪೂರ್ಣ ಪರದೆಯನ್ನು ವೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ""ನಿರ್ಗಮಿಸಲು, ಮೇಲಿನಿಂದ ಕೆಳಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ.""ತಿಳಿಯಿತು"
+ "ಅತ್ಯುತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ತಿರುಗಿಸಿ"
+ "ಅತ್ಯುತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ನಿಂದ ನಿರ್ಗಮಿಸಿ""ಮುಗಿದಿದೆ""ಗಂಟೆಗಳ ವೃತ್ತಾಕಾರ ಸ್ಲೈಡರ್""ನಿಮಿಷಗಳ ವೃತ್ತಾಕಾರ ಸ್ಲೈಡರ್"
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 2ae119d25911a8326988eceeaf923e53c42d5012..d93784b832194f0d6235cc59bcaa372bf9c795d0 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -339,6 +339,7 @@
"기기 지문 센서에서 동작을 캡처합니다.""스크린샷 촬영""디스플레이 스크린샷을 촬영할 수 있습니다."
+ "미리보기, %1$s""상태 표시줄 사용 중지 또는 수정""앱이 상태 표시줄을 사용중지하거나 시스템 아이콘을 추가 및 제거할 수 있도록 허용합니다.""상태 표시줄에 위치"
@@ -1839,6 +1840,8 @@
"전체 화면 모드""종료하려면 위에서 아래로 스와이프합니다.""확인"
+ "카메라 미리보기 화면이 잘 보이도록 회전하세요."
+ "카메라 미리보기 화면이 잘 보이도록 화면 분할을 종료하세요.""완료""시간 원형 슬라이더""분 원형 슬라이더"
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 5bf3dfce5666dba770feac63c0d62c920d6d6ed9..1b307f1d2acaad5aaeccc8857571700522b60f3a 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -339,6 +339,7 @@
"Түзмөктөгү манжа изинин сенсорунда жасалган жаңсоолорду жаздырып алат.""Скриншот тартып алуу""Дисплейдин скриншотун тартып алсаңыз болот."
+ "Алдын ала көрүү, %1$s""абал тилкесин өчүрүү же өзгөртүү""Колдонмого абал тилкесин өчүрүү же тутум сүрөтчөлөрүн кошуу же алып салуу мүмкүнчүлүгүн берет.""абал тилкесинин милдетин аткаруу"
@@ -1839,6 +1840,8 @@
"Толук экран режими""Чыгуу үчүн экранды ылдый сүрүп коюңуз.""Түшүндүм"
+ "Жакшыраак көрүү үчүн буруңуз"
+ "Жакшыраак көрүү үчүн экранды бөлүү режиминен чыгыңыз""Даяр""Саат жебеси""Мүнөт жебеси"
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 40951c27f6d8ed80362617eb06b5cf0e29911282..25352c09dde78d6883306c0b5bbfc98ad8cf97af 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -339,6 +339,7 @@
"ສາມາດບັນທຶກທ່າທາງທີ່ເກີດຂຶ້ນໃນອຸປະກອນເຊັນເຊີລາຍນິ້ວມືໄດ້.""ຖ່າຍຮູບໜ້າຈໍ""ສາມາດຖ່າຍຮູບໜ້າຈໍໄດ້."
+ "ຕົວຢ່າງ, %1$s""ປິດການນນຳໃຊ້ ຫຼື ແກ້ໄຂແຖບສະຖານະ""ອະນຸຍາດໃຫ້ແອັບຯປິດການເຮັດວຽກຂອງແຖບສະຖານະ ຫຼືເພີ່ມ ແລະລຶບໄອຄອນລະບົບອອກໄດ້.""ເປັນແຖບສະຖານະ"
@@ -1839,6 +1840,8 @@
"ການເບິ່ງເຕັມໜ້າຈໍ""ຫາກຕ້ອງການອອກ, ໃຫ້ຮູດຈາກທາງເທິງລົງມາທາງລຸ່ມ.""ໄດ້ແລ້ວ"
+ "ໝຸນເພື່ອມຸມມອງທີ່ດີຂຶ້ນ"
+ "ອອກຈາກແບ່ງໜ້າຈໍເພື່ອມຸມມອງທີ່ດີຂຶ້ນ""ແລ້ວໆ""ໂຕໝຸນປັບຊົ່ວໂມງ""ໂຕໝຸນປັບນາທີ"
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 58a17c8c0171b7b72f23dafb33f91ebe2863a701..56aca55e1111a1854fa3fa6cbe312c32334c03dc 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -341,6 +341,7 @@
"Gali užfiksuoti gestus, atliktus naudojant įrenginio piršto antspaudo jutiklį.""Ekrano kopijos kūrimas""Galima sukurti vaizdo ekrano kopiją."
+ "Peržiūra, %1$s""išjungti ar keisti būsenos juostą""Leidžiama programai neleisti būsenos juostos arba pridėti ir pašalinti sistemos piktogramas.""būti būsenos juosta"
@@ -1841,6 +1842,8 @@
"Peržiūrima viso ekrano režimu""Jei norite išeiti, perbraukite žemyn iš viršaus.""Supratau"
+ "Pasukite, kad geriau matytumėte vaizdą"
+ "Išeikite iš išskaidyto ekrano režimo, kad geriau matytumėte vaizdą""Atlikta""Apskritas valandų šliaužiklis""Apskritas minučių šliaužiklis"
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index ae60301c89d42f18cf78de8693849bc4bb44c028..1f1cd674f65c38c3704860e24da100376272e89f 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -340,6 +340,7 @@
"Var uztvert žestus ierīces pirksta nospieduma sensorā.""Ekrānuzņēmuma izveide""Var izveidot displeja ekrānuzņēmumu."
+ "%1$s (priekšskatījums)""atspējot vai pārveidot statusa joslu""Ļauj lietotnei atspējot statusa joslu vai pievienot un noņemt sistēmas ikonas.""Būt par statusa joslu"
@@ -1840,6 +1841,8 @@
"Skatīšanās pilnekrāna režīmā""Lai izietu, no augšdaļas velciet lejup.""Labi"
+ "Lai uzlabotu skatu, pagrieziet ekrānu."
+ "Lai uzlabotu skatu, izejiet no ekrāna sadalīšanas režīma.""Gatavs""Stundu apļveida slīdnis""Minūšu apļveida slīdnis"
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index d5629d5c6aa6b355751a3ae31b055c721f77f2bc..a6ab8603fd5b83bd84ebc8e8047762445e6cc22b 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -339,6 +339,7 @@
"Може да сними движења што се направени на сензорот за отпечатоци на уредот.""Зачувување слика од екранот""Може да зачува слика од екранот."
+ "Преглед, %1$s""оневозможи или измени статусна лента""Дозволува апликацијата да ја оневозможи статусната лента или да додава или отстранува системски икони.""да стане статусна лента"
@@ -1839,6 +1840,8 @@
"Се прикажува на цел екран""За да излезете, повлечете одозгора надолу.""Сфатив"
+ "Ротирајте за подобар приказ"
+ "За подобар приказ, излезете од поделениот екран""Готово""Приказ на часови во кружно движење""Приказ на минути во кружно движење"
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 3e5933478e71b336756ab1ec54f4d87b5aef53eb..faac4b3e4ced7b249d0f617ff8cd6c15efd3648e 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -339,6 +339,7 @@
"ഉപകരണത്തിന്റെ ഫിംഗർപ്രിന്റ് സെൻസറിൽ ചെയ്ത ജെസ്റ്ററുകൾ ക്യാപ്ചർ ചെയ്യാനാകും.""സ്ക്രീന്ഷോട്ട് എടുക്കുക""ഡിസ്പ്ലേയുടെ സ്ക്രീൻഷോട്ട് എടുക്കാൻ കഴിയും."
+ "പ്രിവ്യൂ, %1$s""സ്റ്റാറ്റസ് ബാർ പ്രവർത്തനരഹിതമാക്കുക അല്ലെങ്കിൽ പരിഷ്ക്കരിക്കുക""നില ബാർ പ്രവർത്തരഹിതമാക്കുന്നതിന് അല്ലെങ്കിൽ സിസ്റ്റം ഐക്കണുകൾ ചേർക്കുന്നതിനും നീക്കംചെയ്യുന്നതിനും അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു.""സ്റ്റാറ്റസ് ബാർ ആയിരിക്കുക"
@@ -1839,6 +1840,8 @@
"പൂർണ്ണ സ്ക്രീനിൽ കാണുന്നു""അവസാനിപ്പിക്കാൻ, മുകളിൽ നിന്ന് താഴോട്ട് സ്വൈപ്പ് ചെയ്യുക.""മനസ്സിലായി"
+ "മികച്ച കാഴ്ചയ്ക്കായി റൊട്ടേറ്റ് ചെയ്യുക"
+ "മികച്ച കാഴ്ചയ്ക്കായി സ്ക്രീൻ വിഭജന മോഡിൽ നിന്ന് പുറത്തുകടക്കുക""പൂർത്തിയാക്കി""ചാക്രികമായി മണിക്കൂറുകൾ ദൃശ്യമാകുന്ന സ്ലൈഡർ""ചാക്രികമായി മിനിറ്റുകൾ ദൃശ്യമാകുന്ന സ്ലൈഡർ"
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index c9eb10058857c07530765fdca40b20b61eee2a00..99933719f040a0cbd31d32dc5b155c96100eed85 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -339,6 +339,7 @@
"Төхөөрөмжийн хурууны хээ мэдрэгчид зангасан зангааг танина.""Дэлгэцийн зургийг дарах""Дэлгэцийн зургийг дарах боломжтой."
+ "Урьдчилан үзэх, %1$s""статус самбарыг идэвхгүй болгох болон өөрчлөх""Апп нь статус самбарыг идэвхгүй болгох эсвэл систем дүрсийг нэмэх, хасах боломжтой.""статусын хэсэг болох"
@@ -1839,6 +1840,8 @@
"Бүтэн дэлгэцээр үзэж байна""Гарахаар бол дээрээс нь доош нь чирнэ үү.""Ойлголоо"
+ "Харагдах байдлыг сайжруулах бол эргүүлнэ үү"
+ "Харагдах байдлыг сайжруулах бол дэлгэцийг хуваах горимоос гарна уу""Дууссан""Цаг гүйлгэгч""Минут гүйлгэгч"
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 45e7f1ccac79a64f380dd12add5d0ed67e834925..30f9d05aab6a0e983aa7634d158bbd6559e78645 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -339,6 +339,7 @@
"डिव्हाइसच्या फिंगरप्रिंट सेंन्सरवरील जेश्चर कॅप्चर करू शकते.""स्क्रीनशॉट घ्या""डिस्प्लेचा स्क्रीनशॉट घेऊ शकतो."
+ "पूर्वावलोकन, %1$s""स्टेटस बार अक्षम करा किंवा सुधारित करा""स्टेटस बार अक्षम करण्यासाठी किंवा सिस्टम चिन्हे जोडण्यासाठी आणि काढण्यासाठी अॅप ला अनुमती देते.""स्टेटस बार होऊ द्या"
@@ -1839,6 +1840,8 @@
"पूर्ण स्क्रीनवर पाहत आहात""बाहेर पडण्यासाठी, वरून खाली स्वाइप करा.""समजले"
+ "अधिक चांगल्या दृश्यासाठी फिरवा"
+ "अधिक चांगल्या दृश्यासाठी स्प्लिट स्क्रीनमधून बाहेर पडा""पूर्ण झाले""तास परिपत्रक स्लायडर""मिनिटे परिपत्रक स्लायडर"
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index c17cb657b6a8818f9b73b4b78cbba42281d2e59b..80c8ae04f526c54db6b4eb2c17d91eea4ea564f2 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -339,6 +339,7 @@
"Boleh menangkap gerak isyarat yang dilakukan pada penderia cap jari peranti.""Ambil tangkapan skrin""Boleh mengambil tangkapan skrin paparan."
+ "Pratonton, %1$s""lumpuhkan atau ubah suai bar status""Membenarkan apl melumpuhkan bar status atau menambah dan mengalih keluar ikon sistem.""jadi bar status"
@@ -1839,6 +1840,8 @@
"Melihat skrin penuh""Untuk keluar, leret dari atas ke bawah.""Faham"
+ "Putar untuk mendapatkan paparan yang lebih baik"
+ "Keluar daripada skrin pisah untuk mendapatkan paparan yang lebih baik""Selesai""Penggelangsar bulatan jam""Penggelangsar bulatan minit"
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index c8198603ed8f996b6f0546f23491b29f9deccfce..97267ef1c9619f588ffc87ba840a3d145a1c46af 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -339,6 +339,7 @@
"စက်ပစ္စည်း၏ လက်ဗွေအာရုံခံကိရိယာတွင် လုပ်ဆောင်ထားသည့် လက်ဟန်များကို မှတ်သားထားနိုင်သည်။""ဖန်သားပြင်ဓာတ်ပုံ ရိုက်ရန်""ဖန်သားပြင်ပြသမှုကို ဓာတ်ပုံရိုက်နိုင်ပါသည်။"
+ "%1$s အစမ်းကြည့်ခြင်း""အခြေအနေပြဘားအား အလုပ်မလုပ်ခိုင်းရန်သို့မဟုတ် မွမ်းမံရန်""အက်ပ်အား အခြေအနေပြ ဘားကို ပိတ်ခွင့် သို့မဟတ် စနစ် အိုင်ကွန်များကို ထည့်ခြင်း ဖယ်ရှားခြင်း ပြုလုပ်ခွင့် ပြုသည်။""အခြေအနေပြ ဘားဖြစ်ပါစေ"
@@ -1839,6 +1840,8 @@
"မျက်နှာပြင်အပြည့် ကြည့်နေသည်""ထွက်ရန် အပေါ်မှ အောက်သို့ ဆွဲချပါ။""ရပါပြီ"
+ "ပိုကောင်းသောမြင်ကွင်းအတွက် လှည့်ပါ"
+ "ပိုကောင်းသောမြင်ကွင်းအတွက် မျက်နှာပြင် ခွဲ၍ပြသခြင်းမှ ထွက်ပါ""ပြီးပါပြီ""နာရီရွေးချက်စရာ""မိနစ်လှည့်သော ရွေ့လျားတန်"
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 2de0c4b9bbb3667674915dcc67b8847d4103e197..549ee0c381bfb89c2f5745b5b29a2caa97e3a502 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -339,6 +339,7 @@
"Kan fange inn bevegelser som utføres på enhetens fingeravtrykkssensor.""Ta skjermdump""Kan ikke ta en skjermdump av skjermen."
+ "Forhåndsvisning, %1$s""deaktivere eller endre statusfeltet""Lar appen deaktivere statusfeltet eller legge til og fjerne systemikoner.""vise appen i statusfeltet"
@@ -1839,6 +1840,8 @@
"Visning i fullskjerm""Sveip ned fra toppen for å avslutte.""Skjønner"
+ "Roter for å få en bedre visning"
+ "Avslutt delt skjerm for å få en bedre visning""Ferdig""Sirkulær glidebryter for timer""Sirkulær glidebryter for minutter"
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index f9333a0105344f7c97f55058bb4dda0a686b3de6..9a414ccb48a87c2eec64d27524b08302af70aa65 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -339,6 +339,7 @@
"यसले यन्त्रको फिंगरप्रिन्टसम्बन्धी सेन्सरमा गरिएका इसाराहरूलाई खिच्न सक्छ।""स्क्रिनसट लिनुहोस्""डिस्प्लेको स्क्रिनसट लिन सकिन्छ।"
+ "प्रिभ्यू, %1$s""स्थिति पट्टिलाई अक्षम वा संशोधित गर्नुहोस्""स्थिति पट्टि असक्षम पार्न वा प्रणाली आइकनहरू थप्न र हटाउन एपलाई अनुमति दिन्छ।""स्टाटस बार हुन दिनुहोस्"
@@ -1839,6 +1840,8 @@
"पूरा पर्दा हेर्दै""बाहिर निस्कन, माथिबाट तल स्वाइप गर्नुहोस्।""बुझेँ"
+ "अझ राम्रो दृश्य हेर्न चाहनुहुन्छ भने रोटेट गर्नुहोस्"
+ "अझ राम्रो दृश्य हेर्न चाहनुहुन्छ भने \"स्प्लिट स्क्रिन\" बाट बाहिरिनुहोस्""भयो""घन्टा गोलाकार स्लाइडर""मिनेट गोलाकार स्लाइडर"
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 2fe7ebc1ff0dc697e9ea961648f435f6e4cda529..801676131968dbb2c83af82d313de3f7b1b67737 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -339,6 +339,7 @@
"Kan gebaren registreren die op de vingerafdruksensor van het apparaat worden getekend.""Screenshot maken""Kan een screenshot van het scherm maken."
+ "Voorbeeld, %1$s""statusbalk uitzetten of wijzigen""Hiermee kan de app de statusbalk uitzetten of systeemiconen toevoegen en verwijderen.""de statusbalk zijn"
@@ -1839,6 +1840,8 @@
"Volledig scherm wordt getoond""Swipe omlaag vanaf de bovenkant van het scherm om af te sluiten.""Ik snap het"
+ "Draai voor een betere weergave"
+ "Sluit het gesplitste scherm voor een betere weergave""Klaar""Ronde schuifregelaar voor uren""Ronde schuifregelaar voor minuten"
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index e958bb8f9fd8519a1d9e2c625d8e30bb6689a0c0..f9502cbd8b3dbeb94574a0425ea140f0691d2633 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -339,6 +339,7 @@
"ଡିଭାଇସ୍ର ଟିପଚିହ୍ନ ସେନସର୍ ଉପରେ ଜେଶ୍ଚର୍ କ୍ୟାପଚର୍ କାର୍ଯ୍ୟ କରାଯାଇପାରିବ।""ସ୍କ୍ରିନସଟ୍ ନିଅନ୍ତୁ""ଡିସପ୍ଲେର ଏକ ସ୍କ୍ରିନସଟ୍ ନିଆଯାଇପାରେ।"
+ "ପ୍ରିଭ୍ୟୁ, %1$s""ଷ୍ଟାଟସ୍ ବାର୍କୁ ଅକ୍ଷମ କିମ୍ୱା ସଂଶୋଧନ କରନ୍ତୁ""ଆପ୍କୁ, ସ୍ଥିତି ବାର୍ ଅକ୍ଷମ କରିବାକୁ କିମ୍ବା ସିଷ୍ଟମ୍ ଆଇକନ୍ ଯୋଡ଼ିବା କିମ୍ବା ବାହାର କରିବାକୁ ଦେଇଥାଏ।""ଷ୍ଟାଟସ୍ ବାର୍ ରହିବାକୁ ଦିଅନ୍ତୁ"
@@ -1600,7 +1601,7 @@
"ଆଙ୍ଗୁଠି ଚିହ୍ନ:""SHA-256 ଆଙ୍ଗୁଠି ଚିହ୍ନ:""SHA-1 ଟିପଚିହ୍ନ:"
- "ସମସ୍ତ ଦେଖନ୍ତୁ"
+ "ସବୁ ଦେଖନ୍ତୁ""ଗତିବିଧି ଚୟନ କରନ୍ତୁ""ଏହାଙ୍କ ସହ ସେୟାର୍ କରନ୍ତୁ""ପଠାଯାଉଛି…"
@@ -1839,6 +1840,8 @@
"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ଦେଖାଯାଉଛି""ବାହାରିବା ପାଇଁ, ଉପରୁ ତଳକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ।""ବୁଝିଗଲି"
+ "ଏକ ଭଲ ଭ୍ୟୁ ପାଇଁ ରୋଟେଟ କରନ୍ତୁ"
+ "ଏକ ଭଲ ଭ୍ୟୁ ପାଇଁ ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନରୁ ବାହାରି ଯାଆନ୍ତୁ""ହୋଇଗଲା""ଘଣ୍ଟା ସର୍କୁଲାର୍ ସ୍ଲାଇଡର୍""ମିନିଟ୍ସ ସର୍କୁଲାର୍ ସ୍ଲାଇଡର୍"
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 5b2c186d71d16c777e3dcdf98e020eeeb5f18e9f..3967c264c13d874175933f6519f08dcd151d9b55 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -339,6 +339,7 @@
"ਡੀਵਾਈਸਾਂ ਦੇ ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ \'ਤੇ ਕੀਤੇ ਗਏ ਸੰਕੇਤਾਂ ਨੂੰ ਕੈਪਚਰ ਕਰ ਸਕਦੇ ਹਨ।""ਸਕ੍ਰੀਨਸ਼ਾਟ ਲਓ""ਡਿਸਪਲੇ ਦਾ ਸਕ੍ਰੀਨਸ਼ਾਟ ਲੈ ਸਕਦੀ ਹੈ।"
+ "ਪੂਰਵ-ਝਲਕ ਦੇਖੋ, %1$s""ਸਥਿਤੀ ਪੱਟੀ ਬੰਦ ਕਰੋ ਜਾਂ ਸੰਸ਼ੋਧਿਤ ਕਰੋ""ਐਪ ਨੂੰ ਸਥਿਤੀ ਪੱਟੀ ਨੂੰ ਚਾਲੂ ਕਰਨ ਜਾਂ ਸਿਸਟਮ ਪ੍ਰਤੀਕਾਂ ਨੂੰ ਜੋੜਨ ਅਤੇ ਹਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।""ਸਥਿਤੀ ਪੱਟੀ ਬਣਨ ਦਿਓ"
@@ -1839,6 +1840,8 @@
"ਪੂਰੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦੇਖੋ""ਬਾਹਰ ਜਾਣ ਲਈ, ਉਪਰੋਂ ਹੇਠਾਂ ਸਵਾਈਪ ਕਰੋ।""ਸਮਝ ਲਿਆ"
+ "ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਅਨੁਭਵ ਲਈ ਘੁਮਾਓ"
+ "ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਅਨੁਭਵ ਲਈ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਤੋਂ ਬਾਹਰ ਆਓ""ਹੋ ਗਿਆ""ਘੰਟੇ ਸਰਕੁਲਰ ਸਲਾਈਡਰ""ਮਿੰਟ ਸਰਕੁਲਰ ਸਲਾਈਡਰ"
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 7a08a3edaee71e35104865e69f4e1f15d496fc3f..cb5361dbc60fa0377e659c9e274329ac0cbe9b38 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -341,6 +341,7 @@
"Może przechwytywać gesty wykonywane na czytniku linii papilarnych w urządzeniu.""Robienie zrzutu ekranu""Może robić zrzuty ekranu wyświetlacza."
+ "Podgląd, %1$s""wyłączanie lub zmienianie paska stanu""Pozwala aplikacji na wyłączanie paska stanu oraz dodawanie i usuwanie ikon systemowych.""działanie jako pasek stanu"
@@ -1841,6 +1842,8 @@
"Włączony pełny ekran""Aby wyjść, przesuń palcem z góry na dół.""OK"
+ "Obróć, aby lepiej widzieć"
+ "Zamknij podzielony ekran, aby lepiej widzieć""Gotowe""Kołowy suwak godzin""Kołowy suwak minut"
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 2a72af1bee93691a8cc4305dbf829a35f18d8654..aa11e2b86e779b9b8b7d13be4bdf27ec5b58bece 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -340,6 +340,7 @@
"Pode captar gestos realizados no sensor de impressão digital do dispositivo.""Fazer uma captura de tela""Pode fazer uma captura de tela."
+ "Visualização, %1$s""desativar ou modificar a barra de status""Permite que o app desative a barra de status ou adicione e remova ícones do sistema.""ser a barra de status"
@@ -1840,6 +1841,8 @@
"Visualização em tela cheia""Para sair, deslize de cima para baixo.""Entendi"
+ "Gire a tela para ter uma visualização melhor"
+ "Saia da tela dividida para ter uma visualização melhor""Concluído""Controle deslizante circular das horas""Controle deslizante circular dos minutos"
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 4772d11986280075e5398afcb3c1023549fe18e1..bc771cdc8bbd4ac9fb1002007f738049403ce430 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -340,6 +340,7 @@
"Pode capturar gestos realizados no sensor de impressões digitais do dispositivo.""Fazer captura de ecrã""É possível tirar uma captura de ecrã."
+ "Pré-visualização, %1$s""desativar ou modificar barra de estado""Permite à app desativar a barra de estado ou adicionar e remover ícones do sistema.""ser apresentada na barra de estado"
@@ -1840,6 +1841,8 @@
"Visualização de ecrã inteiro""Para sair, deslize rapidamente para baixo a partir da parte superior.""OK"
+ "Rode para uma melhor visualização"
+ "Saia do ecrã dividido para uma melhor visualização""Concluído""Controlo de deslize circular das horas""Controlo de deslize circular dos minutos"
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 2a72af1bee93691a8cc4305dbf829a35f18d8654..aa11e2b86e779b9b8b7d13be4bdf27ec5b58bece 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -340,6 +340,7 @@
"Pode captar gestos realizados no sensor de impressão digital do dispositivo.""Fazer uma captura de tela""Pode fazer uma captura de tela."
+ "Visualização, %1$s""desativar ou modificar a barra de status""Permite que o app desative a barra de status ou adicione e remova ícones do sistema.""ser a barra de status"
@@ -1840,6 +1841,8 @@
"Visualização em tela cheia""Para sair, deslize de cima para baixo.""Entendi"
+ "Gire a tela para ter uma visualização melhor"
+ "Saia da tela dividida para ter uma visualização melhor""Concluído""Controle deslizante circular das horas""Controle deslizante circular dos minutos"
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 03ec760141a547b7d3164bf4b4d2ae8dc6d554bd..d7d6ef3ecf42fdcc71005cee7ac7d5b7dd7095ff 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -340,6 +340,7 @@
"Poate reda gesturile făcute pe senzorul de amprentă al dispozitivului.""Fă o captură de ecran""Poate face o captură de ecran."
+ "Previzualizare, %1$s""dezactivare sau modificare bare de stare""Permite aplicației să dezactiveze bara de stare sau să adauge și să elimine pictograme de sistem.""să fie bara de stare"
@@ -1840,6 +1841,8 @@
"Vizualizare pe ecran complet""Pentru a ieși, glisează de sus în jos.""Am înțeles"
+ "Rotește pentru o previzualizare mai bună"
+ "Ieși din ecranul împărțit pentru o previzualizare mai bună""Terminat""Selector circular pentru ore""Selector circular pentru minute"
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index bb081a0406f06c4f2a43eb1efd5f27b4de6871ad..5ddac36f115bde84097e4b93686bd77cfd338668 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -341,6 +341,7 @@
"Использовать сканер отпечатков пальцев для дополнительных жестов.""Создавать скриншоты""Создавать снимки экрана."
+ "%1$s: предпросмотр""Отключение/изменение строки состояния""Приложение сможет отключать строку состояния, а также добавлять и удалять системные значки.""Замена строки состояния"
@@ -1841,6 +1842,8 @@
"Полноэкранный режим""Чтобы выйти, проведите по экрану сверху вниз.""ОК"
+ "Поверните, чтобы лучше видеть."
+ "Выйдите из режима разделения экрана, чтобы лучше видеть.""Готово""Выбор часов на циферблате""Выбор минут на циферблате"
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 3f62b9c387c368dfd45c0e714ab719cd2a7c05dd..196d57ac742e7c2e35dfc69d7cdef456afebf80d 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -339,6 +339,7 @@
"උපාංගයෙහි ඇඟිලි සලකුණු සංවේදකය මත සිදු කරන ඉංගිත ග්රහණය කළ හැකිය.""තිර රුව ගන්න""සංදර්ශකයේ තිර රුවක් ගැනීමට හැකිය."
+ "පෙරදසුන, %1$s""තත්ව තීරුව අබල කරන්න හෝ වෙනස් කරන්න""තත්ව තීරුව අක්රිය කිරීමට හෝ පද්ධති නිරූපක එකතු හෝ ඉවත් කිරීමට යෙදුමට අවසර දේ.""තත්ත්ව තීරුව බවට පත්වීම"
@@ -1839,6 +1840,8 @@
"මුළු තිරය බලමින්""ඉවත් වීමට, ඉහළ සිට පහළට ස්වයිප් කරන්න""වැටහුණි"
+ "වඩා හොඳ දසුනක් සඳහා කරකවන්න"
+ "වඩා හොඳ දර්ශනයක් සඳහා බෙදුම් තිරයෙන් පිටවන්න""අවසන්""පැය කවාකාර සර්පනය""මිනිත්තු කවාකාර සර්පනය"
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index c513f9cfa63a3157649a4c11f18f0d9ad7b6edeb..66ac4728abb680b65cce41dfaad89f37fa1c8c9f 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -341,6 +341,7 @@
"Dokáže zaznamenať gestá na senzore odtlačkov prstov zariadenia.""Vytvoriť snímku obrazovky""Je možné vytvoriť snímku obrazovky."
+ "Ukážka, %1$s""zakázanie alebo zmeny stavového riadka""Umožňuje aplikácii vypnúť stavový riadok alebo pridať a odstrániť systémové ikony.""vydávanie sa za stavový riadok"
@@ -1841,6 +1842,8 @@
"Zobrazenie na celú obrazovku""Ukončíte potiahnutím zhora nadol.""Dobre"
+ "Otočte zariadenie pre lepšie zobrazenie"
+ "Ukončite rozdelenú obrazovku pre lepšie zobrazenie""Hotovo""Kruhový posúvač hodín""Kruhový posúvač minút"
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 78e3bf3e487393dae5a5734c85866c4e4bf53fbc..b3d5f6d4ee02ce96efbe8070be93636631a9a9ad 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -341,6 +341,7 @@
"Prepoznava poteze, narejene po tipalu prstnih odtisov naprave.""Ustvarjanje posnetka zaslona""Lahko naredi posnetek zaslona."
+ "Predogled, %1$s""onemogočanje ali spreminjanje vrstice stanja""Aplikacijam omogoča onemogočenje vrstice stanja ali dodajanje in odstranjevanje ikon sistema.""postane vrstica stanja"
@@ -1841,6 +1842,8 @@
"Vklopljen je celozaslonski način""Zaprete ga tako, da z vrha s prstom povlečete navzdol.""Razumem"
+ "Zasukajte za boljši pregled."
+ "Zaprite razdeljeni zaslon za boljši pregled.""Dokončano""Okrogli drsnik za ure""Okrogli drsnik za minute"
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 8aff5dbb4b11fbcd62da27a2511d8e79742bb6e7..270ae48db367c428da1d10a3b06e2c10f570125e 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -339,6 +339,7 @@
"Mund të regjistrojë gjestet e kryera në sensorin e gjurmës së gishtit të pajisjes.""Nxirr një pamje të ekranit""Mund të nxjerrë një pamje e ekranit."
+ "Versioni paraprak, %1$s""çaktivizo ose modifiko shiritin e statusit""Lejon aplikacionin të çaktivizojë shiritin e statusit dhe të heqë ikonat e sistemit.""të bëhet shiriti i statusit"
@@ -1839,6 +1840,8 @@
"Po shikon ekranin e plotë""Për të dalë, rrëshqit nga lart poshtë.""E kuptova"
+ "Rrotullo për një pamje më të mirë"
+ "Dil nga ekrani i ndarë për një pamje më të mirë""U krye""Rrëshqitësi rrethor i orëve""Rrëshqitësi rrethor i minutave"
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 47db1e3e2aaf6050132f351f14af8d6c41174287..37727cf44dc6d4748811efafade0d09896eea1b2 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -340,6 +340,7 @@
"Може да региструје покрете на сензору за отисак прста на уређају.""Направи снимак екрана""Може да направи снимак екрана."
+ "Преглед, %1$s""онемогућавање или измена статусне траке""Дозвољава апликацији да онемогући статусну траку или да додаје и уклања системске иконе.""функционисање као статусна трака"
@@ -1840,6 +1841,8 @@
"Приказује се цео екран""Да бисте изашли, превуците надоле одозго.""Важи"
+ "Ротирајте ради бољег приказа"
+ "Изађите из подељеног екрана ради бољег приказа""Готово""Кружни клизач за сате""Кружни клизач за минуте"
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 5acbfba28e20420246e73d3c8e5c72329d19a7d9..1cb246a4ceccf99053174168a20a5b03e1259354 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -339,6 +339,7 @@
"Kan registrera rörelser som utförs med hjälp av enhetens fingeravtryckssensor.""Ta skärmbild""Kan ta en skärmbild av skärmen."
+ "Förhandsgranskar %1$s""inaktivera eller ändra statusfält""Tillåter att appen inaktiverar statusfältet eller lägger till och tar bort systemikoner.""visas i statusfältet"
@@ -1839,6 +1840,8 @@
"Visar på fullskärm""Svep nedåt från skärmens överkant för att avsluta.""OK"
+ "Rotera för att få en bättre vy"
+ "Stäng delad skärm för att få en bättre vy""Klart""Cirkelreglage för timmar""Cirkelreglage för minuter"
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 046ccf181568f859c3c5dfe0e77a4fd9927efadf..35d233af6ab9b63e10b57863b6f8f1367d03f0dd 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -339,6 +339,7 @@
"Inaweza kurekodi ishara zinazotekelezwa kwenye kitambua alama ya kidole.""Piga picha ya skrini""Inaweza kupiga picha ya skrini ya onyesho."
+ "Onyesho la kukagua, %1$s""zima au rekebisha mwambaa hali""Inaruhusu programu kulemaza upau wa hali au kuongeza na kutoa aikoni za mfumo.""kuwa sehemu ya arifa"
@@ -1839,6 +1840,8 @@
"Unatazama skrini nzima""Ili kuondoka, telezesha kidole kutoka juu hadi chini.""Nimeelewa"
+ "Zungusha ili upate mwonekano bora"
+ "Funga skrini iliyogawanywa ili upate mwonekano bora""Imekamilika""Kitelezi cha mviringo wa saa""Kitelezi cha mviringo wa dakika"
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index dcc1c5a2ed3f7f99121c66f2fd326488da3703b0..9a99d560d0004bb6500840f517bd3100755372fd 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -339,6 +339,7 @@
"சாதனத்தின் கைரேகை சென்சார்மேல் செய்யப்படும் சைகைகளைக் கேப்ட்சர் செய்ய முடியும்.""ஸ்கிரீன்ஷாட்டை எடுக்கும்""டிஸ்ப்ளேவை ஸ்கிரீன்ஷாட் எடுக்க முடியும்."
+ "மாதிரிக்காட்சி, %1$s""நிலைப் பட்டியை முடக்குதல் அல்லது மாற்றுதல்""நிலைப் பட்டியை முடக்க அல்லது முறைமையில் ஐகான்களைச் சேர்க்க மற்றும் அகற்ற ஆப்ஸை அனுமதிக்கிறது.""நிலைப் பட்டியில் இருக்கும்"
@@ -1839,6 +1840,8 @@
"முழுத் திரையில் காட்டுகிறது""வெளியேற, மேலிருந்து கீழே ஸ்வைப் செய்யவும்""புரிந்தது"
+ "சிறந்த காட்சிக்கு சுழற்றுங்கள்"
+ "சிறந்த காட்சிக்கு திரைப் பிரிப்புப் பயன்முறையில் இருந்து வெளியேறுங்கள்""முடிந்தது""மணிநேர வட்ட வடிவ ஸ்லைடர்""நிமிடங்களுக்கான வட்டவடிவ ஸ்லைடர்"
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 6a374fd916051d5bf436665e9a24de6a0adc857f..5beee424a5a7a3421e861082e0fddc303367c872 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -339,6 +339,7 @@
"పరికర వేలిముద్ర సెన్సార్లో ఉపయోగించిన సంజ్ఞలను క్యాప్చర్ చేయవచ్చు.""స్క్రీన్షాట్ను తీయండి""డిస్ప్లే యొక్క స్క్రీన్షాట్ తీసుకోవచ్చు."
+ "ప్రివ్యూ, %1$s""స్టేటస్ బార్ను డిజేబుల్ చేయడం లేదా మార్చడం""స్టేటస్ బార్ను డిజేబుల్ చేయడానికి లేదా సిస్టమ్ చిహ్నాలను జోడించడానికి మరియు తీసివేయడానికి యాప్ను అనుమతిస్తుంది.""స్టేటస్ పట్టీగా ఉండటం"
@@ -1839,6 +1840,8 @@
"ఫుల్-స్క్రీన్లో వీక్షిస్తున్నారు""నిష్క్రమించడానికి, పై నుండి క్రిందికి స్వైప్ చేయండి.""అర్థమైంది"
+ "మెరుగైన వీక్షణ కోసం తిప్పండి"
+ "మెరుగైన వీక్షణ కోసం స్ప్లిట్ స్క్రీన్ నుండి నిష్క్రమించండి""పూర్తయింది""గంటల వృత్తాకార స్లయిడర్""నిమిషాల వృత్తాకార స్లయిడర్"
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 9c46a0aa0133f907f74e9be2bd27211cb4cd77aa..c22f19c5d55b253e615c04cc89caf3f856eb983c 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -339,6 +339,7 @@
"สามารถจับท่าทางสัมผัสที่เกิดขึ้นบนเซ็นเซอร์ลายนิ้วมือของอุปกรณ์""ถ่ายภาพหน้าจอ""ถ่ายภาพหน้าจอได้"
+ "ตัวอย่าง %1$s""ปิดการใช้งานหรือแก้ไขแถบสถานะ""อนุญาตให้แอปพลิเคชันปิดใช้งานแถบสถานะหรือเพิ่มและนำไอคอนระบบออก""เป็นแถบสถานะ"
@@ -1839,6 +1840,8 @@
"กำลังดูแบบเต็มหน้าจอ""หากต้องการออก ให้เลื่อนลงจากด้านบน""รับทราบ"
+ "หมุนเพื่อรับมุมมองที่ดียิ่งขึ้น"
+ "ออกจากโหมดแยกหน้าจอเพื่อรับมุมมองที่ดียิ่งขึ้น""เสร็จสิ้น""ตัวเลื่อนหมุนระบุชั่วโมง""ตัวเลื่อนหมุนระบุนาที"
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index b3748f64f51bdfef446c7e60ee2a6a4d64875c5d..b0f85b09bf1dce701159585a3c23e09e55ac69e1 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -339,6 +339,7 @@
"Makukunan ang mga galaw na ginawa sa sensor para sa fingerprint ng device.""Kumuha ng screenshot""Puwedeng kumuha ng screenshot ng display."
+ "Preview, %1$s""i-disable o baguhin ang status bar""Pinapayagan ang app na i-disable ang status bar o magdagdag at mag-alis ng mga icon ng system.""maging status bar"
@@ -1839,6 +1840,8 @@
"Panonood sa full screen""Upang lumabas, mag-swipe mula sa itaas pababa.""Nakuha ko"
+ "I-rotate para sa mas magandang view"
+ "Lumabas sa split screen para sa mas magandang view""Tapos na""Pabilog na slider ng mga oras""Pabilog na slider ng mga minuto"
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 816f89ab4c94896f5e3267efec3116d4207d437a..56e8f9605ab448bcf19bd6385224285ee2568860 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -339,6 +339,7 @@
"Cihazın parmak izi sensörlerinde gerçekleştirilen hareketleri yakalayabilir.""Ekran görüntüsü al""Ekran görüntüsü alınabilir."
+ "Önizleme, %1$s""durum çubuğunu devre dışı bırak veya değiştir""Uygulamaya, durum çubuğunu devre dışı bırakma ve sistem simgelerini ekleyip kaldırma izni verir.""durum çubuğunda olma"
@@ -1839,6 +1840,8 @@
"Tam ekran olarak görüntüleme""Çıkmak için yukarıdan aşağıya doğru hızlıca kaydırın.""Anladım"
+ "Daha iyi bir görünüm elde etmek için ekranı döndürün"
+ "Daha iyi bir görünüm elde etmek için bölünmüş ekrandan çıkın""Bitti""Saat kaydırma çemberi""Dakika kaydırma çemberi"
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 7f5cc3314b8ae1d86e1a48df884ef782bb56f244..c1bb418ad6ccf4083624d059edd910c5298ecf9c 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -341,6 +341,7 @@
"Може фіксувати жести на сканері відбитків пальців.""Робити знімки екрана""Може робити знімки екрана."
+ "%1$s (попередній перегляд)""вимикати чи змін. рядок стану""Дозволяє програмі вимикати рядок стану чи додавати та видаляти піктограми системи.""відображатися як рядок стану"
@@ -1841,6 +1842,8 @@
"Перегляд на весь екран""Щоб вийти, проведіть пальцем зверху вниз.""OK"
+ "Оберніть для кращого огляду"
+ "Для кращого огляду вийдіть із режиму розділення екрана""Готово""Вибір годин на циферблаті""Вибір хвилин на циферблаті"
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 4486c55097b443a06b14db231e38c0d91495f57d..2e7c6db9f925b29ddd0ec56b4cdc3bd73530b410 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -339,6 +339,7 @@
"آلہ کے فنگر پرنٹ سینسر پر کیے گئے اشاروں کو کیپچر کر سکتا ہے۔""اسکرین شاٹ لیں""ڈسپلے کا اسکرین شاٹ لیا جا سکتا ہے۔"
+ "پیش منظر، %1$s""اسٹیٹس بار کو غیر فعال یا اس میں ترمیم کریں""ایپ کو اسٹیٹس بار غیر فعال کرنے یا سسٹم آئیکنز شامل کرنے اور ہٹانے کی اجازت دیتا ہے۔""بطور اسٹیٹس بار کام لیں"
@@ -1839,6 +1840,8 @@
"پوری اسکرین میں دیکھ رہے ہیں""خارج ہونے کیلئے اوپر سے نیچے سوائپ کریں۔""سمجھ آ گئی"
+ "بہتر منظر کے لیے گھمائیں"
+ "بہتر منظر کے لیے اسپلٹ اسکرین سے باہر نکلیں""ہو گیا""گھنٹوں کا سرکلر سلائیڈر""منٹس سرکلر سلائیڈر"
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index a569cdf510ffef1f3c2c762422365aa1b29569af..d1e86f8435633aedb8443004f270851e75431bed 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -339,6 +339,7 @@
"Barmoq izi skanerida kiritilgan ishoralarni taniy oladi.""Skrinshot olish""Ekrandan skrinshot olishi mumkin."
+ "Razm solish, %1$s""holat panelini o‘zgartirish yoki o‘chirish""Ilova holat panelini o‘chirib qo‘yishi hamda tizim ikonkalarini qo‘shishi yoki olib tashlashi mumkin.""holat qatorida ko‘rinishi"
@@ -1839,6 +1840,8 @@
"Butun ekranli rejim""Chiqish uchun tepadan pastga torting.""OK"
+ "Yaxshiroq koʻrish uchun kamerani buring"
+ "Yaxshiroq koʻrish uchun ajratilgan ekran rejimidan chiqing""Tayyor""Doiradan soatni tanlang""Doiradan daqiqani tanlang"
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 2ed1c8b54578ae0a7e99626c7b00971397890f4c..17e1257244c5e2a70db36334212dc7238ebdabf6 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -339,6 +339,7 @@
"Có thể ghi lại các cử chỉ được thực hiện trên cảm biến vân tay của thiết bị.""Chụp ảnh màn hình""Có thể chụp ảnh màn hình."
+ "Bản xem trước, %1$s""vô hiệu hóa hoặc sửa đổi thanh trạng thái""Cho phép ứng dụng vô hiệu hóa thanh trạng thái hoặc thêm và xóa biểu tượng hệ thống.""trở thành thanh trạng thái"
@@ -1839,6 +1840,8 @@
"Xem toàn màn hình""Để thoát, hãy vuốt từ trên cùng xuống dưới.""OK"
+ "Xoay để xem dễ hơn"
+ "Thoát chế độ chia đôi màn hình để xem dễ hơn""Xong""Thanh trượt giờ hình tròn""Thanh trượt phút hình tròn"
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index c0ecfd8c804318a8e08c1a93e0eab841ca1235f6..aac8d01eeb5d2e18db60cfc74e06091e6e08c36d 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -339,6 +339,7 @@
"可以捕捉在设备指纹传感器上执行的手势。""截取屏幕截图""可截取显示画面的屏幕截图。"
+ "预览,%1$s""停用或修改状态栏""允许应用停用状态栏或者增删系统图标。""用作状态栏"
@@ -1839,6 +1840,8 @@
"目前处于全屏模式""要退出,请从顶部向下滑动。""知道了"
+ "旋转可改善预览效果"
+ "退出分屏可改善预览效果""完成""小时转盘""分钟转盘"
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 41db5ad3004b5ead4f7d6f91f9d28234653558a3..4e96e4e58d9d2f5b5910c2e52cd452bea052746c 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -339,6 +339,7 @@
"可以擷取在裝置指紋感應器上執行的手勢。""擷取螢幕擷圖""可以擷取螢幕截圖。"
+ "預覽,%1$s""停用或修改狀態列""允許應用程式停用狀態列,並可新增或移除系統圖示。""成為狀態列"
@@ -1839,6 +1840,8 @@
"開啟全螢幕""由頂部向下滑動即可退出。""知道了"
+ "旋轉以改善預覽效果"
+ "退出分割螢幕,以改善預覽效果""完成""小時環形滑桿""分鐘環形滑桿"
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 7ef638f162453fa9294eba88b42d5efc8b9364b4..3f7e50df15c4085efcd4154c03b1c26d1a0d00f9 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -339,6 +339,7 @@
"可以擷取使用者對裝置的指紋感應器執行的手勢。""擷取螢幕畫面""可以擷取螢幕畫面。"
+ "預覽,%1$s""停用或變更狀態列""允許應用程式停用狀態列,並可新增或移除系統圖示。""以狀態列顯示"
@@ -1839,6 +1840,8 @@
"以全螢幕檢視""如要退出,請從畫面頂端向下滑動。""知道了"
+ "旋轉螢幕以瀏覽完整的檢視畫面"
+ "結束分割畫面以全螢幕瀏覽""完成""小時數環狀滑桿""分鐘數環狀滑桿"
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 9a21b131c155532d836c04a34dd4958ed3c3d2a5..b0acffc7395158ff018644335d19e4497dfa9215 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -339,6 +339,7 @@
"Ingathatha ukuthinta okwenziwe kunzwa yezigxivizo zeminwe zedivayisi.""Thatha isithombe-skrini""Ingathatha isithombe-skrini sesiboniso"
+ "Hlola kuqala, %1$s""khubaza noma guqula ibha yomumo""Ivumela uhlelo lokusebenza ukuthi yenze umudwa ochaza ngesimo ukuthi ungasebenzi noma ukufaka noma ukukhipha izithonjana zohlelo.""yiba yibha yesimo"
@@ -1839,6 +1840,8 @@
"Ukubuka isikrini esigcwele""Ukuze uphume, swayiphela phansi kusuka phezulu.""Ngiyitholile"
+ "Zungezisa ukuze uthole ukubuka okungcono"
+ "Phuma ekuhlukaniseni isikrini ukuze ubuke kangcono""Kwenziwe""Amahora weslayidi esiyindingilizi""Amaminithi weslayidi esiyindingilizi"
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 4b27bf2849fb41bbcca3269986dd29b76a5ec425..fe296c70409572644be20f787ff9017d377c3c5f 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -18,7 +18,6 @@
truefalsetrue
- falsetruetruefalse
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index d5875f547e91cbdd3d31e3bc1329ede77aeedff3..b83d3b4ea298f30d831da5c868c84b42e5325969 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -150,6 +150,8 @@
#757575@color/notification_default_color
+ 0.38
+ 0.12@color/notification_secondary_text_color_current
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index ea6e1f182fbff9765be7f752d5c07a8acc32f3a6..a99ba152510e994a9664220ef7772470c379ca9e 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -72,8 +72,8 @@
.70.60
- 0.10
- 0.10
+ 0.5
+ 0.50.10
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 031a6669a3c60b798fc2deccf2ef2795508e6ce5..6cb48db10590c444c0fc107ceb8565d75eaa8252 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -560,6 +560,10 @@
rotations as the default behavior. -->
false
+
+ false
+
false
+
+
+
+
+
@@ -652,10 +666,34 @@
-->
+
+
+
+
+
false
+
+ false
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
@@ -687,20 +725,22 @@
mode. -->
1000
+
+ 1000
+
true
-
+
@@ -729,6 +769,11 @@
we rely on gravity to determine the effective orientation. -->
true
+
+ false
+
true
+
+ 3000
+
+
+
+
+
false
-
- true
+
+ false7
@@ -4947,9 +5000,8 @@
true
-
- false
+
+ false
+
+
+
+
+
@@ -5271,6 +5328,10 @@
false
+
+ false
+
false
+
+ false
+
false
@@ -5343,10 +5407,20 @@
split screen. -->
false
+
+ false
+
false
+
+ false
+
false
@@ -6024,4 +6098,17 @@
+
+
+ false
+
+
+ -1
+
+
+ false
+
+
+ false
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5a1e0e8d6d47983230ac141ca7f139de8db8b5d7..681163dd6c710761a4b825e4c53779b72983f74f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -968,6 +968,11 @@
Can take a screenshot of the display.
+
+
+
+ Preview, %1$s
+
@@ -5153,6 +5158,14 @@
Got it
+
+ Rotate for a better view
+
+
+ Exit split screen for a better view
+
Done
@@ -2720,7 +2728,7 @@
-
+
@@ -2835,7 +2843,6 @@
-
@@ -3299,7 +3306,10 @@
+
+
+
@@ -3351,6 +3361,7 @@
+
@@ -3432,6 +3443,12 @@
+
+
+
+
@@ -4011,9 +4028,15 @@
+
+
+
+
+
+
@@ -4194,6 +4217,7 @@
+
@@ -4268,6 +4292,8 @@
+
+
@@ -4486,6 +4512,7 @@
+
@@ -4494,9 +4521,12 @@
+
+
+
@@ -4912,5 +4942,8 @@
+
+
+
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3e4b1cc87ef8e0bab0ad2af6d245daeaa19431b4..e96c64206c45191ae81104a861168d38e088667f 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1414,6 +1414,7 @@
+
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index ed2b10117308b87d6aacb2ad2a18f1d51784dea9..3768063f2a915cbe725aefc9e70fdb8166328956 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -368,4 +368,20 @@ public class PropertyInvalidatedCacheTests {
PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
assertEquals(n1, "cache_key.bluetooth.get_state");
}
+
+ @Test
+ public void testOnTrimMemory() {
+ TestCache cache = new TestCache(MODULE, "trimMemoryTest");
+ // The cache is not active until it has been invalidated once.
+ cache.invalidateCache();
+ // Populate the cache with six entries.
+ for (int i = 0; i < 6; i++) {
+ cache.query(i);
+ }
+ // The maximum number of entries in TestCache is 4, so even though six entries were
+ // created, only four are retained.
+ assertEquals(4, cache.size());
+ PropertyInvalidatedCache.onTrimMemory();
+ assertEquals(0, cache.size());
+ }
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index eae1bbc930d4abdb0202e916ff0d3e811a91beca..17ed4c478350984becc6429b1346f0abfacba737 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -15,6 +15,8 @@
*/
package android.view.contentcapture;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertThrows;
@@ -54,4 +56,19 @@ public class ContentCaptureManagerTest {
assertThrows(NullPointerException.class, () -> manager.removeData(null));
}
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void testFlushViewTreeAppearingEventDisabled_setAndGet() {
+ final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
+ final ContentCaptureOptions options = new ContentCaptureOptions(null);
+ final ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mockService, options);
+
+ assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
+ manager.setFlushViewTreeAppearingEventDisabled(true);
+ assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isTrue();
+ manager.setFlushViewTreeAppearingEventDisabled(false);
+ assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
+ }
}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f370ebd9454551ed94c6e8b1a0fad5af9756bbfe..9d6b29e5c072a923207c8dae1415ffbd1bfbaf24 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -17,6 +17,7 @@
package android.window;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
@@ -60,8 +61,8 @@ public class WindowOnBackInvokedDispatcherTest {
private OnBackAnimationCallback mCallback1;
@Mock
private OnBackAnimationCallback mCallback2;
- @Mock
- private BackEvent mBackEvent;
+ private final BackMotionEvent mBackEvent = new BackMotionEvent(
+ 0, 0, 0, BackEvent.EDGE_LEFT, null);
@Before
public void setUp() throws Exception {
@@ -89,12 +90,12 @@ public class WindowOnBackInvokedDispatcherTest {
captor.capture());
captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted(mBackEvent);
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
verifyZeroInteractions(mCallback2);
captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted(mBackEvent);
+ verify(mCallback2).onBackStarted(any(BackEvent.class));
verifyNoMoreInteractions(mCallback1);
}
@@ -114,7 +115,7 @@ public class WindowOnBackInvokedDispatcherTest {
assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted(mBackEvent);
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
}
@Test
@@ -152,6 +153,6 @@ public class WindowOnBackInvokedDispatcherTest {
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted(mBackEvent);
+ verify(mCallback2).onBackStarted(any(BackEvent.class));
}
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..973b904c9344fa856304b0037b9fda119cac2d0c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.accessibility;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.doubleClick;
+import static androidx.test.espresso.action.ViewActions.scrollTo;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.RootMatchers.isDialog;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.endsWith;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Collections;
+
+/**
+ * Tests for {@link AccessibilityShortcutChooserActivity}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityShortcutChooserActivityTest {
+ private static final String ONE_HANDED_MODE = "One-Handed mode";
+ private static final String TEST_LABEL = "TEST_LABEL";
+ private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class");
+
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Mock
+ private AccessibilityServiceInfo mAccessibilityServiceInfo;
+ @Mock
+ private ResolveInfo mResolveInfo;
+ @Mock
+ private ServiceInfo mServiceInfo;
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+ @Mock
+ private IAccessibilityManager mAccessibilityManagerService;
+
+ @Test
+ public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist()
+ throws Exception {
+ configureTestService();
+ final ActivityScenario scenario =
+ ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+ scenario.moveToState(Lifecycle.State.CREATED);
+ scenario.moveToState(Lifecycle.State.STARTED);
+ scenario.moveToState(Lifecycle.State.RESUMED);
+
+ onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+ isDialog()).check(matches(isDisplayed()));
+ onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+ onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
+ onView(withId(R.id.accessibility_permission_enable_deny_button)).perform(scrollTo(),
+ click());
+
+ onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check(
+ doesNotExist());
+ scenario.moveToState(Lifecycle.State.DESTROYED);
+ }
+
+ @Test
+ public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() {
+ TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
+ final ActivityScenario scenario =
+ ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+ scenario.moveToState(Lifecycle.State.CREATED);
+ scenario.moveToState(Lifecycle.State.STARTED);
+ scenario.moveToState(Lifecycle.State.RESUMED);
+
+ onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+ isDialog()).check(matches(isDisplayed()));
+ onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+ onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(matches(isDisplayed()));
+ scenario.moveToState(Lifecycle.State.DESTROYED);
+ }
+
+ @Test
+ public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() {
+ TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
+ final ActivityScenario scenario =
+ ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+ scenario.moveToState(Lifecycle.State.CREATED);
+ scenario.moveToState(Lifecycle.State.STARTED);
+ scenario.moveToState(Lifecycle.State.RESUMED);
+
+ onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+ isDialog()).check(matches(isDisplayed()));
+ onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+ onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist());
+ scenario.moveToState(Lifecycle.State.DESTROYED);
+ }
+
+ private void configureTestService() throws Exception {
+ when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
+ mResolveInfo.serviceInfo = mServiceInfo;
+ mServiceInfo.applicationInfo = mApplicationInfo;
+ when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
+ when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
+ when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
+ anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
+
+ TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
+ }
+
+ /**
+ * Used for testing.
+ */
+ public static class TestAccessibilityShortcutChooserActivity extends
+ AccessibilityShortcutChooserActivity {
+ private static IAccessibilityManager sAccessibilityManagerService;
+
+ public static void setupForTesting(IAccessibilityManager accessibilityManagerService) {
+ sAccessibilityManagerService = accessibilityManagerService;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.ACCESSIBILITY_SERVICE.equals(name)
+ && sAccessibilityManagerService != null) {
+ return new AccessibilityManager(this, new Handler(getMainLooper()),
+ sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true);
+ }
+
+ return super.getSystemService(name);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 6baf3056be32039dde0b2768070bac6c638fec21..c92ae2c98f9adc4042639cc64a7e36233abb5210 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -21,6 +21,11 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SC
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -203,6 +208,17 @@ public class AccessibilityShortcutControllerTest {
when(mAlertDialog.getWindow()).thenReturn(window);
when(mTextToSpeech.getVoice()).thenReturn(mVoice);
+
+ // Clears the sFrameworkShortcutFeaturesMap field which was not properly initialized
+ // during testing.
+ try {
+ Field field = AccessibilityShortcutController.class.getDeclaredField(
+ "sFrameworkShortcutFeaturesMap");
+ field.setAccessible(true);
+ field.set(window, null);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to set sFrameworkShortcutFeaturesMap", e);
+ }
}
@AfterClass
@@ -428,11 +444,10 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- public void getFrameworkFeatureMap_shouldBeNonNullAndUnmodifiable() {
- Map
+ public void getFrameworkFeatureMap_shouldBeUnmodifiable() {
+ final Map
frameworkFeatureMap =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
- assertTrue("Framework features not supported", frameworkFeatureMap.size() > 0);
try {
frameworkFeatureMap.clear();
@@ -442,6 +457,39 @@ public class AccessibilityShortcutControllerTest {
}
}
+ @Test
+ public void getFrameworkFeatureMap_containsExpectedDefaultKeys() {
+ final Map
+ frameworkFeatureMap =
+ AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
+
+ assertTrue(frameworkFeatureMap.containsKey(COLOR_INVERSION_COMPONENT_NAME));
+ assertTrue(frameworkFeatureMap.containsKey(DALTONIZER_COMPONENT_NAME));
+ assertTrue(frameworkFeatureMap.containsKey(REDUCE_BRIGHT_COLORS_COMPONENT_NAME));
+ }
+
+ @Test
+ public void getFrameworkFeatureMap_oneHandedModeEnabled_containsExpectedKey() {
+ TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
+
+ final Map
+ frameworkFeatureMap =
+ AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
+
+ assertTrue(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME));
+ }
+
+ @Test
+ public void getFrameworkFeatureMap_oneHandedModeDisabled_containsExpectedKey() {
+ TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
+
+ final Map
+ frameworkFeatureMap =
+ AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
+
+ assertFalse(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME));
+ }
+
@Test
public void testOnAccessibilityShortcut_forServiceWithNoSummary_doesNotCrash()
throws Exception {
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java b/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff014add793a3a2f86a7363b8b8e278c68d88198
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java
@@ -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.internal.accessibility;
+
+import com.android.internal.os.RoSystemProperties;
+
+import java.lang.reflect.Field;
+
+/**
+ * Test utility methods.
+ */
+public class TestUtils {
+
+ /**
+ * Sets the {@code enabled} of the given OneHandedMode flags to simulate device behavior.
+ */
+ public static void setOneHandedModeEnabled(Object obj, boolean enabled) {
+ try {
+ final Field field = RoSystemProperties.class.getDeclaredField(
+ "SUPPORT_ONE_HANDED_MODE");
+ field.setAccessible(true);
+ field.setBoolean(obj, enabled);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..61899143b9c5e9618a0f860450643d793320bdb1
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app.procstats;
+
+import static com.android.internal.app.procstats.ProcessStats.STATE_TOP;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.app.ActivityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import junit.framework.TestCase;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import java.util.concurrent.TimeUnit;
+
+/** Provides test cases for ProcessStats. */
+public class ProcessStatsTest extends TestCase {
+
+ private static final String APP_1_PACKAGE_NAME = "com.android.testapp";
+ private static final int APP_1_UID = 5001;
+ private static final long APP_1_VERSION = 10;
+ private static final String APP_1_PROCESS_NAME = "com.android.testapp.p";
+ private static final String APP_1_SERVICE_NAME = "com.android.testapp.service";
+
+ private static final String APP_2_PACKAGE_NAME = "com.android.testapp2";
+ private static final int APP_2_UID = 5002;
+ private static final long APP_2_VERSION = 30;
+ private static final String APP_2_PROCESS_NAME = "com.android.testapp2.p";
+
+ private static final long NOW_MS = 123000;
+ private static final int DURATION_SECS = 6;
+
+ @Mock StatsEventOutput mStatsEventOutput;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ }
+
+ @SmallTest
+ public void testDumpProcessState() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processStats.getProcessStateLocked(
+ APP_2_PACKAGE_NAME, APP_2_UID, APP_2_VERSION, APP_2_PROCESS_NAME);
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_2_UID),
+ eq(APP_2_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ }
+
+ @SmallTest
+ public void testNonZeroProcessStateDuration() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ ProcessState processState =
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processState.setCombinedState(STATE_TOP, NOW_MS);
+ processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(DURATION_SECS),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ }
+
+ @SmallTest
+ public void testDumpBoundFgsDuration() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ ProcessState processState =
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processState.setState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+ ProcessStats.ADJ_MEM_FACTOR_NORMAL, NOW_MS, /* pkgList */ null);
+ processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(DURATION_SECS),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ }
+
+ @SmallTest
+ public void testDumpProcessAssociation() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ AssociationState associationState =
+ processStats.getAssociationStateLocked(
+ APP_1_PACKAGE_NAME,
+ APP_1_UID,
+ APP_1_VERSION,
+ APP_1_PROCESS_NAME,
+ APP_1_SERVICE_NAME);
+ AssociationState.SourceState sourceState =
+ associationState.startSource(APP_2_UID, APP_2_PROCESS_NAME, APP_2_PACKAGE_NAME);
+ sourceState.stop();
+ processStats.dumpProcessAssociation(
+ FrameworkStatsLog.PROCESS_ASSOCIATION, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_ASSOCIATION),
+ eq(APP_2_UID),
+ eq(APP_2_PROCESS_NAME),
+ eq(APP_1_UID),
+ eq(APP_1_SERVICE_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(APP_1_PROCESS_NAME));
+ }
+
+ @SmallTest
+ public void testSafelyResetClearsProcessInUidState() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ ProcessState processState =
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processState.makeActive();
+ UidState uidState = processStats.mUidStates.get(APP_1_UID);
+ assertTrue(uidState.isInUse());
+ processState.makeInactive();
+ uidState.resetSafely(NOW_MS);
+ processState.makeActive();
+ assertFalse(uidState.isInUse());
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/config/sysui/OWNERS b/core/tests/coretests/src/com/android/internal/config/sysui/OWNERS
new file mode 100644
index 0000000000000000000000000000000000000000..2e96c97c8bb352f425747ce548ea1a34f89f0dc0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/config/sysui/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/config/sysui/SystemUiSystemPropertiesFlagsTest.java b/core/tests/coretests/src/com/android/internal/config/sysui/SystemUiSystemPropertiesFlagsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b9d39ceb79a8afabbbb237c10c5d1d2c0f11dca
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/config/sysui/SystemUiSystemPropertiesFlagsTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.config.sysui;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@SmallTest
+public class SystemUiSystemPropertiesFlagsTest extends TestCase {
+
+ public class TestableDebugResolver extends SystemUiSystemPropertiesFlags.DebugResolver {
+ final Map mTestData = new HashMap<>();
+
+ @Override
+ public boolean getBoolean(String key, boolean defaultValue) {
+ Boolean testValue = mTestData.get(key);
+ return testValue == null ? defaultValue : testValue;
+ }
+
+ public void set(Flag flag, Boolean value) {
+ mTestData.put(flag.mSysPropKey, value);
+ }
+ }
+
+ private FlagResolver mProdResolver;
+ private TestableDebugResolver mDebugResolver;
+
+ private Flag mReleasedFlag;
+ private Flag mTeamfoodFlag;
+ private Flag mDevFlag;
+
+ public void setUp() {
+ mProdResolver = new SystemUiSystemPropertiesFlags.ProdResolver();
+ mDebugResolver = new TestableDebugResolver();
+ mReleasedFlag = SystemUiSystemPropertiesFlags.releasedFlag("mReleasedFlag");
+ mTeamfoodFlag = SystemUiSystemPropertiesFlags.teamfoodFlag("mTeamfoodFlag");
+ mDevFlag = SystemUiSystemPropertiesFlags.devFlag("mDevFlag");
+ }
+
+ public void tearDown() {
+ SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+ }
+
+ public void testProdResolverReturnsDefault() {
+ assertThat(mProdResolver.isEnabled(mReleasedFlag)).isTrue();
+ assertThat(mProdResolver.isEnabled(mTeamfoodFlag)).isFalse();
+ assertThat(mProdResolver.isEnabled(mDevFlag)).isFalse();
+ }
+
+ public void testDebugResolverAndReleasedFlag() {
+ assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isTrue();
+
+ mDebugResolver.set(mReleasedFlag, false);
+ assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isFalse();
+
+ mDebugResolver.set(mReleasedFlag, true);
+ assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isTrue();
+ }
+
+ private void assertTeamfoodFlag(Boolean flagValue, Boolean teamfood, boolean expected) {
+ mDebugResolver.set(mTeamfoodFlag, flagValue);
+ mDebugResolver.set(SystemUiSystemPropertiesFlags.TEAMFOOD, teamfood);
+ assertThat(mDebugResolver.isEnabled(mTeamfoodFlag)).isEqualTo(expected);
+ }
+
+ public void testDebugResolverAndTeamfoodFlag() {
+ assertTeamfoodFlag(null, null, false);
+ assertTeamfoodFlag(true, null, true);
+ assertTeamfoodFlag(false, null, false);
+ assertTeamfoodFlag(null, true, true);
+ assertTeamfoodFlag(true, true, true);
+ assertTeamfoodFlag(false, true, false);
+ assertTeamfoodFlag(null, false, false);
+ assertTeamfoodFlag(true, false, true);
+ assertTeamfoodFlag(false, false, false);
+ }
+
+ public void testDebugResolverAndDevFlag() {
+ assertThat(mDebugResolver.isEnabled(mDevFlag)).isFalse();
+
+ mDebugResolver.set(mDevFlag, true);
+ assertThat(mDebugResolver.isEnabled(mDevFlag)).isTrue();
+
+ mDebugResolver.set(mDevFlag, false);
+ assertThat(mDebugResolver.isEnabled(mDevFlag)).isFalse();
+ }
+}
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 52feac5a585a7e9c61a5a78d95ceec91853eeda1..4c9b2b7f5dd67a14679326cd64fee360737e65aa 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -360,6 +360,7 @@ public class BatteryStatsNoteTest extends TestCase {
// map of ActivityManager process states and how long to simulate run time in each state
Map stateRuntimeMap = new HashMap();
stateRuntimeMap.put(ActivityManager.PROCESS_STATE_TOP, 1111);
+ stateRuntimeMap.put(ActivityManager.PROCESS_STATE_BOUND_TOP, 7382);
stateRuntimeMap.put(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 1234);
stateRuntimeMap.put(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 2468);
stateRuntimeMap.put(ActivityManager.PROCESS_STATE_TOP_SLEEPING, 7531);
@@ -396,7 +397,8 @@ public class BatteryStatsNoteTest extends TestCase {
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
elapsedTimeUs, STATS_SINCE_CHARGED);
- expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE)
+ + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING,
@@ -406,8 +408,7 @@ public class BatteryStatsNoteTest extends TestCase {
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND,
elapsedTimeUs, STATS_SINCE_CHARGED);
- expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
- + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
@@ -415,7 +416,8 @@ public class BatteryStatsNoteTest extends TestCase {
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BACKUP)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_SERVICE)
- + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
+ + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER)
+ + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_TOP);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_CACHED,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 354b93709976282337aa20e70ecb29d25262c0be..2742861351747ee37eb166b9da2ab672485fc751 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -78,9 +78,9 @@ public class BatteryUsageStatsProviderTest {
batteryUsageStats.getUidBatteryConsumers();
final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND))
- .isEqualTo(60 * MINUTE_IN_MS);
+ .isEqualTo(20 * MINUTE_IN_MS);
assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
- .isEqualTo(10 * MINUTE_IN_MS);
+ .isEqualTo(40 * MINUTE_IN_MS);
assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
.isWithin(PRECISION).of(2.0);
assertThat(
@@ -121,22 +121,44 @@ public class BatteryUsageStatsProviderTest {
private BatteryStatsImpl prepareBatteryStats() {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- batteryStats.noteActivityResumedLocked(APP_UID,
- 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
- batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_TOP,
- 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
- batteryStats.noteActivityPausedLocked(APP_UID,
- 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
- batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_SERVICE,
- 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
- batteryStats.noteUidProcessStateLocked(APP_UID,
- ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
- 40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS);
- batteryStats.noteUidProcessStateLocked(APP_UID,
- ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
- 50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
- batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY,
- 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteActivityResumedLocked(APP_UID);
+ }
+
+ mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_TOP);
+ }
+ mStatsRule.setTime(30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteActivityPausedLocked(APP_UID);
+ }
+ mStatsRule.setTime(30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID,
+ ActivityManager.PROCESS_STATE_SERVICE);
+ }
+ mStatsRule.setTime(40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID,
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ }
+ mStatsRule.setTime(50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID,
+ ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ }
+ mStatsRule.setTime(60 * MINUTE_IN_MS, 60 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID,
+ ActivityManager.PROCESS_STATE_BOUND_TOP);
+ }
+ mStatsRule.setTime(70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID,
+ ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ }
batteryStats.noteFlashlightOnLocked(APP_UID, 1000, 1000);
batteryStats.noteFlashlightOffLocked(APP_UID, 5000, 5000);
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index 00ac1985f897d12eb2e7175a66d3853bc860da52..0bdf491e63776a0e048f08817977a6a1529fd78d 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -245,6 +245,8 @@ public class MobileRadioPowerCalculatorTest {
stats.noteNetworkInterfaceForTransports("cellular",
new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+ stats.notePhoneOnLocked(9800, 9800);
+
// Note application network activity
NetworkStats networkStats = new NetworkStats(10000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
@@ -257,27 +259,33 @@ public class MobileRadioPowerCalculatorTest {
mStatsRule.setTime(12_000, 12_000);
- MobileRadioPowerCalculator calculator =
+ MobileRadioPowerCalculator mobileRadioPowerCalculator =
new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
-
- mStatsRule.apply(calculator);
+ PhonePowerCalculator phonePowerCalculator =
+ new PhonePowerCalculator(mStatsRule.getPowerProfile());
+ mStatsRule.apply(mobileRadioPowerCalculator, phonePowerCalculator);
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(1.53934);
+ .isWithin(PRECISION).of(1.38541);
assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
// 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(2.77778);
+ .isWithin(PRECISION).of(2.5);
assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
+ .isWithin(PRECISION).of(0.27778);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(1.53934);
+ .isWithin(PRECISION).of(1.38541);
assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
diff --git a/core/tests/screenshothelpertests/Android.bp b/core/tests/screenshothelpertests/Android.bp
index 37af99c58d427b77877aaba0917616c720c44bf3..3c71e6e4247b733236a8d1eacdb0887a1cace15d 100644
--- a/core/tests/screenshothelpertests/Android.bp
+++ b/core/tests/screenshothelpertests/Android.bp
@@ -13,7 +13,7 @@ android_test {
srcs: [
"src/**/*.java",
],
-
+
static_libs: [
"frameworks-base-testutils",
"androidx.test.runner",
@@ -21,6 +21,7 @@ android_test {
"androidx.test.ext.junit",
"mockito-target-minus-junit4",
"platform-test-annotations",
+ "testng",
],
libs: [
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 2719431a536e414f5cbaef162005455f6a03aedf..5c9894ebd5900329349dee999288411df7e7c4d5 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -17,6 +17,7 @@
package com.android.internal.util;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
@@ -31,9 +32,11 @@ import static org.mockito.Mockito.mock;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
import android.graphics.Insets;
import android.graphics.Rect;
-import android.os.Bundle;
+import android.hardware.HardwareBuffer;
import android.os.Handler;
import android.os.Looper;
import android.view.WindowManager;
@@ -79,30 +82,48 @@ public final class ScreenshotHelperTest {
@Test
public void testFullscreenScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+ mScreenshotHelper.takeScreenshot(
WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
}
+ @Test
+ public void testFullscreenScreenshotRequest() {
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+ .build();
+ mScreenshotHelper.takeScreenshot(request, mHandler, null);
+ }
+
@Test
public void testProvidedImageScreenshot() {
- mScreenshotHelper.provideScreenshot(
- new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
- WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
+ HardwareBuffer buffer = HardwareBuffer.create(
+ 10, 10, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_PROVIDED_IMAGE, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+ .setTopComponent(new ComponentName("", ""))
+ .setTaskId(1)
+ .setUserId(1)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(new Rect())
+ .setInsets(Insets.NONE)
+ .build();
+ mScreenshotHelper.takeScreenshot(request, mHandler, null);
}
@Test
public void testScreenshotTimesOut() {
long timeoutMs = 10;
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+ .build();
CountDownLatch lock = new CountDownLatch(1);
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
- WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
- mHandler,
- timeoutMs,
+ mScreenshotHelper.takeScreenshotInternal(request, mHandler,
uri -> {
assertNull(uri);
lock.countDown();
- });
+ }, timeoutMs);
try {
// Add tolerance for delay to prevent flakes.
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..89acbc7986fb9f599d494ea07412112163e5674b
--- /dev/null
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ScreenshotRequestTest {
+ private final ComponentName mComponentName =
+ new ComponentName("android.test", "android.test.Component");
+
+ @Test
+ public void testSimpleScreenshot() {
+ ScreenshotRequest in =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build();
+
+ Parcel parcel = Parcel.obtain();
+ in.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
+ assertEquals(SCREENSHOT_OTHER, out.getSource());
+ assertNull("Top component was expected to be null", out.getTopComponent());
+ assertEquals(INVALID_TASK_ID, out.getTaskId());
+ assertEquals(USER_NULL, out.getUserId());
+ assertNull("Bitmap was expected to be null", out.getBitmap());
+ assertNull("Bounds were expected to be null", out.getBoundsInScreen());
+ assertEquals(Insets.NONE, out.getInsets());
+ }
+
+ @Test
+ public void testProvidedScreenshot() {
+ Bitmap bitmap = makeHardwareBitmap(50, 50);
+ ScreenshotRequest in =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(mComponentName)
+ .setTaskId(2)
+ .setUserId(3)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(new Rect(10, 10, 60, 60))
+ .setInsets(Insets.of(2, 3, 4, 5))
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ in.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(TAKE_SCREENSHOT_PROVIDED_IMAGE, out.getType());
+ assertEquals(SCREENSHOT_OTHER, out.getSource());
+ assertEquals(mComponentName, out.getTopComponent());
+ assertEquals(2, out.getTaskId());
+ assertEquals(3, out.getUserId());
+ assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
+ assertEquals(new Rect(10, 10, 60, 60), out.getBoundsInScreen());
+ assertEquals(Insets.of(2, 3, 4, 5), out.getInsets());
+ }
+
+ @Test
+ public void testProvidedScreenshot_nullBitmap() {
+ ScreenshotRequest.Builder inBuilder =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(mComponentName)
+ .setTaskId(2)
+ .setUserId(3)
+ .setBoundsOnScreen(new Rect(10, 10, 60, 60))
+ .setInsets(Insets.of(2, 3, 4, 5));
+
+ assertThrows(IllegalStateException.class, inBuilder::build);
+ }
+
+ @Test
+ public void testFullScreenshot_withBitmap() {
+ // A bitmap added to a FULLSCREEN request will be ignored, but it's technically valid
+ Bitmap bitmap = makeHardwareBitmap(50, 50);
+ ScreenshotRequest in =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
+ .setBitmap(bitmap)
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ in.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
+ assertEquals(SCREENSHOT_OTHER, out.getSource());
+ assertNull(out.getTopComponent());
+ assertEquals(INVALID_TASK_ID, out.getTaskId());
+ assertEquals(USER_NULL, out.getUserId());
+ assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
+ assertNull("Bounds expected to be null", out.getBoundsInScreen());
+ assertEquals(Insets.NONE, out.getInsets());
+ }
+
+ @Test
+ public void testInvalidType() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new ScreenshotRequest.Builder(5, 2).build());
+ }
+
+ private Bitmap makeHardwareBitmap(int width, int height) {
+ HardwareBuffer buffer = HardwareBuffer.create(
+ width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+}
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index e0e13f59b706588c9611182ec92c7f520454a77d..6dcee6d8bd311cf48a2ae137998fe2a82a96d1af 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -49,6 +49,7 @@
+
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index ff42fb52d8e7346005b3fa2cec7742971372a7ab..5703b6f687eed3c1a13e3c08cf76ed21647f1a07 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -306,6 +306,7 @@ applications that come with the platform
+
@@ -516,6 +517,12 @@ applications that come with the platform
+
+
+
+
+
+
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f47d9c6e0c2d65d843d589daa970a40c586e5a1e..334a7275ebe2822b2b8858f434fa6e923a083fb7 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -469,6 +469,18 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RecentTasks.java"
},
+ "-1643780158": {
+ "message": "Saving original orientation before camera compat, last orientation is %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
+ "-1639406696": {
+ "message": "NOSENSOR override detected",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java"
+ },
"-1638958146": {
"message": "Removing activity %s from task=%s adding to task=%s Callers=%s",
"level": "INFO",
@@ -751,6 +763,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
},
+ "-1397175017": {
+ "message": "Other orientation overrides are in place: not reverting",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java"
+ },
"-1394745488": {
"message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
"level": "INFO",
@@ -1051,6 +1069,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
+ "-1104347731": {
+ "message": "Setting requested orientation %s for %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1103716954": {
"message": "Not removing %s due to exit animation",
"level": "VERBOSE",
@@ -1663,6 +1687,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-529187878": {
+ "message": "Reverting orientation after camera compat force rotation",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-521613870": {
"message": "App died during pause, not stopping: %s",
"level": "VERBOSE",
@@ -2359,6 +2389,12 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "138097009": {
+ "message": "NOSENSOR override is absent: reverting",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java"
+ },
"140319294": {
"message": "IME target changed within ActivityRecord",
"level": "DEBUG",
@@ -3331,6 +3367,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "1015746067": {
+ "message": "Display id=%d is ignoring orientation request for %d, return %d following a per-app override for %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"1022095595": {
"message": "TaskFragment info changed name=%s",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 14dc6a2513a0b73b929c60050e3b405570c1ecd6..6b1cf8b1ed2af1e221305eabf9ac391b5fbfbd1c 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -23,7 +23,6 @@ import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.animation.ValueAnimator;
-import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -983,9 +982,9 @@ public class RippleDrawable extends LayerDrawable {
RippleShader shader = new RippleShader();
// Grab the color for the current state and cut the alpha channel in
// half so that the ripple and background together yield full alpha.
- final int color = clampAlpha(mMaskColorFilter == null
+ final int color = mMaskColorFilter == null
? mState.mColor.getColorForState(getState(), Color.BLACK)
- : mMaskColorFilter.getColor());
+ : mMaskColorFilter.getColor();
final int effectColor = mState.mEffectColor.getColorForState(getState(), Color.MAGENTA);
final float noisePhase = AnimationUtils.currentAnimationTimeMillis();
shader.setColor(color, effectColor);
@@ -1008,13 +1007,6 @@ public class RippleDrawable extends LayerDrawable {
return properties;
}
- private int clampAlpha(@ColorInt int color) {
- if (Color.alpha(color) < 128) {
- return (color & 0x00FFFFFF) | 0x80000000;
- }
- return color;
- }
-
@Override
public void invalidateSelf() {
invalidateSelf(true);
@@ -1229,7 +1221,7 @@ public class RippleDrawable extends LayerDrawable {
// Grab the color for the current state and cut the alpha channel in
// half so that the ripple and background together yield full alpha.
- final int color = clampAlpha(mState.mColor.getColorForState(getState(), Color.BLACK));
+ final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
final Paint p = mRipplePaint;
if (mMaskColorFilter != null) {
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index dc4b5636a246e1dc936e96b271ea2914b4257752..a5b192cd7ceb730f58a19760ad874065aab29f53 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -63,6 +63,12 @@ android_library_import {
sdk_version: "current",
}
+android_library_import {
+ name: "window-extensions-core",
+ aars: ["window-extensions-core-release.aar"],
+ sdk_version: "current",
+}
+
java_library {
name: "androidx.window.extensions",
srcs: [
@@ -70,7 +76,10 @@ java_library {
"src/androidx/window/util/**/*.java",
"src/androidx/window/common/**/*.java",
],
- static_libs: ["window-extensions"],
+ static_libs: [
+ "window-extensions",
+ "window-extensions-core",
+ ],
installable: true,
sdk_version: "core_platform",
system_ext_specific: true,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 54edd9ec4335871d110c4a87421f9ae5996baae6..666b472c3716fca645382d544306e3ef16714f99 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -48,7 +48,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 1;
+ return 2;
}
@NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 3adae7006369d15a10b012311d3818a16be21d12..9118ee2bf125c6080bc9525a1d1a7d7b59c1ed9a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -25,12 +25,12 @@ import android.hardware.devicestate.DeviceStateRequest;
import android.util.ArraySet;
import androidx.annotation.NonNull;
+import androidx.window.extensions.core.util.function.Consumer;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Reference implementation of androidx.window.extensions.area OEM interface for use with
@@ -187,6 +187,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
@GuardedBy("mLock")
private int getCurrentStatus() {
+ if (mRearDisplayState == INVALID_DEVICE_STATE) {
+ return WindowAreaComponent.STATUS_UNSUPPORTED;
+ }
+
if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
|| isRearDisplayActive()) {
return WindowAreaComponent.STATUS_UNAVAILABLE;
@@ -252,4 +256,37 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
}
}
}
+
+ @Override
+ public void addRearDisplayPresentationStatusListener(
+ @NonNull Consumer consumer) {
+ throw new UnsupportedOperationException(
+ "addRearDisplayPresentationStatusListener is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void removeRearDisplayPresentationStatusListener(
+ @NonNull Consumer consumer) {
+ throw new UnsupportedOperationException(
+ "removeRearDisplayPresentationStatusListener is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void startRearDisplayPresentationSession(@NonNull Activity activity,
+ @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {
+ throw new UnsupportedOperationException(
+ "startRearDisplayPresentationSession is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void endRearDisplayPresentationSession() {
+ throw new UnsupportedOperationException(
+ "endRearDisplayPresentationSession is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public ExtensionWindowAreaPresentation getRearDisplayPresentation() {
+ throw new UnsupportedOperationException(
+ "getRearDisplayPresentation is not supported in API_VERSION=2");
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 87fa63d7fe147039de53794d5fc423daa974bd67..00e13c94ea905557d0a1f364b986cdf30e929a43 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -191,10 +191,25 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
*/
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
+ createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
+ null /* pairedActivityToken */);
+ }
+
+ /**
+ * @param ownerToken The token of the activity that creates this task fragment. It does not
+ * have to be a child of this task fragment, but must belong to the same task.
+ * @param pairedActivityToken The token of the activity that will be reparented to this task
+ * fragment. When it is not {@code null}, the task fragment will be
+ * positioned right above it.
+ */
+ void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode,
+ @Nullable IBinder pairedActivityToken) {
final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
getOrganizerToken(), fragmentToken, ownerToken)
.setInitialBounds(bounds)
.setWindowingMode(windowingMode)
+ .setPairedActivityToken(pairedActivityToken)
.build();
createTaskFragment(wct, fragmentOptions);
}
@@ -216,8 +231,10 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
@WindowingMode int windowingMode, @NonNull Activity activity) {
- createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
- wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
+ final IBinder reparentActivityToken = activity.getActivityToken();
+ createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
+ reparentActivityToken);
+ wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
}
void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
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 1cd3ea5592e3bd3a554c47b70c4f6e696ba3c9af..569eb801989bc05136b24a25069ca591e379a750 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -77,6 +77,9 @@ import androidx.annotation.Nullable;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
+import androidx.window.extensions.WindowExtensionsImpl;
+import androidx.window.extensions.core.util.function.Consumer;
+import androidx.window.extensions.core.util.function.Function;
import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
@@ -87,7 +90,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Main controller class that manages split states and presentation.
@@ -113,7 +115,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* A developer-defined {@link SplitAttributes} calculator to compute the current
* {@link SplitAttributes} with the current device and window states.
- * It is registered via {@link #setSplitAttributesCalculator(SplitAttributesCalculator)}
+ * It is registered via {@link #setSplitAttributesCalculator(Function)}
* and unregistered via {@link #clearSplitAttributesCalculator()}.
* This is called when:
*
@@ -126,7 +128,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@GuardedBy("mLock")
@Nullable
- private SplitAttributesCalculator mSplitAttributesCalculator;
+ private Function mSplitAttributesCalculator;
/**
* Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
@@ -139,6 +141,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final SparseArray mTaskContainers = new SparseArray<>();
/** Callback to Jetpack to notify about changes to split states. */
+ @GuardedBy("mLock")
@Nullable
private Consumer> mEmbeddingCallback;
private final List mLastReportedSplitStates = new ArrayList<>();
@@ -164,7 +167,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
foldingFeatureProducer.addDataChangedCallback(new FoldingFeatureListener());
}
- private class FoldingFeatureListener implements Consumer> {
+ private class FoldingFeatureListener
+ implements java.util.function.Consumer> {
@Override
public void accept(List foldingFeatures) {
synchronized (mLock) {
@@ -205,7 +209,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@Override
- public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) {
+ public void setSplitAttributesCalculator(
+ @NonNull Function calculator) {
synchronized (mLock) {
mSplitAttributesCalculator = calculator;
}
@@ -220,7 +225,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
@Nullable
- SplitAttributesCalculator getSplitAttributesCalculator() {
+ Function getSplitAttributesCalculator() {
return mSplitAttributesCalculator;
}
@@ -233,9 +238,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Registers the split organizer callback to notify about changes to active splits.
+ * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with
+ * {@link WindowExtensionsImpl#getVendorApiLevel()} 2.
*/
+ @Deprecated
@Override
- public void setSplitInfoCallback(@NonNull Consumer> callback) {
+ public void setSplitInfoCallback(
+ @NonNull java.util.function.Consumer> callback) {
+ Consumer> oemConsumer = callback::accept;
+ setSplitInfoCallback(oemConsumer);
+ }
+
+ /**
+ * Registers the split organizer callback to notify about changes to active splits.
+ * @since {@link WindowExtensionsImpl#getVendorApiLevel()} 2
+ */
+ public void setSplitInfoCallback(Consumer> callback) {
synchronized (mLock) {
mEmbeddingCallback = callback;
updateCallbackIfNecessary();
@@ -1481,7 +1499,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns the active split that has the provided containers as primary and secondary or as
* secondary and primary, if available.
*/
- @VisibleForTesting
+ @GuardedBy("mLock")
@Nullable
SplitContainer getActiveSplitForContainers(
@NonNull TaskFragmentContainer firstContainer,
@@ -2138,4 +2156,30 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return configuration != null
&& configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
}
+
+ @Override
+ public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
+ @NonNull IBinder token) {
+ throw new UnsupportedOperationException(
+ "setLaunchingActivityStack is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void finishActivityStacks(@NonNull Set activityStackTokens) {
+ throw new UnsupportedOperationException(
+ "finishActivityStacks is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void invalidateTopVisibleSplitAttributes() {
+ throw new UnsupportedOperationException(
+ "invalidateTopVisibleSplitAttributes is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void updateSplitAttributes(@NonNull IBinder splitInfoToken,
+ @NonNull SplitAttributes splitAttributes) {
+ throw new UnsupportedOperationException(
+ "updateSplitAttributes is not supported in API_VERSION=2");
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 14d244bbb6cee1300e2659ff4685ce435d3c7193..c23ac75e696f709a2bedf8f1923ccea95e214649 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -43,11 +43,11 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.window.extensions.core.util.function.Function;
import androidx.window.extensions.embedding.SplitAttributes.SplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
-import androidx.window.extensions.embedding.SplitAttributesCalculator.SplitAttributesCalculatorParams;
import androidx.window.extensions.embedding.TaskContainer.TaskProperties;
import androidx.window.extensions.layout.DisplayFeature;
import androidx.window.extensions.layout.FoldingFeature;
@@ -268,10 +268,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
container = mController.newContainer(activity, taskId);
final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(bounds);
- createTaskFragment(wct, container.getTaskFragmentToken(), activity.getActivityToken(),
- bounds, windowingMode);
+ final IBinder reparentActivityToken = activity.getActivityToken();
+ createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
+ bounds, windowingMode, reparentActivityToken);
wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
- activity.getActivityToken());
+ reparentActivityToken);
} else {
resizeTaskFragmentIfRegistered(wct, container, bounds);
final int windowingMode = mController.getTaskContainer(taskId)
@@ -551,11 +552,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull SplitRule rule, @Nullable Pair minDimensionsPair) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
- final SplitAttributesCalculator calculator = mController.getSplitAttributesCalculator();
+ final Function calculator =
+ mController.getSplitAttributesCalculator();
final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes();
- final boolean isDefaultMinSizeSatisfied = rule.checkParentMetrics(taskWindowMetrics);
+ final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
if (calculator == null) {
- if (!isDefaultMinSizeSatisfied) {
+ if (!areDefaultConstraintsSatisfied) {
return EXPAND_CONTAINERS_ATTRIBUTES;
}
return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes,
@@ -565,9 +567,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
.getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
taskConfiguration.windowConfiguration);
final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams(
- taskWindowMetrics, taskConfiguration, defaultSplitAttributes,
- isDefaultMinSizeSatisfied, windowLayoutInfo, rule.getTag());
- final SplitAttributes splitAttributes = calculator.computeSplitAttributesForParams(params);
+ taskWindowMetrics, taskConfiguration, windowLayoutInfo, defaultSplitAttributes,
+ areDefaultConstraintsSatisfied, rule.getTag());
+ final SplitAttributes splitAttributes = calculator.apply(params);
return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair);
}
@@ -659,21 +661,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull SplitAttributes splitAttributes) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
- final SplitType splitType = computeSplitType(splitAttributes, taskConfiguration,
- foldingFeature);
- final SplitAttributes computedSplitAttributes = new SplitAttributes.Builder()
- .setSplitType(splitType)
- .setLayoutDirection(splitAttributes.getLayoutDirection())
- .build();
- if (!shouldShowSplit(computedSplitAttributes)) {
+ if (!shouldShowSplit(splitAttributes)) {
return new Rect();
}
switch (position) {
case POSITION_START:
- return getPrimaryBounds(taskConfiguration, computedSplitAttributes, foldingFeature);
+ return getPrimaryBounds(taskConfiguration, splitAttributes, foldingFeature);
case POSITION_END:
- return getSecondaryBounds(taskConfiguration, computedSplitAttributes,
- foldingFeature);
+ return getSecondaryBounds(taskConfiguration, splitAttributes, foldingFeature);
case POSITION_FILL:
default:
return new Rect();
@@ -683,63 +678,76 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull
private Rect getPrimaryBounds(@NonNull Configuration taskConfiguration,
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
- if (!shouldShowSplit(splitAttributes)) {
+ final SplitAttributes computedSplitAttributes = updateSplitAttributesType(splitAttributes,
+ computeSplitType(splitAttributes, taskConfiguration, foldingFeature));
+ if (!shouldShowSplit(computedSplitAttributes)) {
return new Rect();
}
- switch (splitAttributes.getLayoutDirection()) {
+ switch (computedSplitAttributes.getLayoutDirection()) {
case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
- return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getLeftContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
- return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getRightContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.LOCALE: {
final boolean isLtr = taskConfiguration.getLayoutDirection()
== View.LAYOUT_DIRECTION_LTR;
return isLtr
- ? getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature)
- : getRightContainerBounds(taskConfiguration, splitAttributes,
+ ? getLeftContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature)
+ : getRightContainerBounds(taskConfiguration, computedSplitAttributes,
foldingFeature);
}
case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
- return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getTopContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
- return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getBottomContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
default:
throw new IllegalArgumentException("Unknown layout direction:"
- + splitAttributes.getLayoutDirection());
+ + computedSplitAttributes.getLayoutDirection());
}
}
@NonNull
private Rect getSecondaryBounds(@NonNull Configuration taskConfiguration,
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
- if (!shouldShowSplit(splitAttributes)) {
+ final SplitAttributes computedSplitAttributes = updateSplitAttributesType(splitAttributes,
+ computeSplitType(splitAttributes, taskConfiguration, foldingFeature));
+ if (!shouldShowSplit(computedSplitAttributes)) {
return new Rect();
}
- switch (splitAttributes.getLayoutDirection()) {
+ switch (computedSplitAttributes.getLayoutDirection()) {
case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
- return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getRightContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
- return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getLeftContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.LOCALE: {
final boolean isLtr = taskConfiguration.getLayoutDirection()
== View.LAYOUT_DIRECTION_LTR;
return isLtr
- ? getRightContainerBounds(taskConfiguration, splitAttributes,
+ ? getRightContainerBounds(taskConfiguration, computedSplitAttributes,
foldingFeature)
- : getLeftContainerBounds(taskConfiguration, splitAttributes,
+ : getLeftContainerBounds(taskConfiguration, computedSplitAttributes,
foldingFeature);
}
case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
- return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getBottomContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
- return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+ return getTopContainerBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
}
default:
throw new IllegalArgumentException("Unknown layout direction:"
@@ -747,6 +755,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
}
+ /**
+ * Returns the {@link SplitAttributes} that update the {@link SplitType} to
+ * {@code splitTypeToUpdate}.
+ */
+ private static SplitAttributes updateSplitAttributesType(
+ @NonNull SplitAttributes splitAttributes, @NonNull SplitType splitTypeToUpdate) {
+ return new SplitAttributes.Builder()
+ .setSplitType(splitTypeToUpdate)
+ .setLayoutDirection(splitAttributes.getLayoutDirection())
+ .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ .build();
+ }
+
@NonNull
private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration,
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
@@ -839,7 +860,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@Nullable
- private FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
+ @VisibleForTesting
+ FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
final int displayId = taskProperties.getDisplayId();
final WindowConfiguration windowConfiguration = taskProperties.getConfiguration()
.windowConfiguration;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index dcc12ac075891f2043f4468b7fe9ca3c1be939dc..b917ac80256c506c628f5cd08fbf3e623ffedc76 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -215,6 +215,8 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
} else {
closingTargets.add(target);
closingWholeScreenBounds.union(target.screenSpaceBounds);
+ // Union the start bounds since this may be the ClosingChanging animation.
+ closingWholeScreenBounds.union(target.startBounds);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 076856c373d62aac01066ec81f7fcd121413d0ff..17814c65e791ec5402935a9e3658479d589d6e31 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -141,12 +141,26 @@ class TaskFragmentContainer {
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
if (pairedPrimaryContainer != null) {
+ // The TaskFragment will be positioned right above the paired container.
if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
throw new IllegalArgumentException(
"pairedPrimaryContainer must be in the same Task");
}
final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
taskContainer.mContainers.add(primaryIndex + 1, this);
+ } else if (pendingAppearedActivity != null) {
+ // The TaskFragment will be positioned right above the pending appeared Activity. If any
+ // existing TaskFragment is empty with pending Intent, it is likely that the Activity of
+ // the pending Intent hasn't been created yet, so the new Activity should be below the
+ // empty TaskFragment.
+ int i = taskContainer.mContainers.size() - 1;
+ for (; i >= 0; i--) {
+ final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+ if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
+ break;
+ }
+ }
+ taskContainer.mContainers.add(i + 1, this);
} else {
taskContainer.mContainers.add(this);
}
@@ -500,6 +514,8 @@ class TaskFragmentContainer {
}
if (!shouldFinishDependent) {
+ // Always finish the placeholder when the primary is finished.
+ finishPlaceholderIfAny(wct, presenter);
return;
}
@@ -526,6 +542,28 @@ class TaskFragmentContainer {
mActivitiesToFinishOnExit.clear();
}
+ @GuardedBy("mController.mLock")
+ private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct,
+ @NonNull SplitPresenter presenter) {
+ final List containersToRemove = new ArrayList<>();
+ for (TaskFragmentContainer container : mContainersToFinishOnExit) {
+ if (container.mIsFinished) {
+ continue;
+ }
+ final SplitContainer splitContainer = mController.getActiveSplitForContainers(
+ this, container);
+ if (splitContainer != null && splitContainer.isPlaceholderContainer()
+ && splitContainer.getSecondaryContainer() == container) {
+ // Remove the placeholder secondary TaskFragment.
+ containersToRemove.add(container);
+ }
+ }
+ mContainersToFinishOnExit.removeAll(containersToRemove);
+ for (TaskFragmentContainer container : containersToRemove) {
+ container.finish(false /* shouldFinishDependent */, presenter, wct, mController);
+ }
+ }
+
boolean isFinished() {
return mIsFinished;
}
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 c9f870005eb95f0e4c6295140db658ceeb8f060e..8386131b177dde0449a4af6e5ffa6119cfe064da 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -45,6 +45,7 @@ import androidx.annotation.UiContext;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
+import androidx.window.extensions.core.util.function.Consumer;
import androidx.window.util.DataProducer;
import java.util.ArrayList;
@@ -53,7 +54,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.function.Consumer;
/**
* Reference implementation of androidx.window.extensions.layout OEM interface for use with
@@ -82,6 +82,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final Map mConfigurationChangeListeners =
new ArrayMap<>();
+ @GuardedBy("mLock")
+ private final Map, Consumer>
+ mJavaToExtConsumers = new ArrayMap<>();
+
private final TaskFragmentOrganizer mTaskFragmentOrganizer;
public WindowLayoutComponentImpl(@NonNull Context context,
@@ -95,7 +99,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
/** Registers to listen to {@link CommonFoldingFeature} changes */
- public void addFoldingStateChangedCallback(Consumer> consumer) {
+ public void addFoldingStateChangedCallback(
+ java.util.function.Consumer> consumer) {
synchronized (mLock) {
mFoldingFeatureProducer.addDataChangedCallback(consumer);
}
@@ -109,13 +114,27 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*/
@Override
public void addWindowLayoutInfoListener(@NonNull Activity activity,
- @NonNull Consumer consumer) {
- addWindowLayoutInfoListener((Context) activity, consumer);
+ @NonNull java.util.function.Consumer consumer) {
+ final Consumer extConsumer = consumer::accept;
+ synchronized (mLock) {
+ mJavaToExtConsumers.put(consumer, extConsumer);
+ }
+ addWindowLayoutInfoListener(activity, extConsumer);
+ }
+
+ @Override
+ public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
+ @NonNull java.util.function.Consumer consumer) {
+ final Consumer extConsumer = consumer::accept;
+ synchronized (mLock) {
+ mJavaToExtConsumers.put(consumer, extConsumer);
+ }
+ addWindowLayoutInfoListener(context, extConsumer);
}
/**
- * Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context
- * as a parameter.
+ * Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.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}
@@ -156,6 +175,18 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
}
+ @Override
+ public void removeWindowLayoutInfoListener(
+ @NonNull java.util.function.Consumer consumer) {
+ final Consumer extConsumer;
+ synchronized (mLock) {
+ extConsumer = mJavaToExtConsumers.remove(consumer);
+ }
+ if (extConsumer != null) {
+ removeWindowLayoutInfoListener(extConsumer);
+ }
+ }
+
/**
* Removes a listener no longer interested in receiving updates.
*
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 2f92a577baa216f159d725e8af2b5465a3dc82da..459ec9f89c4a017460478f9e0de0665e480b8f80 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -34,9 +34,11 @@ import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Pair;
+import android.view.WindowMetrics;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerToken;
+import androidx.window.extensions.core.util.function.Predicate;
import androidx.window.extensions.embedding.SplitAttributes.SplitType;
import androidx.window.extensions.layout.DisplayFeature;
import androidx.window.extensions.layout.FoldingFeature;
@@ -107,7 +109,7 @@ public class EmbeddingTestUtils {
static SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent, boolean clearTop) {
final Pair targetPair = new Pair<>(primaryActivity, secondaryIntent);
- return new SplitPairRule.Builder(
+ return createSplitPairRuleBuilder(
activityPair -> false,
targetPair::equals,
w -> true)
@@ -144,7 +146,7 @@ public class EmbeddingTestUtils {
@NonNull Activity secondaryActivity, int finishPrimaryWithSecondary,
int finishSecondaryWithPrimary, boolean clearTop) {
final Pair targetPair = new Pair<>(primaryActivity, secondaryActivity);
- return new SplitPairRule.Builder(
+ return createSplitPairRuleBuilder(
targetPair::equals,
activityIntentPair -> false,
w -> true)
@@ -223,4 +225,26 @@ public class EmbeddingTestUtils {
displayFeatures.add(foldingFeature);
return new WindowLayoutInfo(displayFeatures);
}
+
+ static ActivityRule.Builder createActivityBuilder(
+ @NonNull Predicate activityPredicate,
+ @NonNull Predicate intentPredicate) {
+ return new ActivityRule.Builder(activityPredicate, intentPredicate);
+ }
+
+ static SplitPairRule.Builder createSplitPairRuleBuilder(
+ @NonNull Predicate> activitiesPairPredicate,
+ @NonNull Predicate> activityIntentPairPredicate,
+ @NonNull Predicate windowMetricsPredicate) {
+ return new SplitPairRule.Builder(activitiesPairPredicate, activityIntentPairPredicate,
+ windowMetricsPredicate);
+ }
+
+ static SplitPlaceholderRule.Builder createSplitPlaceholderRuleBuilder(
+ @NonNull Intent placeholderIntent, @NonNull Predicate activityPredicate,
+ @NonNull Predicate intentPredicate,
+ @NonNull Predicate windowMetricsPredicate) {
+ return new SplitPlaceholderRule.Builder(placeholderIntent, activityPredicate,
+ intentPredicate, windowMetricsPredicate);
+ }
}
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 81c39571bffa2b2fa81afea8bf60a6feb6d429e9..0bf0bc85b511171986d91b83164ceaae20d52458 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
@@ -34,8 +34,11 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTR
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityBuilder;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
@@ -432,7 +435,7 @@ public class SplitControllerTest {
@Test
public void testResolveStartActivityIntent_withoutLaunchingActivity() {
final Intent intent = new Intent();
- final ActivityRule expandRule = new ActivityRule.Builder(r -> false, i -> i == intent)
+ final ActivityRule expandRule = createActivityBuilder(r -> false, i -> i == intent)
.setShouldAlwaysExpand(true)
.build();
mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
@@ -1170,7 +1173,7 @@ public class SplitControllerTest {
@Test
public void testHasSamePresentation() {
- SplitPairRule splitRule1 = new SplitPairRule.Builder(
+ SplitPairRule splitRule1 = createSplitPairRuleBuilder(
activityPair -> true,
activityIntentPair -> true,
windowMetrics -> true)
@@ -1178,7 +1181,7 @@ public class SplitControllerTest {
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.build();
- SplitPairRule splitRule2 = new SplitPairRule.Builder(
+ SplitPairRule splitRule2 = createSplitPairRuleBuilder(
activityPair -> true,
activityIntentPair -> true,
windowMetrics -> true)
@@ -1191,7 +1194,7 @@ public class SplitControllerTest {
SplitController.haveSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
- splitRule2 = new SplitPairRule.Builder(
+ splitRule2 = createSplitPairRuleBuilder(
activityPair -> true,
activityIntentPair -> true,
windowMetrics -> true)
@@ -1355,7 +1358,7 @@ public class SplitControllerTest {
/** Setups a rule to always expand the given intent. */
private void setupExpandRule(@NonNull Intent expandIntent) {
- final ActivityRule expandRule = new ActivityRule.Builder(r -> false, expandIntent::equals)
+ final ActivityRule expandRule = createActivityBuilder(r -> false, expandIntent::equals)
.setShouldAlwaysExpand(true)
.build();
mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
@@ -1363,7 +1366,7 @@ public class SplitControllerTest {
/** Setups a rule to always expand the given activity. */
private void setupExpandRule(@NonNull Activity expandActivity) {
- final ActivityRule expandRule = new ActivityRule.Builder(expandActivity::equals, i -> false)
+ final ActivityRule expandRule = createActivityBuilder(expandActivity::equals, i -> false)
.setShouldAlwaysExpand(true)
.build();
mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
@@ -1371,7 +1374,7 @@ public class SplitControllerTest {
/** Setups a rule to launch placeholder for the given activity. */
private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
- final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT,
+ final SplitRule placeholderRule = createSplitPlaceholderRuleBuilder(PLACEHOLDER_INTENT,
primaryActivity::equals, i -> false, w -> true)
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.build();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 121e81394b2d761dd651f8454efa75a0d4ff0ea9..d286d23da750cd632a038d8e2c363611513c9ef7 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -28,6 +28,7 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUND
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
@@ -76,6 +77,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.core.util.function.Function;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
@@ -511,7 +513,7 @@ public class SplitPresenterTest {
final Activity secondaryActivity = createMockActivity();
final TaskFragmentContainer bottomTf = mController.newContainer(secondaryActivity, TASK_ID);
final TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID);
- final SplitPairRule rule = new SplitPairRule.Builder(pair ->
+ final SplitPairRule rule = createSplitPairRuleBuilder(pair ->
pair.first == mActivity && pair.second == secondaryActivity, pair -> false,
metrics -> true)
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
@@ -529,7 +531,7 @@ public class SplitPresenterTest {
@Test
public void testComputeSplitAttributes() {
- final SplitPairRule splitPairRule = new SplitPairRule.Builder(
+ final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
activityPair -> true,
activityIntentPair -> true,
windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS))
@@ -561,15 +563,36 @@ public class SplitPresenterTest {
SplitAttributes.SplitType.RatioSplitType.splitEqually()
)
).build();
+ final Function calculator =
+ params -> splitAttributes;
- mController.setSplitAttributesCalculator(params -> {
- return splitAttributes;
- });
+ mController.setSplitAttributesCalculator(calculator);
assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties,
splitPairRule, null /* minDimensionsPair */));
}
+ @Test
+ public void testComputeSplitAttributesOnHingeSplitTypeOnDeviceWithoutFoldingFeature() {
+ final SplitAttributes hingeSplitAttrs = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+ SplitAttributes.SplitType.RatioSplitType.splitEqually()))
+ .build();
+ final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+ activityPair -> true,
+ activityIntentPair -> true,
+ windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS))
+ .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+ .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+ .setDefaultSplitAttributes(hingeSplitAttrs)
+ .build();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ doReturn(null).when(mPresenter).getFoldingFeature(any());
+
+ assertEquals(hingeSplitAttrs, mPresenter.computeSplitAttributes(taskProperties,
+ splitPairRule, null /* minDimensionsPair */));
+ }
+
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
final Configuration activityConfig = new Configuration();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 7d9d8b0f3a06634ee03b2b6f91b17de5399741d4..78b85e642c13310ad02392ecebce0f8b8d355889 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -154,16 +154,51 @@ public class TaskFragmentContainerTest {
null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryContainer */);
doReturn(container1).when(mController).getContainerWithActivity(mActivity);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
// The activity is requested to be reparented, so don't finish it.
- container0.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
+ container0.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController);
verify(mTransaction, never()).finishActivity(any());
- verify(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken());
+ verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken());
verify(mController).removeContainer(container0);
}
+ @Test
+ public void testFinish_alwaysFinishPlaceholder() {
+ // Register container1 as a placeholder
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
+ final TaskFragmentInfo info0 = createMockTaskFragmentInfo(container0, mActivity);
+ container0.setInfo(mTransaction, info0);
+ final Activity placeholderActivity = createMockActivity();
+ final TaskFragmentContainer container1 = new TaskFragmentContainer(placeholderActivity,
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
+ final TaskFragmentInfo info1 = createMockTaskFragmentInfo(container1, placeholderActivity);
+ container1.setInfo(mTransaction, info1);
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder().build();
+ final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(new Intent(),
+ mActivity::equals, (java.util.function.Predicate) i -> false,
+ (java.util.function.Predicate) w -> true)
+ .setDefaultSplitAttributes(splitAttributes)
+ .build();
+ mController.registerSplit(mTransaction, container0, mActivity, container1, rule,
+ splitAttributes);
+
+ // The placeholder TaskFragment should be finished even if the primary is finished with
+ // shouldFinishDependent = false.
+ container0.finish(false /* shouldFinishDependent */, mPresenter, mTransaction, mController);
+
+ assertTrue(container0.isFinished());
+ assertTrue(container1.isFinished());
+ verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken());
+ verify(mPresenter).deleteTaskFragment(mTransaction, container1.getTaskFragmentToken());
+ verify(mController).removeContainer(container0);
+ verify(mController).removeContainer(container1);
+ }
+
@Test
public void testSetInfo() {
final TaskContainer taskContainer = createTestTaskContainer();
@@ -493,8 +528,6 @@ public class TaskFragmentContainerTest {
final TaskFragmentContainer tf1 = new TaskFragmentContainer(
null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
null /* pairedPrimaryTaskFragment */);
- taskContainer.mContainers.add(tf0);
- taskContainer.mContainers.add(tf1);
// When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted
// right above tf0.
@@ -505,6 +538,26 @@ public class TaskFragmentContainerTest {
assertEquals(2, taskContainer.indexOf(tf1));
}
+ @Test
+ public void testNewContainerWithPairedPendingAppearedActivity() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer tf0 = new TaskFragmentContainer(
+ createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+ final TaskFragmentContainer tf1 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+
+ // When tf2 is created with pendingAppearedActivity, tf2 should be inserted below any
+ // TaskFragment without any Activity.
+ final TaskFragmentContainer tf2 = new TaskFragmentContainer(
+ createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+ assertEquals(0, taskContainer.indexOf(tf0));
+ assertEquals(1, taskContainer.indexOf(tf2));
+ assertEquals(2, taskContainer.indexOf(tf1));
+ }
+
@Test
public void testIsVisible() {
final TaskContainer taskContainer = createTestTaskContainer();
diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar
new file mode 100644
index 0000000000000000000000000000000000000000..96ff840b984beaa757a2472732f7708104e1908a
Binary files /dev/null and b/libs/WindowManager/Jetpack/window-extensions-core-release.aar differ
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 84ab4487feeec395ea3e30d21ef6669f6022f676..c3b6916121d057043f2be78037c22abd517101e7 100644
Binary files a/libs/WindowManager/Jetpack/window-extensions-release.aar and b/libs/WindowManager/Jetpack/window-extensions-release.aar differ
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index f615ad6e671b246a675a98f3d2238f007a81dcb6..c7c94246b96a01bf7629165244af1d4175007c0d 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -47,7 +47,9 @@ filegroup {
"src/com/android/wm/shell/sysui/ShellSharedConstants.java",
"src/com/android/wm/shell/common/TransactionPool.java",
"src/com/android/wm/shell/animation/Interpolators.java",
+ "src/com/android/wm/shell/pip/PipContentOverlay.java",
"src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
+ "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
],
path: "src",
}
diff --git a/libs/WindowManager/Shell/res/color-night/taskbar_background.xml b/libs/WindowManager/Shell/res/color-night/taskbar_background.xml
new file mode 100644
index 0000000000000000000000000000000000000000..01df006f1bd294c1c80eb21cbbb6b19bd3cded57
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color-night/taskbar_background.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/split_divider_background.xml b/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml
similarity index 83%
rename from libs/WindowManager/Shell/res/color/split_divider_background.xml
rename to libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml
index 049980803ee3d771e9f56d56e1f94740582a62c6..a3ca74fac4e6c27c06920b42bc956b7d7ab5ae81 100644
--- a/libs/WindowManager/Shell/res/color/split_divider_background.xml
+++ b/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a3ca74fac4e6c27c06920b42bc956b7d7ab5ae81
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml
index b3d2602991062b07ad928101f56def4cbdf29598..876ee02a8adfcb2c87854468ca8ddf6302f389fe 100644
--- a/libs/WindowManager/Shell/res/color/taskbar_background.xml
+++ b/libs/WindowManager/Shell/res/color/taskbar_background.xml
@@ -16,5 +16,5 @@
-->
-
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/unfold_background.xml b/libs/WindowManager/Shell/res/color/unfold_background.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e33eb126012d5bb985355e41ad6d0f67585aaa53
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/unfold_background.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/caption_close_button.xml b/libs/WindowManager/Shell/res/drawable/caption_close_button.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e258564c70f7f4041520ba805e95d8f7ff15c9fd
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_close_button.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml b/libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml
new file mode 100644
index 0000000000000000000000000000000000000000..166552dcb9e831672d46340ac8cd2fb191ac2bf3
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/res/drawable/caption_decor_title.xml b/libs/WindowManager/Shell/res/drawable/caption_decor_title.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6114ad6e277a6fc6292f8fd1f2b456d09ed84279
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_decor_title.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml b/libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7c86888f52263d4081700044207988f7a4e7ca69
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/res/drawable/caption_select_button.xml b/libs/WindowManager/Shell/res/drawable/caption_select_button.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8c60c840717431fdca387b740cc88f3ac5bafe15
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_select_button.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index c9f262398f683065073996690fd66683705e5810..27e0b184f4275fd765813a562f480dfbee7d00dc 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -17,9 +17,10 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="@color/decor_button_dark_color">
+ android:fillColor="@android:color/white" android:pathData="M3,5V3H21V5Z"/>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
index 0bcaa530dc8030602771bbd33ab26eb2ee9165be..91edbf1a7bd4d4bca41e55df932979df06e43aa5 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
@@ -14,11 +14,11 @@
~ limitations under the License.
-->
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+ android:tint="@color/decor_button_dark_color">
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
index 416287d2cbb334ae28d24e08ef4ccc51dcbdd9a8..c6e634c6622c3f6af3dae7996ef272383b062d85 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
@@ -17,5 +17,6 @@
-
+
+
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
index 416287d2cbb334ae28d24e08ef4ccc51dcbdd9a8..ef300604226186f007edaea62ce869eacba64b02 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
@@ -15,7 +15,8 @@
~ limitations under the License.
-->
-
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
index 42572d64b96fde0ebe925a8071f61274096861f8..a2699681e6564dcd75901d1dff3aee513bf6883e 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
@@ -14,7 +14,30 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1f125148775dd9350f785339914874986bd1b8ad
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c247c6e4c8cfb81b0f134bd2bd9a1b923cfa3c04
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4f97e2c7ea0d1b2d1b64be9631661e0d6aa3ee4a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bb14d1961e818de07a6ccef52bbab7ebae2bbc5a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml
similarity index 73%
rename from libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml
rename to libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml
index 0d8811357c058c79c41c673df8ba713e0075994d..e3c18a2db66feb6643d13b5490e607f393faad1f 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml
@@ -1,6 +1,6 @@
-
-
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3aa0981e45aaaefd8aae7423a39bc59ca2b0032c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5053971a17d3a1cbd786e14d0c0633807cc52070
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b6e0172af1df2a83951ebf9f693bd0b1c95e4ad9
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml
new file mode 100644
index 0000000000000000000000000000000000000000..029d83881165644befee5e1e1846565c333e5f70
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml
new file mode 100644
index 0000000000000000000000000000000000000000..592f899d2ecce881bc1e63b5106ff29dbda852c8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index 29945937788bb0a00e0e95e95ea8255d0a3b64e6..b3f8e801bac4efe2d32e1c7d24af6aacaa7cf026 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -25,12 +25,10 @@
android:fillAlpha="0.8"
android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
+ android:translateX="12"
+ android:translateY="12">
+ android:fillColor="@color/compat_controls_text"
+ android:pathData="M3,21V15H5V17.6L8.1,14.5L9.5,15.9L6.4,19H9V21ZM15,21V19H17.6L14.5,15.9L15.9,14.5L19,17.6V15H21V21ZM8.1,9.5 L5,6.4V9H3V3H9V5H6.4L9.5,8.1ZM15.9,9.5 L14.5,8.1 17.6,5H15V3H21V9H19V6.4Z"/>
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decor.xml b/libs/WindowManager/Shell/res/layout/caption_window_decor.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f3d219872001af05fe93f132ccdb4c3a8e24aeda
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decor.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
index 44b2f45052ba342351d5cb75c304f463a46a0cd1..3d3c00381164470bb16c179009fce94cf748abe7 100644
--- a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
@@ -29,11 +29,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingExtra="4sp"
+ android:letterSpacing="0.02"
android:background="@drawable/compat_hint_bubble"
android:padding="16dp"
android:textAlignment="viewStart"
android:textColor="@color/compat_controls_text"
- android:textSize="14sp"/>
+ android:textSize="14sp"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Subhead"
+ />
-
+
-
-
-
-
-
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/handle_menu"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@drawable/desktop_mode_decor_menu_background"
+ android:divider="?android:attr/dividerHorizontal"
+ android:showDividers="middle"
+ android:dividerPadding="18dip">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
index 2a4cc02f0925bc061fa17cbd858a69ba7d13a994..29cf1512e2e5dd8d612e891ab78b3729a0f5f839 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
@@ -17,21 +17,20 @@
+ android:background="@drawable/decor_back_button_dark"/>
-
+ style="@style/LetterboxDialog">
@@ -69,6 +67,8 @@
android:text="@string/letterbox_education_dialog_title"
android:textAlignment="center"
android:textColor="@color/compat_controls_text"
+ android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"
android:textSize="24sp"/>
-
-
+
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5aff4159e135a2bc0451332f70073f39a9de6fb2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml b/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
new file mode 100644
index 0000000000000000000000000000000000000000..49491a7b572c3b9739e08dd89df7668f66754fbb
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 3d50d2262bb8d08945eba574283ee4a8a2a97076..74497bfa28165487fd2cc6aff083b75af933b174 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -47,6 +47,10 @@
"Bo 50%""Bo 30%""Volskerm onder"
+ "Verdeel links"
+ "Verdeel regs"
+ "Verdeel bo"
+ "Verdeel onder""Gebruik eenhandmodus""Swiep van die onderkant van die skerm af op of tik enige plek bo die program om uit te gaan""Begin eenhandmodus"
@@ -82,14 +86,25 @@
"Dubbeltik buite ’n program om dit te herposisioneer""Het dit""Vou uit vir meer inligting."
+ "Herbegin vir ’n beter aansig?"
+ "Jy kan die app herbegin sodat dit beter op jou skerm lyk, maar jy sal dalk jou vordering of enige ongestoorde veranderinge verloor"
+ "Kanselleer"
+ "Herbegin"
+ "Moenie weer wys nie"
+ "Dubbeltik om\nhierdie app te skuif""Maksimeer""Maak klein""Maak toe""Terug""Handvatsel"
+ "Appikoon""Volskerm""Rekenaarmodus""Verdeelde skerm""Meer""Sweef"
+ "Kies"
+ "Skermskoot"
+ "Maak toe"
+ "Maak kieslys toe"
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 83062959b597bac32ac88a9e97f342d8aba0d27d..feca5052981a69a4d30eadfb8c045239fd49e30e 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -47,6 +47,10 @@
"ከላይ 50%""ከላይ 30%""የታች ሙሉ ማያ ገፅ"
+ "ወደ ግራ ከፋፍል"
+ "ወደ ቀኝ ከፋፍል"
+ "ወደ ላይ ከፋፍል"
+ "ወደ ታች ከፋፍል""ባለአንድ እጅ ሁነታን በመጠቀም ላይ""ለመውጣት ከማያው ግርጌ ወደ ላይ ይጥረጉ ወይም ከመተግበሪያው በላይ ማንኛውም ቦታ ላይ መታ ያድርጉ""ባለአንድ እጅ ሁነታ ጀምር"
@@ -82,14 +86,25 @@
"ቦታውን ለመቀየር ከመተግበሪያው ውጪ ሁለቴ መታ ያድርጉ""ገባኝ""ለተጨማሪ መረጃ ይዘርጉ።"
+ "ለተሻለ ዕይታ እንደገና ይጀመር?"
+ "በማያ ገጽዎ ላይ የተሻለ ሆኖ እንዲታይ መተግበሪያውን እንደገና ማስጀመር ይችላሉ ነገር ግን የደረሱበትን የሂደት ደረጃ ወይም ማናቸውንም ያልተቀመጡ ለውጦች ሊያጡ ይችላሉ"
+ "ይቅር"
+ "እንደገና ያስጀምሩ"
+ "ዳግም አታሳይ"
+ "ይህን መተግበሪያ\nለማንቀሳቀስ ሁለቴ መታ ያድርጉ""አስፋ""አሳንስ""ዝጋ""ተመለስ""መያዣ"
+ "የመተግበሪያ አዶ""ሙሉ ማያ""የዴስክቶፕ ሁነታ""የተከፈለ ማያ ገፅ""ተጨማሪ""ተንሳፋፊ"
+ "ምረጥ"
+ "ቅጽበታዊ ገፅ እይታ"
+ "ዝጋ"
+ "ምናሌ ዝጋ"
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 0f74aab789241004c9224674fc7eff2ca6c25953..095233e28cc65091a01af547830d8fd7b503590d 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -47,6 +47,10 @@
"ضبط حجم النافذة العلوية ليكون ٥٠%""ضبط حجم النافذة العلوية ليكون ٣٠%""عرض النافذة السفلية بملء الشاشة"
+ "تقسيم لليسار"
+ "تقسيم لليمين"
+ "تقسيم للأعلى"
+ "تقسيم للأسفل""استخدام وضع \"التصفح بيد واحدة\"""للخروج، مرِّر سريعًا من أسفل الشاشة إلى أعلاها أو انقر في أي مكان فوق التطبيق.""بدء وضع \"التصفح بيد واحدة\""
@@ -82,14 +86,25 @@
"انقر مرّتين خارج تطبيق لتغيير موضعه.""حسنًا""التوسيع للحصول على مزيد من المعلومات"
+ "هل تريد إعادة تشغيل التطبيق لعرضه بشكل أفضل؟"
+ "يمكنك إعادة تشغيل التطبيق حتى يظهر بشكل أفضل على شاشتك، ولكن قد تفقد تقدمك أو أي تغييرات غير محفوظة."
+ "إلغاء"
+ "إعادة التشغيل"
+ "عدم عرض مربّع حوار التأكيد مجددًا"
+ "انقر مرّتَين لنقل\nهذا التطبيق.""تكبير""تصغير""إغلاق""رجوع""مقبض"
+ "رمز التطبيق""ملء الشاشة""وضع سطح المكتب""تقسيم الشاشة""المزيد""نافذة عائمة"
+ "اختيار"
+ "لقطة شاشة"
+ "إغلاق"
+ "إغلاق القائمة"
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index a0213f42b1253dab8c2e872c3b956fb4c0053fbd..1685c8472fdcd399b576313400c2a64bffc74e21 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -47,6 +47,10 @@
"শীর্ষ স্ক্ৰীনখন ৫০% কৰক""শীর্ষ স্ক্ৰীনখন ৩০% কৰক""তলৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"
+ "বাওঁফালে বিভাজন কৰক"
+ "সোঁফালে বিভাজন কৰক"
+ "একেবাৰে ওপৰৰফালে বিভাজন কৰক"
+ "একেবাৰে তলৰফালে বিভাজন কৰক""এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা""বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্টোৰ ওপৰত যিকোনো ঠাইত টিপক""এখন হাতেৰে ব্যৱহাৰ কৰা ম\'ডটো আৰম্ভ কৰক"
@@ -82,14 +86,25 @@
"এপ্টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ বাহিৰত দুবাৰ টিপক""বুজি পালোঁ""অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"
+ "উন্নতভাৱে দেখা পাবলৈ ৰিষ্টাৰ্ট কৰিবনে?"
+ "আপোনাৰ স্ক্ৰীনত উন্নতভাৱে দেখা পাবলৈ আপুনি এপ্টো ৰিষ্টাৰ্ট কৰিব পাৰে, কিন্তু আপুনি আপোনাৰ অগ্ৰগতি অথবা ছেভ নকৰা যিকোনো সালসলনি হেৰুৱাব পাৰে"
+ "বাতিল কৰক"
+ "ৰিষ্টাৰ্ট কৰক"
+ "পুনৰাই নেদেখুৱাব"
+ "এই এপ্টো\nস্থানান্তৰ কৰিবলৈ দুবাৰ টিপক""সৰ্বাধিক মাত্ৰালৈ বঢ়াওক""মিনিমাইজ কৰক""বন্ধ কৰক""উভতি যাওক""হেণ্ডেল"
+ "এপৰ চিহ্ন""সম্পূৰ্ণ স্ক্ৰীন""ডেস্কটপ ম’ড""বিভাজিত স্ক্ৰীন""অধিক""ওপঙা"
+ "বাছনি কৰক"
+ "স্ক্ৰীনশ্বট"
+ "বন্ধ কৰক"
+ "মেনু বন্ধ কৰক"
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index f842bfe13efc8ee31b30cf30c6a40f19c7caad61..fd725d17a6bd3daf2f46aaec8eb2bf6aa0d14d3d 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -47,6 +47,10 @@
"Yuxarı 50%""Yuxarı 30%""Aşağı tam ekran"
+ "Sola ayırın"
+ "Sağa ayırın"
+ "Yuxarı ayırın"
+ "Aşağı ayırın""Birəlli rejim istifadəsi""Çıxmaq üçün ekranın aşağısından yuxarıya doğru sürüşdürün və ya tətbiqin yuxarısında istənilən yerə toxunun""Birəlli rejim başlasın"
@@ -82,14 +86,25 @@
"Tətbiqin yerini dəyişmək üçün kənarına iki dəfə toxunun""Anladım""Ətraflı məlumat üçün genişləndirin."
+ "Daha yaxşı görünüş üçün yenidən başladılsın?"
+ "Tətbiqi yenidən başlada bilərsiniz ki, ekranınızda daha yaxşı görünsün, lakin irəliləyişi və ya yadda saxlanmamış dəyişiklikləri itirə bilərsiniz"
+ "Ləğv edin"
+ "Yenidən başladın"
+ "Yenidən göstərməyin"
+ "Tətbiqi köçürmək üçün\niki dəfə toxunun""Böyüdün""Kiçildin""Bağlayın""Geriyə""Hər kəsə açıq istifadəçi adı"
+ "Tətbiq ikonası""Tam Ekran""Masaüstü Rejimi""Bölünmüş Ekran""Ardı""Üzən pəncərə"
+ "Seçin"
+ "Skrinşot"
+ "Bağlayın"
+ "Menyunu bağlayın"
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 540ae7ce69539a78f6e523191840ad0d67ce689d..edbd7a3613b082d6fd155a809652b93c580e8a89 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -47,6 +47,10 @@
"Gornji ekran 50%""Gornji ekran 30%""Režim celog ekrana za donji ekran"
+ "Podelite levo"
+ "Podelite desno"
+ "Podelite u vrhu"
+ "Podelite u dnu""Korišćenje režima jednom rukom""Da biste izašli, prevucite nagore od dna ekrana ili dodirnite bilo gde iznad aplikacije""Pokrenite režim jednom rukom"
@@ -82,14 +86,25 @@
"Dvaput dodirnite izvan aplikacije da biste promenili njenu poziciju""Važi""Proširite za još informacija."
+ "Želite li da restartujete radi boljeg prikaza?"
+ "Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, ali možete da izgubite napredak ili nesačuvane promene"
+ "Otkaži"
+ "Restartuj"
+ "Ne prikazuj ponovo"
+ "Dvaput dodirnite da biste\npremestili ovu aplikaciju""Uvećajte""Umanjite""Zatvorite""Nazad""Identifikator"
+ "Ikona aplikacije""Preko celog ekrana""Režim za računare""Podeljeni ekran""Još""Plutajuće"
+ "Izaberite"
+ "Snimak ekrana"
+ "Zatvorite"
+ "Zatvorite meni"
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index bea753837b7b44bf458a09d8227863aaeb62f801..1bdf16525c673161dca5d0af5ba3a031180b918f 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -47,6 +47,10 @@
"Верхні экран – 50%""Верхні экран – 30%""Ніжні экран – поўнаэкранны рэжым"
+ "Падзяліць злева"
+ "Падзяліць справа"
+ "Падзяліць уверсе"
+ "Падзяліць унізе""Выкарыстоўваецца рэжым кіравання адной рукой""Каб выйсці, правядзіце па экране пальцам знізу ўверх або націсніце ў любым месцы над праграмай""Запусціць рэжым кіравання адной рукой"
@@ -82,14 +86,25 @@
"Двойчы націсніце экран па-за праграмай, каб перамясціць яе""Зразумела""Разгарнуць для дадатковай інфармацыі"
+ "Перазапусціць?"
+ "Вы можаце перазапусціць праграму, каб яна выглядала лепш на вашым экране, але пры гэтым могуць знікнуць даныя пра ваш прагрэс або незахаваныя змяненні"
+ "Скасаваць"
+ "Перазапусціць"
+ "Больш не паказваць"
+ "Каб перамясціць праграму,\nнацісніце двойчы""Разгарнуць""Згарнуць""Закрыць""Назад""Маркер"
+ "Значок праграмы""На ўвесь экран""Рэжым працоўнага стала""Падзяліць экран""Яшчэ""Зрабіць рухомым акном"
+ "Выбраць"
+ "Здымак экрана"
+ "Закрыць"
+ "Закрыць меню"
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 59915e6b2a6e87984793caf7f742e2584c86acb4..d78435080aafd509991a0be8a3f2566b5271f39e 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -47,6 +47,10 @@
"Горен екран: 50%""Горен екран: 30%""Долен екран: Показване на цял екран"
+ "Разделяне в лявата част"
+ "Разделяне в дясната част"
+ "Разделяне в горната част"
+ "Разделяне в долната част""Използване на режима за работа с една ръка""За изход прекарайте пръст нагоре от долната част на екрана или докоснете произволно място над приложението""Стартиране на режима за работа с една ръка"
@@ -82,14 +86,25 @@
"Докоснете два пъти извън дадено приложение, за да промените позицията му""Разбрах""Разгъване за още информация."
+ "Да се рестартира ли с цел подобряване на изгледа?"
+ "Можете да рестартирате приложението, за да изглежда по-добре на екрана. Възможно е обаче да загубите напредъка си или незапазените промени"
+ "Отказ"
+ "Рестартиране"
+ "Да не се показва отново"
+ "Докоснете двукратно, за да\nпреместите това приложение""Увеличаване""Намаляване""Затваряне""Назад""Манипулатор"
+ "Икона на приложението""Цял екран""Режим за настолни компютри""Разделяне на екрана""Още""Плаващо"
+ "Избиране"
+ "Екранна снимка"
+ "Затваряне"
+ "Затваряне на менюто"
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 63c9684070b6a8732729b3e05c23eddd11be51b3..b587511d538353e856183285685b48c36e32bc00 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -47,6 +47,10 @@
"শীর্ষ ৫০%""শীর্ষ ৩০%""নীচের অংশ নিয়ে পূর্ণ স্ক্রিন"
+ "স্ক্রিনের বাঁদিকে স্প্লিট করুন"
+ "স্ক্রিনের ডানদিকে স্প্লিট করুন"
+ "স্ক্রিনের উপরের দিকে স্প্লিট করুন"
+ "স্প্লিট করার বোতাম""\'এক হাতে ব্যবহার করার মোড\'-এর ব্যবহার""বেরিয়ে আসার জন্য, স্ক্রিনের নিচ থেকে উপরের দিকে সোয়াইপ করুন অথবা অ্যাপ আইকনের উপরে যেকোনও জায়গায় ট্যাপ করুন""\'এক হাতে ব্যবহার করার মোড\' শুরু করুন"
@@ -82,14 +86,25 @@
"কোনও অ্যাপের স্থান পরিবর্তন করতে তার বাইরে ডবল ট্যাপ করুন""বুঝেছি""আরও তথ্যের জন্য বড় করুন।"
+ "আরও ভালভাবে দেখার জন্য রিস্টার্ট করবেন?"
+ "স্ক্রিনে আরও ভালভাবে দেখার জন্য আপনি অ্যাপ রিস্টার্ট করতে পারবেন, এর ফলে চলতে থাকা কোনও প্রক্রিয়া বা সেভ না করা পরিবর্তন হারিয়ে যেতে পারে"
+ "বাতিল করুন"
+ "রিস্টার্ট করুন"
+ "আর দেখতে চাই না"
+ "এই অ্যাপ সরাতে\nডবল ট্যাপ করুন""বড় করুন""ছোট করুন""বন্ধ করুন""ফিরে যান""হাতল"
+ "অ্যাপ আইকন""ফুলস্ক্রিন""ডেস্কটপ মোড""স্প্লিট স্ক্রিন""আরও""ফ্লোট"
+ "বেছে নিন"
+ "স্ক্রিনশট"
+ "বন্ধ করুন"
+ "\'মেনু\' বন্ধ করুন"
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index b725efea6e48e20d9e6623cf24787e6a71da7588..cd101133897551189945001b849aacf4481b5c5d 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -47,6 +47,10 @@
"Gore 50%""Gore 30%""Donji ekran kao cijeli ekran"
+ "Podjela ulijevo"
+ "Podjela udesno"
+ "Podjela nagore"
+ "Podjela nadolje""Korištenje načina rada jednom rukom""Da izađete, prevucite s dna ekrana prema gore ili dodirnite bilo gdje iznad aplikacije""Započinjanje načina rada jednom rukom"
@@ -82,14 +86,25 @@
"Dvaput dodirnite izvan aplikacije da promijenite njen položaj""Razumijem""Proširite za više informacija."
+ "Ponovo pokrenuti za bolji prikaz?"
+ "Možete ponovo pokrenuti aplikaciju da bolje izgleda na ekranu, ali možete izgubiti napredak ili izmjene koje nisu sačuvane"
+ "Otkaži"
+ "Ponovo pokreni"
+ "Ne prikazuj ponovo"
+ "Dodirnite dvaput da\npomjerite aplikaciju""Maksimiziranje""Minimiziranje""Zatvaranje""Nazad""Identifikator"
+ "Ikona aplikacije""Cijeli ekran""Način rada radne površine""Podijeljeni ekran""Više""Lebdeći"
+ "Odabir"
+ "Snimak ekrana"
+ "Zatvaranje"
+ "Zatvaranje menija"
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 9c310d24cf8dae72ad63416cd8fa78627dd37fb1..43781fd4ba9cc5c8506a0368139311e82b03b6bd 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -47,6 +47,10 @@
"Pantalla superior al 50%""Pantalla superior al 30%""Pantalla inferior completa"
+ "Divideix a l\'esquerra"
+ "Divideix a la dreta"
+ "Divideix a la part superior"
+ "Divideix a la part inferior""S\'està utilitzant el mode d\'una mà""Per sortir, llisca cap amunt des de la part inferior de la pantalla o toca qualsevol lloc a sobre de l\'aplicació""Inicia el mode d\'una mà"
@@ -82,14 +86,25 @@
"Fes doble toc fora d\'una aplicació per canviar-ne la posició""Entesos""Desplega per obtenir més informació."
+ "Vols reiniciar per a una millor visualització?"
+ "Pots reiniciar l\'aplicació perquè es vegi millor en pantalla, però és possible que perdis el teu progrés o qualsevol canvi que no hagis desat"
+ "Cancel·la"
+ "Reinicia"
+ "No ho tornis a mostrar"
+ "Fes doble toc per\nmoure aquesta aplicació""Maximitza""Minimitza""Tanca""Enrere""Ansa"
+ "Icona de l\'aplicació""Pantalla completa""Mode d\'escriptori""Pantalla dividida""Més""Flotant"
+ "Selecciona"
+ "Captura de pantalla"
+ "Tanca"
+ "Tanca el menú"
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 4b043717797acc678fb0f0cbe687fa5d88bad857..7d9ffc4f646f80eb42a2ecb76e178bc3947aad99 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -47,6 +47,10 @@
"50 % nahoře""30 % nahoře""Dolní část na celou obrazovku"
+ "Rozdělit vlevo"
+ "Rozdělit vpravo"
+ "Rozdělit nahoře"
+ "Rozdělit dole""Používání režimu jedné ruky""Režim ukončíte, když přejedete prstem z dolní části obrazovky nahoru nebo klepnete kamkoli nad aplikaci""Spustit režim jedné ruky"
@@ -82,14 +86,25 @@
"Dvojitým klepnutím mimo aplikaci změníte její umístění""OK""Rozbalením zobrazíte další informace."
+ "Restartovat pro lepší zobrazení?"
+ "Aplikaci můžete restartovat, aby na obrazovce vypadala lépe, ale můžete přijít o svůj postup nebo o neuložené změny"
+ "Zrušit"
+ "Restartovat"
+ "Tuto zprávu příště nezobrazovat"
+ "Dvojitým klepnutím\npřesunete aplikaci""Maximalizovat""Minimalizovat""Zavřít""Zpět""Úchyt"
+ "Ikona aplikace""Celá obrazovka""Režim počítače""Rozdělená obrazovka""Více""Plovoucí"
+ "Vybrat"
+ "Snímek obrazovky"
+ "Zavřít"
+ "Zavřít nabídku"
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 46f7c6985ec2c72990ad19cde69b38ae282ff9e7..e3ba7875a045b7ca02689760c0330d47723cc47e 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -47,6 +47,10 @@
"Øverste 50 %""Øverste 30 %""Vis nederste del i fuld skærm"
+ "Vis i venstre side"
+ "Vis i højre side"
+ "Vis øverst"
+ "Vis nederst""Brug af enhåndstilstand""Du kan afslutte ved at stryge opad fra bunden af skærmen eller trykke et vilkårligt sted over appen""Start enhåndstilstand"
@@ -82,14 +86,25 @@
"Tryk to gange uden for en app for at justere dens placering""OK""Udvid for at få flere oplysninger."
+ "Vil du genstarte for at få en bedre visning?"
+ "Du kan genstarte appen, så den ser bedre ud på din skærm, men du mister muligvis dine fremskridt og de ændringer, der ikke gemt"
+ "Annuller"
+ "Genstart"
+ "Vis ikke igen"
+ "Tryk to gange\nfor at flytte appen""Maksimér""Minimer""Luk""Tilbage""Håndtag"
+ "Appikon""Fuld skærm""Computertilstand""Opdelt skærm""Mere""Svævende"
+ "Vælg"
+ "Screenshot"
+ "Luk"
+ "Luk menu"
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index b18c585305ba58bcf6c6e78e9404be6708c47fd5..e196b22bb80002f4eefc6fd23c8367c7be70d410 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -47,6 +47,10 @@
"50 % oben""30 % oben""Vollbild unten"
+ "Links teilen"
+ "Rechts teilen"
+ "Oben teilen"
+ "Unten teilen""Einhandmodus wird verwendet""Wenn du die App schließen möchtest, wische vom unteren Rand des Displays nach oben oder tippe auf eine beliebige Stelle oberhalb der App""Einhandmodus starten"
@@ -82,14 +86,25 @@
"Außerhalb einer App doppeltippen, um die Position zu ändern""Ok""Für weitere Informationen maximieren."
+ "Für bessere Darstellung neu starten?"
+ "Du kannst die App neu starten, damit sie an die Bildschirmabmessungen deines Geräts angepasst dargestellt wird – jedoch können dadurch dein Fortschritt oder nicht gespeicherte Änderungen verloren gehen."
+ "Abbrechen"
+ "Neu starten"
+ "Nicht mehr anzeigen"
+ "Zum Verschieben\ndoppeltippen""Maximieren""Minimieren""Schließen""Zurück""Ziehpunkt"
+ "App-Symbol""Vollbild""Desktopmodus""Splitscreen""Mehr""Frei schwebend"
+ "Auswählen"
+ "Screenshot"
+ "Schließen"
+ "Menü schließen"
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index f8a69ef796b9b5fbfcbdd4ab36af8335666bd8d5..ef14084ab56c953d5b5816c653b356e4dce00092 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -47,6 +47,10 @@
"Πάνω 50%""Πάνω 30%""Κάτω πλήρης οθόνη"
+ "Διαχωρισμός αριστερά"
+ "Διαχωρισμός δεξιά"
+ "Διαχωρισμός επάνω"
+ "Διαχωρισμός κάτω""Χρήση λειτουργίας ενός χεριού""Για έξοδο, σύρετε προς τα πάνω από το κάτω μέρος της οθόνης ή πατήστε οπουδήποτε πάνω από την εφαρμογή.""Έναρξη λειτουργίας ενός χεριού"
@@ -82,14 +86,25 @@
"Πατήστε δύο φορές έξω από μια εφαρμογή για να αλλάξετε τη θέση της""Το κατάλαβα""Ανάπτυξη για περισσότερες πληροφορίες."
+ "Επανεκκίνηση για καλύτερη προβολή;"
+ "Μπορείτε να επανεκκινήσετε την εφαρμογή για να προβάλλεται καλύτερα στην οθόνη σας, αλλά η πρόοδός σας και τυχόν μη αποθηκευμένες αλλαγές ενδέχεται να χαθούν."
+ "Ακύρωση"
+ "Επανεκκίνηση"
+ "Να μην εμφανιστεί ξανά"
+ "Πατήστε δύο φορές για\nμετακίνηση αυτής της εφαρμογής""Μεγιστοποίηση""Ελαχιστοποίηση""Κλείσιμο""Πίσω""Λαβή"
+ "Εικονίδιο εφαρμογής""Πλήρης οθόνη""Λειτουργία επιφάνειας εργασίας""Διαχωρισμός οθόνης""Περισσότερα""Κινούμενο"
+ "Επιλογή"
+ "Στιγμιότυπο οθόνης"
+ "Κλείσιμο"
+ "Κλείσιμο μενού"
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 8e46c3e0999e0b771791c1da134882bdfaf40a73..7367a7af35e9cdc48202b04df7d9d68db9951ec0 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -47,6 +47,10 @@
"Top 50%""Top 30%""Bottom full screen"
+ "Split left"
+ "Split right"
+ "Split top"
+ "Split bottom""Using one-handed mode""To exit, swipe up from the bottom of the screen or tap anywhere above the app""Start one-handed mode"
@@ -82,14 +86,25 @@
"Double-tap outside an app to reposition it""Got it""Expand for more information."
+ "Restart for a better view?"
+ "You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"
+ "Cancel"
+ "Restart"
+ "Don\'t show again"
+ "Double-tap to\nmove this app""Maximise""Minimise""Close""Back""Handle"
+ "App icon""Full screen""Desktop mode""Split screen""More""Float"
+ "Select"
+ "Screenshot"
+ "Close"
+ "Close menu"
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 7cbbf64991cdfddf36211ac23b0eb7400b028b15..4c311936b0edc8b87989cddebcb1e842b6740c00 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -47,6 +47,10 @@
"Top 50%""Top 30%""Bottom full screen"
+ "Split left"
+ "Split right"
+ "Split top"
+ "Split bottom""Using one-handed mode""To exit, swipe up from the bottom of the screen or tap anywhere above the app""Start one-handed mode"
@@ -82,14 +86,25 @@
"Double-tap outside an app to reposition it""Got it""Expand for more information."
+ "Restart for a better view?"
+ "You can restart the app so it looks better on your screen, but you may lose your progress or any unsaved changes"
+ "Cancel"
+ "Restart"
+ "Don’t show again"
+ "Double-tap to\nmove this app""Maximize""Minimize""Close""Back""Handle"
+ "App Icon""Fullscreen""Desktop Mode""Split Screen""More""Float"
+ "Select"
+ "Screenshot"
+ "Close"
+ "Close Menu"
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 8e46c3e0999e0b771791c1da134882bdfaf40a73..7367a7af35e9cdc48202b04df7d9d68db9951ec0 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -47,6 +47,10 @@
"Top 50%""Top 30%""Bottom full screen"
+ "Split left"
+ "Split right"
+ "Split top"
+ "Split bottom""Using one-handed mode""To exit, swipe up from the bottom of the screen or tap anywhere above the app""Start one-handed mode"
@@ -82,14 +86,25 @@
"Double-tap outside an app to reposition it""Got it""Expand for more information."
+ "Restart for a better view?"
+ "You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"
+ "Cancel"
+ "Restart"
+ "Don\'t show again"
+ "Double-tap to\nmove this app""Maximise""Minimise""Close""Back""Handle"
+ "App icon""Full screen""Desktop mode""Split screen""More""Float"
+ "Select"
+ "Screenshot"
+ "Close"
+ "Close menu"
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 8e46c3e0999e0b771791c1da134882bdfaf40a73..7367a7af35e9cdc48202b04df7d9d68db9951ec0 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -47,6 +47,10 @@
"Top 50%""Top 30%""Bottom full screen"
+ "Split left"
+ "Split right"
+ "Split top"
+ "Split bottom""Using one-handed mode""To exit, swipe up from the bottom of the screen or tap anywhere above the app""Start one-handed mode"
@@ -82,14 +86,25 @@
"Double-tap outside an app to reposition it""Got it""Expand for more information."
+ "Restart for a better view?"
+ "You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"
+ "Cancel"
+ "Restart"
+ "Don\'t show again"
+ "Double-tap to\nmove this app""Maximise""Minimise""Close""Back""Handle"
+ "App icon""Full screen""Desktop mode""Split screen""More""Float"
+ "Select"
+ "Screenshot"
+ "Close"
+ "Close menu"
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index b2720be3d8a0368901844f1ec659ecde018e6f24..eb61cf97d6b1c0df3ae431ad097fbb1b0dcf4cc6 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -47,6 +47,10 @@
"Top 50%""Top 30%""Bottom full screen"
+ "Split left"
+ "Split right"
+ "Split top"
+ "Split bottom""Using one-handed mode""To exit, swipe up from the bottom of the screen or tap anywhere above the app""Start one-handed mode"
@@ -82,14 +86,25 @@
"Double-tap outside an app to reposition it""Got it""Expand for more information."
+ "Restart for a better view?"
+ "You can restart the app so it looks better on your screen, but you may lose your progress or any unsaved changes"
+ "Cancel"
+ "Restart"
+ "Don’t show again"
+ "Double-tap to\nmove this app""Maximize""Minimize""Close""Back""Handle"
+ "App Icon""Fullscreen""Desktop Mode""Split Screen""More""Float"
+ "Select"
+ "Screenshot"
+ "Close"
+ "Close Menu"
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 47445a7a048811913c4799ae4e5f1c3fb328c1a0..d11cdbdc2979851c12f3e2cf4d17cea743716bf3 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -47,6 +47,10 @@
"Superior: 50%""Superior: 30%""Pantalla inferior completa"
+ "Dividir a la izquierda"
+ "Dividir a la derecha"
+ "Dividir en la parte superior"
+ "Dividir en la parte inferior""Cómo usar el modo de una mano""Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o presiona cualquier parte arriba de la app""Iniciar el modo de una mano"
@@ -82,14 +86,25 @@
"Presiona dos veces fuera de una app para cambiar su ubicación""Entendido""Expande para obtener más información."
+ "¿Quieres reiniciar para que se vea mejor?"
+ "Puedes reiniciar la app para que se vea mejor en la pantalla, pero podrías perder tu progreso o cualquier cambio que no hayas guardado"
+ "Cancelar"
+ "Reiniciar"
+ "No volver a mostrar"
+ "Presiona dos veces\npara mover esta app""Maximizar""Minimizar""Cerrar""Atrás""Controlador"
+ "Ícono de la app""Pantalla completa""Modo de escritorio""Pantalla dividida""Más""Flotante"
+ "Seleccionar"
+ "Captura de pantalla"
+ "Cerrar"
+ "Cerrar menú"
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 6c45231c32615fd88e1c3bba8366301f672ee0a6..227f5f428102165451007c77be8d04eccc5359ea 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -47,6 +47,10 @@
"Superior 50%""Superior 30%""Pantalla inferior completa"
+ "Dividir en la parte izquierda"
+ "Dividir en la parte derecha"
+ "Dividir en la parte superior"
+ "Dividir en la parte inferior""Usar modo Una mano""Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o toca cualquier zona que haya encima de la aplicación""Iniciar modo Una mano"
@@ -82,14 +86,25 @@
"Toca dos veces fuera de una aplicación para cambiarla de posición""Entendido""Mostrar más información"
+ "¿Reiniciar para que se vea mejor?"
+ "Puedes reiniciar la aplicación para que se vea mejor en la pantalla, pero puedes perder tu progreso o cualquier cambio que no hayas guardado"
+ "Cancelar"
+ "Reiniciar"
+ "No volver a mostrar"
+ "Toca dos veces para\nmover esta aplicación""Maximizar""Minimizar""Cerrar""Atrás""Controlador"
+ "Icono de la aplicación""Pantalla completa""Modo Escritorio""Pantalla dividida""Más""Flotante"
+ "Seleccionar"
+ "Captura de pantalla"
+ "Cerrar"
+ "Cerrar menú"
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index a8dc08cbbd2700b80d36141971f059b8c13118ce..2ed0fdf25f1839e2df4b920016851ab8d42a6e11 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -47,6 +47,10 @@
"Ülemine: 50%""Ülemine: 30%""Alumine täisekraan"
+ "Jaga vasakule"
+ "Jaga paremale"
+ "Jaga üles"
+ "Jaga alla""Ühekäerežiimi kasutamine""Väljumiseks pühkige ekraani alaosast üles või puudutage rakenduse kohal olevat ala""Ühekäerežiimi käivitamine"
@@ -82,14 +86,25 @@
"Topeltpuudutage rakendusest väljaspool, et selle asendit muuta""Selge""Laiendage lisateabe saamiseks."
+ "Kas taaskäivitada parema vaate saavutamiseks?"
+ "Saate rakenduse taaskäivitada, et see näeks ekraanikuval parem välja, kuid võite kaotada edenemise või salvestamata muudatused."
+ "Tühista"
+ "Taaskäivita"
+ "Ära kuva uuesti"
+ "Rakenduse teisaldamiseks\ntopeltpuudutage""Maksimeeri""Minimeeri""Sule""Tagasi""Käepide"
+ "Rakenduse ikoon""Täisekraan""Lauaarvuti režiim""Jagatud ekraanikuva""Rohkem""Hõljuv"
+ "Vali"
+ "Ekraanipilt"
+ "Sule"
+ "Sule menüü"
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 9fbf0a0700193766ad2e0a01853685c265d89d8e..4aeb7e3ddf7ffa518ee7ceb564fde3a4009645a7 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -47,6 +47,10 @@
"Ezarri goialdea % 50en""Ezarri goialdea % 30en""Ezarri behealdea pantaila osoan"
+ "Zatitu ezkerraldean"
+ "Zatitu eskuinaldean"
+ "Zatitu goialdean"
+ "Zatitu behealdean""Esku bakarreko modua erabiltzea""Irteteko, pasatu hatza pantailaren behealdetik gora edo sakatu aplikazioaren gainaldea""Abiarazi esku bakarreko modua"
@@ -82,14 +86,25 @@
"Aplikazioaren posizioa aldatzeko, sakatu birritan haren kanpoaldea""Ados""Informazio gehiago lortzeko, zabaldu hau."
+ "Aplikazioa berrabiarazi nahi duzu itxura hobea izan dezan?"
+ "Aplikazioa berrabiarazi egin dezakezu itxura hobea izan dezan, baina agian garapena edo gorde gabeko aldaketak galduko dituzu"
+ "Utzi"
+ "Berrabiarazi"
+ "Ez erakutsi berriro"
+ "Sakatu birritan\naplikazioa mugitzeko""Maximizatu""Minimizatu""Itxi""Atzera""Kontu-izena"
+ "Aplikazioaren ikonoa""Pantaila osoa""Ordenagailuetarako modua""Pantaila zatitua""Gehiago""Leiho gainerakorra"
+ "Hautatu"
+ "Pantaila-argazkia"
+ "Itxi"
+ "Itxi menua"
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 12e99a30a593ada0a57ec134109003c89142b409..703996a6f12d91e7d52f9a70359a5b21de3e08e9 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -47,6 +47,10 @@
"٪۵۰ بالا""٪۳۰ بالا""تمامصفحه پایین"
+ "تقسیم از چپ"
+ "تقسیم از راست"
+ "تقسیم از بالا"
+ "تقسیم از پایین""استفاده از حالت یکدستی""برای خارج شدن، از پایین صفحهنمایش تند بهطرف بالا بکشید یا در هر جایی از بالای برنامه که میخواهید ضربه بزنید""آغاز «حالت یکدستی»"
@@ -82,14 +86,25 @@
"برای جابهجا کردن برنامه، بیرون از آن دوضربه بزنید""متوجهام""برای اطلاعات بیشتر، گسترده کنید."
+ "برای نمایش بهتر بازراهاندازی شود؟"
+ "میتوانید برنامه را بازراهاندازی کنید تا بهتر روی صفحهنمایش نشان داده شود، اما ممکن است پیشرفت یا تغییرات ذخیرهنشده را ازدست بدهید"
+ "لغو کردن"
+ "بازراهاندازی"
+ "دوباره نشان داده نشود"
+ "برای جابهجا کردن این برنامه\nدوضربه بزنید""بزرگ کردن""کوچک کردن""بستن""برگشتن""دستگیره"
+ "نماد برنامه""تمامصفحه""حالت رایانه""صفحهٔ دونیمه""بیشتر""شناور"
+ "انتخاب"
+ "نماگرفت"
+ "بستن"
+ "بستن منو"
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 86199f3cf0927424615cc81ea99e7e0edf172d43..0cd1260d54dc8d988bb996feb76223a603b8a5e7 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -47,6 +47,10 @@
"Yläosa 50 %""Yläosa 30 %""Alaosa koko näytölle"
+ "Vasemmalla"
+ "Oikealla"
+ "Ylhäällä"
+ "Alhaalla""Yhden käden moodin käyttö""Poistu pyyhkäisemällä ylös näytön alareunasta tai napauttamalla sovelluksen yllä""Käynnistä yhden käden moodi"
@@ -82,14 +86,25 @@
"Kaksoisnapauta sovelluksen ulkopuolella, jos haluat siirtää sitä""OK""Katso lisätietoja laajentamalla."
+ "Käynnistetäänkö sovellus uudelleen, niin saat paremman näkymän?"
+ "Voit käynnistää sovelluksen uudelleen, jotta se näyttää paremmalta näytöllä, mutta saatat menettää edistymisesi tai tallentamattomat muutokset"
+ "Peru"
+ "Käynnistä uudelleen"
+ "Älä näytä uudelleen"
+ "Kaksoisnapauta, jos\nhaluat siirtää sovellusta""Suurenna""Pienennä""Sulje""Takaisin""Kahva"
+ "Sovelluskuvake""Koko näyttö""Työpöytätila""Jaettu näyttö""Lisää""Kelluva ikkuna"
+ "Valitse"
+ "Kuvakaappaus"
+ "Sulje"
+ "Sulje valikko"
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 4c0f3cf78fbe499f55fd0f9d83b04830a6126c99..016069f65dc006635c8c9c34b6a64aec8e9cb89d 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -47,6 +47,10 @@
"50 % dans le haut""30 % dans le haut""Plein écran dans le bas"
+ "Diviser à gauche"
+ "Diviser à droite"
+ "Diviser dans la partie supérieure"
+ "Diviser dans la partie inférieure""Utiliser le mode Une main""Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'application""Démarrer le mode Une main"
@@ -82,14 +86,25 @@
"Touchez deux fois à côté d\'une application pour la repositionner""OK""Développer pour en savoir plus."
+ "Redémarrer pour un meilleur affichage?"
+ "Vous pouvez redémarrer l\'application pour qu\'elle s\'affiche mieux sur votre écran, mais il se peut que vous perdiez votre progression ou toute modification non enregistrée"
+ "Annuler"
+ "Redémarrer"
+ "Ne plus afficher"
+ "Toucher deux fois pour\ndéplacer cette application""Agrandir""Réduire""Fermer""Retour""Identifiant"
+ "Icône de l\'application""Plein écran""Mode Bureau""Écran partagé""Plus""Flottant"
+ "Sélectionner"
+ "Capture d\'écran"
+ "Fermer"
+ "Fermer le menu"
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 5336bfca1cc044206223624d0a07dbd139e50b05..5cc8ff07014ddd647f4502387df6b28b8c32d913 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -47,6 +47,10 @@
"Écran du haut à 50 %""Écran du haut à 30 %""Écran du bas en plein écran"
+ "Affichée à gauche"
+ "Affichée à droite"
+ "Affichée en haut"
+ "Affichée en haut""Utiliser le mode une main""Pour quitter, balayez l\'écran de bas en haut ou appuyez n\'importe où au-dessus de l\'application""Démarrer le mode une main"
@@ -82,14 +86,25 @@
"Appuyez deux fois en dehors d\'une appli pour la repositionner""OK""Développez pour obtenir plus d\'informations"
+ "Redémarrer pour améliorer l\'affichage ?"
+ "Vous pouvez redémarrer l\'appli pour un meilleur rendu sur votre écran, mais il se peut que vous perdiez votre progression ou les modifications non enregistrées"
+ "Annuler"
+ "Redémarrer"
+ "Ne plus afficher"
+ "Appuyez deux fois\npour déplacer cette appli""Agrandir""Réduire""Fermer""Retour""Poignée"
+ "Icône d\'application""Plein écran""Mode ordinateur""Écran partagé""Plus""Flottante"
+ "Sélectionner"
+ "Capture d\'écran"
+ "Fermer"
+ "Fermer le menu"
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 6e215a1f5b81043b92f3d32766b3e188bb89792d..3980017aa12b1f1aaf4b921aa6e533c4a057a30e 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -47,6 +47,10 @@
"50 % arriba""30 % arriba""Pantalla completa abaixo"
+ "Dividir (esquerda)"
+ "Dividir (dereita)"
+ "Dividir (arriba)"
+ "Dividir (abaixo)""Como se usa o modo dunha soa man?""Para saír, pasa o dedo cara arriba desde a parte inferior da pantalla ou toca calquera lugar da zona situada encima da aplicación""Iniciar modo dunha soa man"
@@ -82,14 +86,25 @@
"Toca dúas veces fóra da aplicación para cambiala de posición""Entendido""Despregar para obter máis información."
+ "Queres reiniciar a aplicación para que se vexa mellor?"
+ "Podes reiniciar a aplicación para que se vexa mellor na pantalla, pero podes perder o progreso que levas feito ou calquera cambio que non gardases"
+ "Cancelar"
+ "Reiniciar"
+ "Non mostrar outra vez"
+ "Toca dúas veces para\nmover esta aplicación""Maximizar""Minimizar""Pechar""Atrás""Controlador"
+ "Icona de aplicación""Pantalla completa""Modo de escritorio""Pantalla dividida""Máis""Flotante"
+ "Seleccionar"
+ "Captura de pantalla"
+ "Pechar"
+ "Pechar o menú"
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index ad086bb1f7123397bc610817e61c6481aa274b0d..7a2ad0e93855c21c6c1ac9144c545f7f352c7e61 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -47,6 +47,10 @@
"શીર્ષ 50%""શીર્ષ 30%""તળિયાની પૂર્ણ સ્ક્રીન"
+ "ડાબે વિભાજિત કરો"
+ "જમણે વિભાજિત કરો"
+ "ઉપર વિભાજિત કરો"
+ "નીચે વિભાજિત કરો""એક-હાથે વાપરો મોડનો ઉપયોગ કરી રહ્યાં છીએ""બહાર નીકળવા માટે, સ્ક્રીનની નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરો અથવા ઍપના આઇકન પર ગમે ત્યાં ટૅપ કરો""એક-હાથે વાપરો મોડ શરૂ કરો"
@@ -82,14 +86,25 @@
"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બહાર બે વાર ટૅપ કરો""સમજાઈ ગયું""વધુ માહિતી માટે મોટું કરો."
+ "બહેતર વ્યૂ માટે ફરીથી શરૂ કરીએ?"
+ "તમે ઍપને ફરીથી શરૂ કરી શકો છો, જેથી તે તમારી સ્ક્રીન પર વધુ સારી રીતે દેખાય, પરંતુ આમ કરવાથી તમે તમારી ઍપ પર કરી હોય એવી કોઈ પ્રક્રિયાની પ્રગતિ અથવા સાચવ્યા ન હોય એવો કોઈપણ ફેરફાર ગુમાવી શકો છો"
+ "રદ કરો"
+ "ફરી શરૂ કરો"
+ "ફરીથી બતાવશો નહીં"
+ "આ ઍપને ખસેડવા માટે\nબે વાર ટૅપ કરો""મોટું કરો""નાનું કરો""બંધ કરો""પાછળ""હૅન્ડલ"
+ "ઍપનું આઇકન""પૂર્ણસ્ક્રીન""ડેસ્કટૉપ મોડ""સ્ક્રીનને વિભાજિત કરો""વધુ""ફ્લોટિંગ વિન્ડો"
+ "પસંદ કરો"
+ "સ્ક્રીનશૉટ"
+ "બંધ કરો"
+ "મેનૂ બંધ કરો"
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index bed39fb774007bf5881063939cb1e469804629b9..bd3fbb86891f5cde4ff50ab6186b83088bd5c91b 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -47,6 +47,10 @@
"ऊपर की स्क्रीन को 50% बनाएं""ऊपर की स्क्रीन को 30% बनाएं""नीचे की स्क्रीन को फ़ुल स्क्रीन बनाएं"
+ "स्क्रीन को बाएं हिस्से में स्प्लिट करें"
+ "स्क्रीन को दाएं हिस्से में स्प्लिट करें"
+ "स्क्रीन को ऊपर के हिस्से में स्प्लिट करें"
+ "स्क्रीन को सबसे नीचे वाले हिस्से में स्प्लिट करें""वन-हैंडेड मोड का इस्तेमाल करना""इस मोड से बाहर निकलने के लिए, स्क्रीन के सबसे निचले हिस्से से ऊपर की ओर स्वाइप करें या ऐप्लिकेशन के बाहर कहीं भी टैप करें""वन-हैंडेड मोड चालू करें"
@@ -82,14 +86,25 @@
"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बाहर दो बार टैप करें""ठीक है""ज़्यादा जानकारी के लिए बड़ा करें."
+ "बेहतर व्यू पाने के लिए ऐप्लिकेशन को रीस्टार्ट करना है?"
+ "स्क्रीन पर ऐप्लिकेशन का बेहतर व्यू पाने के लिए उसे रीस्टार्ट करें. हालांकि, इससे अब तक किया गया काम और सेव न किए गए बदलाव मिट सकते हैं"
+ "रद्द करें"
+ "रीस्टार्ट करें"
+ "फिर से न दिखाएं"
+ "ऐप्लिकेशन की जगह बदलने के लिए\nदो बार टैप करें""बड़ा करें""विंडो छोटी करें""बंद करें""वापस जाएं""हैंडल"
+ "ऐप्लिकेशन आइकॉन""फ़ुलस्क्रीन""डेस्कटॉप मोड""स्प्लिट स्क्रीन मोड""ज़्यादा देखें""फ़्लोट"
+ "चुनें"
+ "स्क्रीनशॉट"
+ "बंद करें"
+ "मेन्यू बंद करें"
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 5fa60dc2a62393f07de296d55642654079d4fe70..c69b9710162602f81f837f77e4d897c69a7530f7 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -47,6 +47,10 @@
"Gornji zaslon na 50%""Gornji zaslon na 30%""Donji zaslon u cijeli zaslon"
+ "Podijeli lijevo"
+ "Podijeli desno"
+ "Podijeli gore"
+ "Podijeli dolje""Korištenje načina rada jednom rukom""Za izlaz prijeđite prstom od dna zaslona prema gore ili dodirnite bio gdje iznad aplikacije""Pokretanje načina rada jednom rukom"
@@ -82,14 +86,25 @@
"Dvaput dodirnite izvan aplikacije da biste je premjestili""Shvaćam""Proširite da biste saznali više."
+ "Želite li ponovno pokrenuti za bolji pregled?"
+ "Možete ponovno pokrenuti aplikaciju tako da bolje izgleda na zaslonu, no mogli biste izgubiti napredak ili sve nespremljene promjene"
+ "Odustani"
+ "Pokreni ponovno"
+ "Ne prikazuj ponovno"
+ "Dvaput dodirnite da biste\npremjestili ovu aplikaciju""Maksimiziraj""Minimiziraj""Zatvori""Natrag""Pokazivač"
+ "Ikona aplikacije""Puni zaslon""Stolni način rada""Razdvojeni zaslon""Više""Plutajući"
+ "Odaberite"
+ "Snimka zaslona"
+ "Zatvorite"
+ "Zatvorite izbornik"
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 221c329020a020382810aa3aeae3093fe02dc727..c4cdf6de709f0b6b24b25e7c28363a55b0ffc3f5 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -47,6 +47,10 @@
"Felső 50%-ra""Felső 30%-ra""Alsó teljes képernyőre"
+ "Osztás a képernyő bal oldalán"
+ "Osztás a képernyő jobb oldalán"
+ "Osztás a képernyő tetején"
+ "Osztás alul""Egykezes mód használata""A kilépéshez csúsztasson felfelé a képernyő aljáról, vagy koppintson az alkalmazás felett a képernyő bármelyik részére""Egykezes mód indítása"
@@ -82,14 +86,25 @@
"Koppintson duplán az alkalmazáson kívül az áthelyezéséhez""Értem""Kibontással további információkhoz juthat."
+ "Újraindítja a jobb megjelenítés érdekében?"
+ "Újraindíthatja az alkalmazást a képernyőn való jobb megjelenítés érdekében, de elveszítheti az előrehaladását és az esetleges nem mentett változásokat"
+ "Mégse"
+ "Újraindítás"
+ "Ne jelenjen meg többé"
+ "Koppintson duplán\naz alkalmazás áthelyezéséhez""Teljes méret""Kis méret""Bezárás""Vissza""Fogópont"
+ "Alkalmazásikon""Teljes képernyő""Asztali üzemmód""Osztott képernyő""Továbbiak""Lebegő"
+ "Kiválasztás"
+ "Képernyőkép"
+ "Bezárás"
+ "Menü bezárása"
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 7be9941d2a5e7878129b6ed1259ddbd7fc90d6f2..05df3fa7c0d342d24be7233b2335fa46575f59da 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -47,6 +47,10 @@
"Վերևի էկրանը՝ 50%""Վերևի էկրանը՝ 30%""Ներքևի էկրանը՝ լիաէկրան"
+ "Հավելվածը ձախ կողմում"
+ "Հավելվածը աջ կողմում"
+ "Հավելվածը վերևում"
+ "Հավելվածը ներքևում""Ինչպես օգտվել մեկ ձեռքի ռեժիմից""Դուրս գալու համար մատը սահեցրեք էկրանի ներքևից վերև կամ հպեք հավելվածի վերևում որևէ տեղ։""Գործարկել մեկ ձեռքի ռեժիմը"
@@ -82,14 +86,25 @@
"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար""Եղավ""Ծավալեք՝ ավելին իմանալու համար։"
+ "Վերագործարկե՞լ հավելվածը"
+ "Դուք կարող եք վերագործարկել հավելվածը, որպեսզի այն ավելի լավ ցուցադրվի ձեր էկրանին, սակայն ձեր առաջընթացը և չպահված փոփոխությունները կկորեն"
+ "Չեղարկել"
+ "Վերագործարկել"
+ "Այլևս ցույց չտալ"
+ "Կրկնակի հպեք՝\nհավելվածը տեղափոխելու համար""Ծավալել""Ծալել""Փակել""Հետ""Նշիչ"
+ "Հավելվածի պատկերակ""Լիաէկրան""Համակարգչի ռեժիմ""Տրոհված էկրան""Ավելին""Լողացող պատուհան"
+ "Ընտրել"
+ "Սքրինշոթ"
+ "Փակել"
+ "Փակել ընտրացանկը"
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 4e760ef6450c308b8a1d969075d233a211fb7efb..af57ccaeeeca20b28723b2973d2ba0512923467d 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -47,6 +47,10 @@
"Atas 50%""Atas 30%""Layar penuh di bawah"
+ "Pisahkan ke kiri"
+ "Pisahkan ke kanan"
+ "Pisahkan ke atas"
+ "Pisahkan ke bawah""Menggunakan mode satu tangan""Untuk keluar, geser layar dari bawah ke atas atau ketuk di mana saja di atas aplikasi""Mulai mode satu tangan"
@@ -82,14 +86,25 @@
"Ketuk dua kali di luar aplikasi untuk mengubah posisinya""Oke""Luaskan untuk melihat informasi selengkapnya."
+ "Mulai ulang untuk melihat tampilan yang lebih baik?"
+ "Anda dapat memulai ulang aplikasi agar terlihat lebih baik di layar, tetapi Anda mungkin kehilangan progres atau perubahan yang belum disimpan"
+ "Batal"
+ "Mulai ulang"
+ "Jangan tampilkan lagi"
+ "Ketuk dua kali untuk\nmemindahkan aplikasi ini""Maksimalkan""Minimalkan""Tutup""Kembali""Tuas"
+ "Ikon Aplikasi""Layar Penuh""Mode Desktop""Layar Terpisah""Lainnya""Mengambang"
+ "Pilih"
+ "Screenshot"
+ "Tutup"
+ "Tutup Menu"
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 9e3a36c564a8bec66aca6ba266e81e0696706a82..512deb252274407a9d5517328b9c59bb53a99be2 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -47,6 +47,10 @@
"Efri 50%""Efri 30%""Neðri á öllum skjánum"
+ "Skipta vinstra megin"
+ "Skipta hægra megin"
+ "Skipta efst"
+ "Skipta neðst""Notkun einhentrar stillingar""Til að loka skaltu strjúka upp frá neðri hluta skjásins eða ýta hvar sem er fyrir ofan forritið""Ræsa einhenta stillingu"
@@ -82,14 +86,25 @@
"Ýttu tvisvar utan við forrit til að færa það""Ég skil""Stækka til að sjá frekari upplýsingar."
+ "Viltu endurræsa til að fá betri sýn?"
+ "Þú getur endurræst forritið svo það falli betur að skjánum en þú gætir tapað framvindunni eða óvistuðum breytingum"
+ "Hætta við"
+ "Endurræsa"
+ "Ekki sýna þetta aftur"
+ "Ýttu tvisvar til\nað færa þetta forrit""Stækka""Minnka""Loka""Til baka""Handfang"
+ "Tákn forrits""Allur skjárinn""Skjáborðsstilling""Skjáskipting""Meira""Reikult"
+ "Velja"
+ "Skjámynd"
+ "Loka"
+ "Loka valmynd"
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index d2595f7682d2b3e168defaf257e9ff28c19fdfe7..482666632bc9224ceea6ed9405c5bfb1ebd871d3 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -47,6 +47,10 @@
"Schermata superiore al 50%""Schermata superiore al 30%""Schermata inferiore a schermo intero"
+ "Dividi a sinistra"
+ "Dividi a destra"
+ "Dividi in alto"
+ "Dividi in basso""Usare la modalità a una mano""Per uscire, scorri verso l\'alto dalla parte inferiore dello schermo oppure tocca un punto qualsiasi sopra l\'app""Avvia la modalità a una mano"
@@ -82,14 +86,25 @@
"Tocca due volte fuori da un\'app per riposizionarla""OK""Espandi per avere ulteriori informazioni."
+ "Vuoi riavviare per migliorare la visualizzazione?"
+ "Puoi riavviare l\'app affinché venga visualizzata meglio sullo schermo, ma potresti perdere i tuoi progressi o eventuali modifiche non salvate"
+ "Annulla"
+ "Riavvia"
+ "Non mostrare più"
+ "Tocca due volte per\nspostare questa app""Ingrandisci""Riduci a icona""Chiudi""Indietro""Handle"
+ "Icona dell\'app""Schermo intero""Modalità desktop""Schermo diviso""Altro""Mobile"
+ "Seleziona"
+ "Screenshot"
+ "Chiudi"
+ "Chiudi il menu"
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 883596ebcd6fdc3b777ca1612764cfc631e60988..578d2478347b2186acf380dd586178db2e5443ba 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -47,6 +47,10 @@
"עליון 50%""למעלה 30%""מסך תחתון מלא"
+ "פיצול שמאלה"
+ "פיצול ימינה"
+ "פיצול למעלה"
+ "פיצול למטה""איך להשתמש בתכונה \'מצב שימוש ביד אחת\'""כדי לצאת, יש להחליק למעלה מתחתית המסך או להקיש במקום כלשהו במסך מעל האפליקציה""הפעלה של מצב שימוש ביד אחת"
@@ -82,14 +86,25 @@
"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש""הבנתי""מרחיבים כדי לקבל מידע נוסף."
+ "להפעיל מחדש לתצוגה טובה יותר?"
+ "אפשר להפעיל מחדש את האפליקציה כדי שהיא תוצג באופן טוב יותר במסך, אבל ייתכן שההתקדמות שלך או כל שינוי שלא נשמר יאבדו"
+ "ביטול"
+ "הפעלה מחדש"
+ "אין להציג שוב"
+ "אפשר להקיש הקשה כפולה כדי\nלהעביר את האפליקציה למקום אחר""הגדלה""מזעור""סגירה""חזרה""נקודת אחיזה"
+ "סמל האפליקציה""מסך מלא""ממשק המחשב""מסך מפוצל""עוד""בלונים"
+ "בחירה"
+ "צילום מסך"
+ "סגירה"
+ "סגירת התפריט"
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 627477637d7241e6cce9a2aa1fe422553b25d2ef..0e4477a29520eff2cbce6c8d84623ce226cd8929 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -47,6 +47,10 @@
"上 50%""上 30%""下部全画面"
+ "左に分割"
+ "右に分割"
+ "上に分割"
+ "下に分割""片手モードの使用""終了するには、画面を下から上にスワイプするか、アプリの上側の任意の場所をタップします""片手モードを開始します"
@@ -82,14 +86,25 @@
"位置を変えるにはアプリの外側をダブルタップしてください""OK""開くと詳細が表示されます。"
+ "アプリを再起動して画面表示を最適化しますか?"
+ "アプリを再起動することにより表示を最適化できますが、保存されていない変更は失われる可能性があります"
+ "キャンセル"
+ "再起動"
+ "次回から表示しない"
+ "ダブルタップすると\nこのアプリを移動できます""最大化""最小化""閉じる""戻る""ハンドル"
+ "アプリのアイコン""全画面表示""デスクトップ モード""分割画面""その他""フローティング"
+ "選択"
+ "スクリーンショット"
+ "閉じる"
+ "メニューを閉じる"
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 6cf7d7827ee93ae35d1ac24e2444ae78b60356e7..f50a9cdde3557276b80e4f22bbbb74733bbc0980 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -47,6 +47,10 @@
"ზედა ეკრანი — 50%""ზედა ეკრანი — 30%""ქვედა ნაწილის სრულ ეკრანზე გაშლა"
+ "გაყოფა მარცხნივ"
+ "გაყოფა მარჯვნივ"
+ "გაყოფა ზემოთ"
+ "გაყოფა ქვემოთ""ცალი ხელის რეჟიმის გამოყენება""გასასვლელად გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ ან შეეხეთ ნებისმიერ ადგილას აპის ზემოთ""ცალი ხელის რეჟიმის დაწყება"
@@ -82,14 +86,25 @@
"ორმაგად შეეხეთ აპის გარშემო სივრცეს, რათა ის სხვაგან გადაიტანოთ""გასაგებია""დამატებითი ინფორმაციისთვის გააფართოეთ."
+ "გსურთ გადატვირთვა უკეთესი ხედისთვის?"
+ "შეგიძლიათ გადატვირთოთ აპი იმისათვის, რომ თქვენს ეკრანზე უკეთესად გამოჩნდეს, თუმცა თქვენ მიერ შესრულებული მოქმედებები შეიძლება დაიკარგოს ან ცვლილებების შენახვა ვერ მოხერხდეს"
+ "გაუქმება"
+ "გადატვირთვა"
+ "აღარ გამოჩნდეს"
+ "ამ აპის გადასატანად\nორმაგად შეეხეთ მას""მაქსიმალურად გაშლა""ჩაკეცვა""დახურვა""უკან""იდენტიფიკატორი"
+ "აპის ხატულა""სრულ ეკრანზე""დესკტოპის რეჟიმი""ეკრანის გაყოფა""სხვა""ფარფატი"
+ "არჩევა"
+ "ეკრანის ანაბეჭდი"
+ "დახურვა"
+ "მენიუს დახურვა"
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 6991b133a916fc7732bd796f87593a225629a9c6..02dd330851338820768dc1d114b7f61d9d696014 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -47,6 +47,10 @@
"50% жоғарғы жақта""30% жоғарғы жақта""Төменгісін толық экранға шығару"
+ "Сол жақтан шығару"
+ "Оң жақтан шығару"
+ "Жоғарыдан шығару"
+ "Астынан шығару""Бір қолмен енгізу режимін пайдалану""Шығу үшін экранның төменгі жағынан жоғары қарай сырғытыңыз немесе қолданбаның үстінен кез келген жерден түртіңіз.""Бір қолмен енгізу режимін іске қосу"
@@ -82,14 +86,25 @@
"Қолданбаның орнын өзгерту үшін одан тыс жерді екі рет түртіңіз.""Түсінікті""Толығырақ ақпарат алу үшін терезені жайыңыз."
+ "Көріністі жақсарту үшін өшіріп қосу керек пе?"
+ "Экранда жақсырақ көрінуі үшін қолданбаны өшіріп қосуыңызға болады, бірақ мұндайда ағымдағы прогресс өшіп қалуы немесе сақталмаған өзгерістерді жоғалтуыңыз мүмкін."
+ "Бас тарту"
+ "Өшіріп қосу"
+ "Қайта көрсетілмесін"
+ "Бұл қолданбаны басқа орынға\nжылжыту үшін екі рет түртіңіз.""Жаю""Кішірейту""Жабу""Артқа""Идентификатор"
+ "Қолданба белгішесі""Толық экран""Компьютер режимі""Экранды бөлу""Қосымша""Қалқыма"
+ "Таңдау"
+ "Скриншот"
+ "Жабу"
+ "Мәзірді жабу"
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 79aca620f3486d4a77575256f48e93f41f3feb4e..1be2465d7d9c16524b9c804f91f7ecb163fa0bd7 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -47,6 +47,10 @@
"ខាងលើ 50%""ខាងលើ 30%""អេក្រង់ពេញខាងក្រោម"
+ "បំបែកខាងឆ្វេង"
+ "បំបែកខាងស្ដាំ"
+ "បំបែកខាងលើ"
+ "បំបែកខាងក្រោម""កំពុងប្រើមុខងារប្រើដៃម្ខាង""ដើម្បីចាកចេញ សូមអូសឡើងលើពីផ្នែកខាងក្រោមអេក្រង់ ឬចុចផ្នែកណាមួយនៅខាងលើកម្មវិធី""ចាប់ផ្ដើមមុខងារប្រើដៃម្ខាង"
@@ -82,14 +86,25 @@
"ចុចពីរដងនៅក្រៅកម្មវិធី ដើម្បីប្ដូរទីតាំងកម្មវិធីនោះ""យល់ហើយ""ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"
+ "ចាប់ផ្ដើមឡើងវិញ ដើម្បីទទួលបានទិដ្ឋភាពកាន់តែស្អាតឬ?"
+ "អ្នកអាចចាប់ផ្ដើមកម្មវិធីឡើងវិញ ដើម្បីឱ្យកម្មវិធីនេះមើលទៅស្អាតជាងមុននៅលើអេក្រង់របស់អ្នក ប៉ុន្តែអ្នកអាចនឹងបាត់បង់ដំណើរការរបស់អ្នក ឬការកែប្រែណាមួយដែលអ្នកមិនបានរក្សាទុក"
+ "បោះបង់"
+ "ចាប់ផ្ដើមឡើងវិញ"
+ "កុំបង្ហាញម្ដងទៀត"
+ "ចុចពីរដងដើម្បី\nផ្លាស់ទីកម្មវិធីនេះ""ពង្រីក""បង្រួម""បិទ""ថយក្រោយ""ឈ្មោះអ្នកប្រើប្រាស់"
+ "រូបកម្មវិធី""អេក្រង់ពេញ""មុខងារកុំព្យូទ័រ""មុខងារបំបែកអេក្រង់""ច្រើនទៀត""អណ្ដែត"
+ "ជ្រើសរើស"
+ "រូបថតអេក្រង់"
+ "បិទ"
+ "បិទម៉ឺនុយ"
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index db0398f7acf66941c627a39743322c37de751361..106053172a03ffab15ec01629fc348cd2bbd058e 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -47,6 +47,10 @@
"50% ಮೇಲಕ್ಕೆ""30% ಮೇಲಕ್ಕೆ""ಕೆಳಗಿನ ಪೂರ್ಣ ಪರದೆ"
+ "ಎಡಕ್ಕೆ ವಿಭಜಿಸಿ"
+ "ಬಲಕ್ಕೆ ವಿಭಜಿಸಿ"
+ "ಮೇಲಕ್ಕೆ ವಿಭಜಿಸಿ"
+ "ಕೆಳಕ್ಕೆ ವಿಭಜಿಸಿ""ಒಂದು ಕೈ ಮೋಡ್ ಬಳಸುವುದರ ಬಗ್ಗೆ""ನಿರ್ಗಮಿಸಲು, ಸ್ಕ್ರೀನ್ನ ಕೆಳಗಿನಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ ಅಥವಾ ಆ್ಯಪ್ನ ಮೇಲೆ ಎಲ್ಲಿಯಾದರೂ ಟ್ಯಾಪ್ ಮಾಡಿ""ಒಂದು ಕೈ ಮೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ"
@@ -82,14 +86,25 @@
"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಹೊರಗೆ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ""ಸರಿ""ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ವಿಸ್ತೃತಗೊಳಿಸಿ."
+ "ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬೇಕೆ?"
+ "ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬಹುದು, ಇದರಿಂದ ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ಆ್ಯಪ್ ಉತ್ತಮವಾಗಿ ಕಾಣಿಸುತ್ತದೆ, ಆದರೆ ನಿಮ್ಮ ಪ್ರಗತಿಯನ್ನು ಅಥವಾ ಯಾವುದೇ ಉಳಿಸದ ಬದಲಾವಣೆಗಳನ್ನು ನೀವು ಕಳೆದುಕೊಳ್ಳಬಹುದು"
+ "ರದ್ದುಮಾಡಿ"
+ "ಮರುಪ್ರಾರಂಭಿಸಿ"
+ "ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"
+ "ಈ ಆ್ಯಪ್ ಅನ್ನು ಸರಿಸಲು\nಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ""ಹಿಗ್ಗಿಸಿ""ಕುಗ್ಗಿಸಿ""ಮುಚ್ಚಿರಿ""ಹಿಂದಕ್ಕೆ""ಹ್ಯಾಂಡಲ್"
+ "ಆ್ಯಪ್ ಐಕಾನ್""ಫುಲ್ಸ್ಕ್ರೀನ್""ಡೆಸ್ಕ್ಟಾಪ್ ಮೋಡ್""ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್""ಇನ್ನಷ್ಟು""ಫ್ಲೋಟ್"
+ "ಆಯ್ಕೆಮಾಡಿ"
+ "ಸ್ಕ್ರೀನ್ಶಾಟ್"
+ "ಮುಚ್ಚಿ"
+ "ಮೆನು ಮುಚ್ಚಿ"
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index f9b495a38f4fd7966a62e1f3342fae434e3dd08f..d82577b37d80a007c23cc95fd5e0c5220a977ac6 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -47,6 +47,10 @@
"위쪽 화면 50%""위쪽 화면 30%""아래쪽 화면 전체화면"
+ "왼쪽으로 분할"
+ "오른쪽으로 분할"
+ "위쪽으로 분할"
+ "아래쪽으로 분할""한 손 사용 모드 사용하기""화면 하단에서 위로 스와이프하거나 앱 상단을 탭하여 종료합니다.""한 손 사용 모드 시작"
@@ -82,14 +86,25 @@
"앱 위치를 조정하려면 앱 외부를 두 번 탭합니다.""확인""추가 정보는 펼쳐서 확인하세요."
+ "화면에 맞게 보도록 다시 시작할까요?"
+ "앱을 다시 시작하면 화면에 더 잘 맞게 볼 수는 있지만 진행 상황 또는 저장되지 않은 변경사항을 잃을 수도 있습니다."
+ "취소"
+ "다시 시작"
+ "다시 표시 안함"
+ "두 번 탭하여\n이 앱 이동""최대화""최소화""닫기""뒤로""핸들"
+ "앱 아이콘""전체 화면""데스크톱 모드""화면 분할""더보기""플로팅"
+ "선택"
+ "스크린샷"
+ "닫기"
+ "메뉴 닫기"
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 10c9f19ee460fba59314b0def084e07d636bdf87..2fe1c403402db550f984c76bc0b3b3bc958874b9 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -47,6 +47,10 @@
"Үстүнкү экранды 50%""Үстүнкү экранды 30%""Ылдыйкы экранды толук экран режимине өткөрүү"
+ "Солго бөлүү"
+ "Оңго бөлүү"
+ "Өйдө бөлүү"
+ "Ылдый бөлүү""Бир кол режимин колдонуу""Чыгуу үчүн экранды ылдый жагынан өйдө сүрүңүз же колдонмонун өйдө жагын басыңыз""Бир кол режимин баштоо"
@@ -82,14 +86,25 @@
"Колдонмону жылдыруу үчүн сырт жагын эки жолу таптаңыз""Түшүндүм""Толук маалымат алуу үчүн жайып көрүңүз."
+ "Жакшыраак көрүү үчүн өчүрүп күйгүзөсүзбү?"
+ "Экранда жакшыраак көрүү үчүн колдонмону өчүрүп күйгүзө аласыз, бирок аткарылган иш же сакталбаган өзгөрүүлөр өчүрүлүшү мүмкүн"
+ "Токтотуу"
+ "Өчүрүп күйгүзүү"
+ "Экинчи көрүнбөсүн"
+ "Бул колдонмону жылдыруу үчүн\nэки жолу таптаңыз""Чоңойтуу""Кичирейтүү""Жабуу""Артка""Маркер"
+ "Колдонмонун сүрөтчөсү""Толук экран""Компьютер режими""Экранды бөлүү""Дагы""Калкыма"
+ "Тандоо"
+ "Скриншот"
+ "Жабуу"
+ "Менюну жабуу"
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 8e42aa346669b44d3d9710261eefb3090d651bc3..f9501795be7fa8a6fef18ce72e5c1f99946fdf4d 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -47,6 +47,10 @@
"ເທິງສຸດ 50%""ເທິງສຸດ 30%""ເຕັມໜ້າຈໍລຸ່ມສຸດ"
+ "ແຍກຊ້າຍ"
+ "ແຍກຂວາ"
+ "ແຍກເທິງ"
+ "ແຍກລຸ່ມ""ກຳລັງໃຊ້ໂໝດມືດຽວ""ເພື່ອອອກ, ໃຫ້ປັດຂຶ້ນຈາກລຸ່ມສຸດຂອງໜ້າຈໍ ຫຼື ແຕະບ່ອນໃດກໍໄດ້ຢູ່ເໜືອແອັບ""ເລີ່ມໂໝດມືດຽວ"
@@ -82,14 +86,25 @@
"ແຕະສອງເທື່ອໃສ່ນອກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່""ເຂົ້າໃຈແລ້ວ""ຂະຫຍາຍເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ."
+ "ຣີສະຕາດເພື່ອໃຫ້ມີມຸມມອງທີ່ດີຂຶ້ນບໍ?"
+ "ທ່ານສາມາດຣີສະຕາດແອັບໄດ້ເພື່ອໃຫ້ມັນເບິ່ງດີຂຶ້ນໃນໜ້າຈໍຂອງທ່ານ ແຕ່ທ່ານອາດຈະສູນເສຍຄວາມຄືບໜ້າ ຫຼື ການປ່ຽນແປງທີ່ບໍ່ໄດ້ບັນທຶກໄວ້"
+ "ຍົກເລີກ"
+ "ຣີສະຕາດ"
+ "ບໍ່ຕ້ອງສະແດງອີກ"
+ "ແຕະສອງເທື່ອເພື່ອ\nຍ້າຍແອັບນີ້""ຂະຫຍາຍໃຫຍ່ສຸດ""ຫຍໍ້ລົງ""ປິດ""ກັບຄືນ""ມືບັງຄັບ"
+ "ໄອຄອນແອັບ""ເຕັມຈໍ""ໂໝດເດັສທັອບ""ແບ່ງໜ້າຈໍ""ເພີ່ມເຕີມ""ລອຍ"
+ "ເລືອກ"
+ "ຮູບໜ້າຈໍ"
+ "ປິດ"
+ "ປິດເມນູ"
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index dc9969095cf53fe81b1ec0bd6af0d99bbbb867da..1b0c64d9a719dfcf20c3c0086512c97d3fcc6abb 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -47,6 +47,10 @@
"Viršutinis ekranas 50 %""Viršutinis ekranas 30 %""Apatinis ekranas viso ekrano režimu"
+ "Išskaidyti kairėn"
+ "Išskaidyti dešinėn"
+ "Išskaidyti viršuje"
+ "Išskaidyti apačioje""Vienos rankos režimo naudojimas""Jei norite išeiti, perbraukite aukštyn nuo ekrano apačios arba palieskite bet kur virš programos""Pradėti vienos rankos režimą"
@@ -82,14 +86,25 @@
"Dukart palieskite už programos ribų, kad pakeistumėte jos poziciją""Supratau""Išskleiskite, jei reikia daugiau informacijos."
+ "Paleisti iš naujo, kad būtų geresnis vaizdas?"
+ "Galite iš naujo paleisti programą, kad ji geriau atrodytų ekrane, bet galite prarasti eigą ir neišsaugotus pakeitimus"
+ "Atšaukti"
+ "Paleisti iš naujo"
+ "Daugiau neberodyti"
+ "Dukart palieskite, kad\nperkeltumėte šią programą""Padidinti""Sumažinti""Uždaryti""Atgal""Rankenėlė"
+ "Programos piktograma""Visas ekranas""Stalinio kompiuterio režimas""Išskaidyto ekrano režimas""Daugiau""Slankusis langas"
+ "Pasirinkti"
+ "Ekrano kopija"
+ "Uždaryti"
+ "Uždaryti meniu"
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index bd2eef42690b57a28a2a3d8efd3186988e317b95..0cb63954f495dd879ec0f2ed834dab6d6a670f3f 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -47,6 +47,10 @@
"Augšdaļa 50%""Augšdaļa 30%""Apakšdaļu pa visu ekrānu"
+ "Sadalījums pa kreisi"
+ "Sadalījums pa labi"
+ "Sadalījums augšdaļā"
+ "Sadalījums apakšdaļā""Vienas rokas režīma izmantošana""Lai izietu, velciet augšup no ekrāna apakšdaļas vai pieskarieties jebkurā vietā virs lietotnes""Pāriet vienas rokas režīmā"
@@ -82,14 +86,25 @@
"Lai pārvietotu lietotni, veiciet dubultskārienu ārpus lietotnes""Labi""Izvērsiet, lai iegūtu plašāku informāciju."
+ "Vai restartēt, lai uzlabotu skatu?"
+ "Varat restartēt lietotni, lai tā labāk izskatītos ekrānā, taču, iespējams, zaudēsiet paveikto vai nesaglabātas izmaiņas (ja tādas ir)."
+ "Atcelt"
+ "Restartēt"
+ "Vairs nerādīt"
+ "Veiciet dubultskārienu,\nlai pārvietotu šo lietotni""Maksimizēt""Minimizēt""Aizvērt""Atpakaļ""Turis"
+ "Lietotnes ikona""Pilnekrāna režīms""Darbvirsmas režīms""Sadalīt ekrānu""Vairāk""Peldošs"
+ "Atlasīt"
+ "Ekrānuzņēmums"
+ "Aizvērt"
+ "Aizvērt izvēlni"
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 8e73daec46efa444127924796dcb45bcc886e1fa..a21e50281262e16727b59cd949c593a5954f7cb9 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -47,6 +47,10 @@
"Горниот 50%""Горниот 30%""Долниот на цел екран"
+ "Подели налево"
+ "Подели надесно"
+ "Подели нагоре"
+ "Подели долу""Користење на режимот со една рака""За да излезете, повлечете нагоре од дното на екранот или допрете каде било над апликацијата""Започни го режимот со една рака"
@@ -82,14 +86,25 @@
"Допрете двапати надвор од некоја апликација за да ја преместите""Сфатив""Проширете за повеќе информации."
+ "Да се рестартира за подобар приказ?"
+ "Може да ја рестартирате апликацијата за да изгледа подобро на екранот, но може да го изгубите напредокот или незачуваните промени"
+ "Откажи"
+ "Рестартирај"
+ "Не прикажувај повторно"
+ "Допрете двапати за да ја\nпоместите апликацијава""Зголеми""Минимизирај""Затвори""Назад""Прекар"
+ "Икона на апликацијата""Цел екран""Режим за компјутер""Поделен екран""Повеќе""Лебдечко"
+ "Изберете"
+ "Слика од екранот"
+ "Затворете"
+ "Затворете го менито"
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 16927bf19523d78809057b69255cc476a29fecdd..5ef137a0fcd8058fa6685cec00272eb099418847 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -47,6 +47,10 @@
"മുകളിൽ 50%""മുകളിൽ 30%""താഴെ പൂർണ്ണ സ്ക്രീൻ"
+ "ഇടത് ഭാഗത്തേക്ക് വിഭജിക്കുക"
+ "വലത് ഭാഗത്തേക്ക് വിഭജിക്കുക"
+ "മുകളിലേക്ക് വിഭജിക്കുക"
+ "താഴേക്ക് വിഭജിക്കുക""ഒറ്റക്കൈ മോഡ് എങ്ങനെ ഉപയോഗിക്കാം""പുറത്ത് കടക്കാൻ, സ്ക്രീനിന്റെ ചുവടെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക അല്ലെങ്കിൽ ആപ്പിന് മുകളിലായി എവിടെയെങ്കിലും ടാപ്പ് ചെയ്യുക""ഒറ്റക്കൈ മോഡ് ആരംഭിച്ചു"
@@ -82,14 +86,25 @@
"ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ അതിന് പുറത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക""മനസ്സിലായി""കൂടുതൽ വിവരങ്ങൾക്ക് വികസിപ്പിക്കുക."
+ "മെച്ചപ്പെട്ട കാഴ്ചയ്ക്കായി റീസ്റ്റാർട്ട് ചെയ്യണോ?"
+ "ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്യുകയാണെങ്കിൽ ഇത് നിങ്ങളുടെ സ്ക്രീനിൽ മെച്ചപ്പെട്ടതായി കാണും, എന്നാൽ ഇതുവരെയുള്ള പുരോഗതിയും സംരക്ഷിക്കാത്ത മാറ്റങ്ങളും നിങ്ങൾക്ക് നഷ്ടമാകും"
+ "റദ്ദാക്കുക"
+ "റീസ്റ്റാർട്ട് ചെയ്യൂ"
+ "വീണ്ടും കാണിക്കരുത്"
+ "ഈ ആപ്പ് നീക്കാൻ\nഡബിൾ ടാപ്പ് ചെയ്യുക""വലുതാക്കുക""ചെറുതാക്കുക""അടയ്ക്കുക""മടങ്ങുക""ഹാൻഡിൽ"
+ "ആപ്പ് ഐക്കൺ""പൂർണ്ണസ്ക്രീൻ""ഡെസ്ക്ടോപ്പ് മോഡ്""സ്ക്രീൻ വിഭജനം""കൂടുതൽ""ഫ്ലോട്ട്"
+ "തിരഞ്ഞെടുക്കുക"
+ "സ്ക്രീൻഷോട്ട്"
+ "അടയ്ക്കുക"
+ "മെനു അടയ്ക്കുക"
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 264f9a0a4db9c724214f26bdff27963851e93622..ecb65d11e56a6be89a3d4c9306ba8803abb783b6 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -47,6 +47,10 @@
"Дээд 50%""Дээд 30%""Доод бүтэн дэлгэц"
+ "Зүүн талд хуваах"
+ "Баруун талд хуваах"
+ "Дээд талд хуваах"
+ "Доод талд хуваах""Нэг гарын горимыг ашиглаж байна""Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл апп дээр хүссэн газраа товшино уу""Нэг гарын горимыг эхлүүлэх"
@@ -82,14 +86,25 @@
"Аппыг дахин байрлуулахын тулд гадна талд нь хоёр товшино""Ойлголоо""Нэмэлт мэдээлэл авах бол дэлгэнэ үү."
+ "Харагдах байдлыг сайжруулахын тулд дахин эхлүүлэх үү?"
+ "Та аппыг дахин эхлүүлэх боломжтой бөгөөд ингэснээр энэ нь таны дэлгэцэд илүү сайн харагдах хэдий ч та явцаа эсвэл хадгалаагүй аливаа өөрчлөлтөө алдаж магадгүй"
+ "Цуцлах"
+ "Дахин эхлүүлэх"
+ "Дахиж бүү харуул"
+ "Энэ аппыг зөөхийн тулд\nхоёр товшино уу""Томруулах""Багасгах""Хаах""Буцах""Бариул"
+ "Aппын дүрс тэмдэг""Бүтэн дэлгэц""Дэлгэцийн горим""Дэлгэцийг хуваах""Бусад""Хөвөгч"
+ "Сонгох"
+ "Дэлгэцийн агшин"
+ "Хаах"
+ "Цэсийг хаах"
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 7a475edcd964cb0bb4a4239e5fdb1e25806b1079..dba4d57e8dd8a38a44ccc5999e25579e0522fa93 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -47,6 +47,10 @@
"शीर्ष 50%""शीर्ष 10""तळाशी फुल स्क्रीन"
+ "डावीकडे स्प्लिट करा"
+ "उजवीकडे स्प्लिट करा"
+ "सर्वात वरती स्प्लिट करा"
+ "खालती स्प्लिट करा""एकहाती मोड वापरणे""बाहेर पडण्यासाठी स्क्रीनच्या खालून वरच्या दिशेने स्वाइप करा किंवा ॲपवर कोठेही टॅप करा""एकहाती मोड सुरू करा"
@@ -82,14 +86,25 @@
"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या बाहेर दोनदा टॅप करा""समजले""अधिक माहितीसाठी विस्तार करा."
+ "आणखी चांगल्या प्रकारे दिसावे यासाठी रीस्टार्ट करायचे आहे का?"
+ "तुम्ही अॅप रीस्टार्ट करू शकता, जेणेकरून ते तुमच्या स्क्रीनवर आणखी चांगल्या प्रकारे दिसेल, पण तुमची प्रगती किंवा कोणतेही सेव्ह न केलेले बदल तुम्ही गमवाल"
+ "रद्द करा"
+ "रीस्टार्ट करा"
+ "पुन्हा दाखवू नका"
+ "हे ॲप हलवण्यासाठी\nदोनदा टॅप करा""मोठे करा""लहान करा""बंद करा""मागे जा""हँडल"
+ "अॅप आयकन""फुलस्क्रीन""डेस्कटॉप मोड""स्प्लिट स्क्रीन""आणखी""फ्लोट"
+ "निवडा"
+ "स्क्रीनशॉट"
+ "बंद करा"
+ "मेनू बंद करा"
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index be1dc24ccfcb76406536c2ea8b6bb2bbc9c29d2e..d0e9f66f6c23c38efd8331f2bbe6bc673aebd61a 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -47,6 +47,10 @@
"Atas 50%""Atas 30%""Skrin penuh bawah"
+ "Pisah kiri"
+ "Pisah kanan"
+ "Pisah atas"
+ "Pisah bawah""Menggunakan mod sebelah tangan""Untuk keluar, leret ke atas daripada bahagian bawah skrin atau ketik pada mana-mana di bahagian atas apl""Mulakan mod sebelah tangan"
@@ -82,14 +86,25 @@
"Ketik dua kali di luar apl untuk menempatkan semula apl itu""OK""Kembangkan untuk mendapatkan maklumat lanjut."
+ "Mulakan semula untuk mendapatkan paparan yang lebih baik?"
+ "Anda boleh memulakan semula apl supaya apl kelihatan lebih baik pada skrin anda tetapi anda mungkin akan hilang kemajuan anda atau apa-apa perubahan yang belum disimpan"
+ "Batal"
+ "Mulakan semula"
+ "Jangan tunjukkan lagi"
+ "Ketik dua kali untuk\nalih apl ini""Maksimumkan""Minimumkan""Tutup""Kembali""Pemegang"
+ "Ikon Apl""Skrin penuh""Mod Desktop""Skrin Pisah""Lagi""Terapung"
+ "Pilih"
+ "Tangkapan skrin"
+ "Tutup"
+ "Tutup Menu"
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 7b2b7c5dd01ec30e05e0d9418ade31b3d7389291..8618c27e80853ce2d853d4254142c8b32a699f70 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -47,6 +47,10 @@
"အပေါ်ဘက် မျက်နှာပြင် ၅၀%""အပေါ်ဘက် မျက်နှာပြင် ၃၀%""အောက်ခြေ မျက်နှာပြင်အပြည့်"
+ "ဘယ်ဘက်ကို ခွဲရန်"
+ "ညာဘက်ကို ခွဲရန်"
+ "ထိပ်ပိုင်းကို ခွဲရန်"
+ "အောက်ခြေကို ခွဲရန်""လက်တစ်ဖက်သုံးမုဒ် အသုံးပြုခြင်း""ထွက်ရန် ဖန်သားပြင်၏အောက်ခြေမှ အပေါ်သို့ပွတ်ဆွဲပါ သို့မဟုတ် အက်ပ်အပေါ်ဘက် မည်သည့်နေရာတွင်မဆို တို့ပါ""လက်တစ်ဖက်သုံးမုဒ်ကို စတင်လိုက်သည်"
@@ -82,14 +86,25 @@
"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ""နားလည်ပြီ""နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"
+ "ပိုကောင်းသောမြင်ကွင်းအတွက် ပြန်စမလား။"
+ "အက်ပ်ကို ပြန်စပါက ၎င်းသည် စခရင်ပေါ်တွင် ပိုကြည့်ကောင်းသွားသော်လည်း သင်၏ လုပ်ငန်းစဉ် (သို့) မသိမ်းရသေးသော အပြောင်းအလဲများကို ဆုံးရှုံးနိုင်သည်"
+ "မလုပ်တော့"
+ "ပြန်စရန်"
+ "နောက်ထပ်မပြပါနှင့်"
+ "ဤအက်ပ်ကို ရွှေ့ရန်\nနှစ်ချက်တို့ပါ""ချဲ့ရန်""ချုံ့ရန်""ပိတ်ရန်""နောက်သို့""သုံးသူအမည်"
+ "အက်ပ်သင်္ကေတ""ဖန်သားပြင်အပြည့်""ဒက်စ်တော့မုဒ်""မျက်နှာပြင် ခွဲ၍ပြသရန်""ပိုပြပါ""မျှောရန်"
+ "ရွေးရန်"
+ "ဖန်သားပြင်ဓာတ်ပုံ"
+ "ပိတ်ရန်"
+ "မီနူး ပိတ်ရန်"
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 3e18b4968464ce6cd7427b5cf846c20ebd9215bc..8c319a279eaad747387f1ebde134f2784c7710ce 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -47,6 +47,10 @@
"Sett størrelsen på den øverste delen av skjermen til 50 %""Sett størrelsen på den øverste delen av skjermen til 30 %""Utvid den nederste delen av skjermen til hele skjermen"
+ "Del opp til venstre"
+ "Del opp til høyre"
+ "Del opp øverst"
+ "Del opp nederst""Bruk av enhåndsmodus""For å avslutte, sveip opp fra bunnen av skjermen eller trykk hvor som helst over appen""Start enhåndsmodus"
@@ -82,14 +86,25 @@
"Dobbelttrykk utenfor en app for å flytte den""Greit""Vis for å få mer informasjon."
+ "Vil du starte på nytt for bedre visning?"
+ "Du kan starte appen på nytt, slik at den ser bedre ut på skjermen, men du kan miste fremdrift eller ulagrede endringer"
+ "Avbryt"
+ "Start på nytt"
+ "Ikke vis dette igjen"
+ "Dobbelttrykk for\nå flytte denne appen""Maksimer""Minimer""Lukk""Tilbake""Håndtak"
+ "Appikon""Fullskjerm""Skrivebordmodus""Delt skjerm""Mer""Svevende"
+ "Velg"
+ "Skjermdump"
+ "Lukk"
+ "Lukk menyen"
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 4b10f5a1a886b03b811314cddfd0172ecb936163..f3f18765e455a8fb2cf00bff8dbb3681628e658e 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -47,6 +47,10 @@
"माथिल्लो भाग ५०%""माथिल्लो भाग ३०%""तल्लो भाग फुल स्क्रिन"
+ "बायाँतिर स्प्लिट गर्नुहोस्"
+ "दायाँतिर स्प्लिट गर्नुहोस्"
+ "सिरानतिर स्प्लिट गर्नुहोस्"
+ "पुछारतिर स्प्लिट गर्नुहोस्""एक हाते मोड प्रयोग गरिँदै छ""बाहिर निस्कन, स्क्रिनको पुछारबाट माथितिर स्वाइप गर्नुहोस् वा एपभन्दा माथि जुनसुकै ठाउँमा ट्याप गर्नुहोस्""एक हाते मोड सुरु गर्नुहोस्"
@@ -82,14 +86,25 @@
"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको बाहिर डबल ट्याप गर्नुहोस्""बुझेँ""थप जानकारी प्राप्त गर्न चाहनुहुन्छ भने एक्स्पान्ड गर्नुहोस्।"
+ "अझ राम्रोसँग देखिने बनाउन एप रिस्टार्ट गर्ने हो?"
+ "यो एप तपाईंको स्क्रिनमा अझ राम्रोसँग देखियोस् भन्नाका लागि तपाईं सो एप रिस्टार्ट गर्न सक्नुहुन्छ तर तपाईंले अहिलेसम्म गरेका क्रियाकलाप वा सेभ गर्न बाँकी परिवर्तनहरू हट्न सक्छन्"
+ "रद्द गर्नुहोस्"
+ "रिस्टार्ट गर्नुहोस्"
+ "फेरि नदेखाइयोस्"
+ "यो एप सार्न डबल\nट्याप गर्नुहोस्""ठुलो बनाउनुहोस्""मिनिमाइज गर्नुहोस्""बन्द गर्नुहोस्""पछाडि""ह्यान्डल"
+ "एपको आइकन""फुल स्क्रिन""डेस्कटप मोड""स्प्लिट स्क्रिन""थप""फ्लोट"
+ "चयन गर्नुहोस्"
+ "स्क्रिनसट"
+ "बन्द गर्नुहोस्"
+ "मेनु बन्द गर्नुहोस्"
diff --git a/libs/WindowManager/Shell/res/values-night/colors.xml b/libs/WindowManager/Shell/res/values-night/colors.xml
index 83c4d93982f4ee9ef14dcfe0165f62f332c364bd..5c6bb57a7f1cf8899e7842b3ac8a0c44d4198bf7 100644
--- a/libs/WindowManager/Shell/res/values-night/colors.xml
+++ b/libs/WindowManager/Shell/res/values-night/colors.xml
@@ -15,6 +15,7 @@
-->
+ #ffffff@color/GM2_grey_200
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index b056483e3b2dfc855edbc0ee923b602bdcd7e1d6..75cd69e1cff0226e9c9ede9c08a3117961c779de 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -47,6 +47,10 @@
"Bovenste scherm 50%""Bovenste scherm 30%""Onderste scherm op volledig scherm"
+ "Gesplitst scherm links"
+ "Gesplitst scherm rechts"
+ "Gesplitst scherm boven"
+ "Gesplitst scherm onder""Bediening met 1 hand gebruiken""Als je wilt afsluiten, swipe je omhoog vanaf de onderkant van het scherm of tik je ergens boven de app""Bediening met 1 hand starten"
@@ -82,14 +86,25 @@
"Dubbeltik naast een app om deze opnieuw te positioneren""OK""Uitvouwen voor meer informatie."
+ "Opnieuw opstarten voor een betere weergave?"
+ "Je kunt de app opnieuw opstarten zodat deze er beter uitziet op je scherm, maar je kunt je voortgang of niet-opgeslagen wijzigingen kwijtraken"
+ "Annuleren"
+ "Opnieuw opstarten"
+ "Niet opnieuw tonen"
+ "Dubbeltik om\ndeze app te verplaatsen""Maximaliseren""Minimaliseren""Sluiten""Terug""Gebruikersnaam"
+ "App-icoon""Volledig scherm""Desktopmodus""Gesplitst scherm""Meer""Zwevend"
+ "Selecteren"
+ "Screenshot"
+ "Sluiten"
+ "Menu sluiten"
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 9188de3261277cc00fa63a07e167222ee47816e1..4ffbf34ef61ca05037c541d3a612ead97d88e7d1 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -47,6 +47,10 @@
"ଉପର ଆଡ଼କୁ 50% କରନ୍ତୁ""ଉପର ଆଡ଼କୁ 30% କରନ୍ତୁ""ତଳ ଅଂଶର ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍"
+ "ବାମପଟକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"
+ "ଡାହାଣପଟକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"
+ "ଶୀର୍ଷକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"
+ "ନିମ୍ନକୁ ସ୍ଲିଟ କରନ୍ତୁ""ଏକ-ହାତ ମୋଡ୍ ବ୍ୟବହାର କରି""ବାହାରି ଯିବା ପାଇଁ, ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ କିମ୍ବା ଆପରେ ଯେ କୌଣସି ସ୍ଥାନରେ ଟାପ୍ କରନ୍ତୁ""ଏକ-ହାତ ମୋଡ୍ ଆରମ୍ଭ କରନ୍ତୁ"
@@ -82,14 +86,25 @@
"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହାର ବାହାରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ""ବୁଝିଗଲି""ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"
+ "ଏକ ଭଲ ଭ୍ୟୁ ପାଇଁ ରିଷ୍ଟାର୍ଟ କରିବେ?"
+ "ଆପଣ ଆପକୁ ରିଷ୍ଟାର୍ଟ କରିପାରିବେ ଯାହା ଫଳରେ ଏହା ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଆହୁରି ଭଲ ଦେଖାଯିବ, କିନ୍ତୁ ଆପଣ ଆପଣଙ୍କ ପ୍ରଗତି କିମ୍ବା ସେଭ ହୋଇନଥିବା ଯେ କୌଣସି ପରିବର୍ତ୍ତନ ହରାଇପାରନ୍ତି"
+ "ବାତିଲ କରନ୍ତୁ"
+ "ରିଷ୍ଟାର୍ଟ କରନ୍ତୁ"
+ "ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"
+ "ଏହି ଆପକୁ ମୁଭ\nକରିବା ପାଇଁ ଦୁଇଥର-ଟାପ କରନ୍ତୁ""ବଡ଼ କରନ୍ତୁ""ଛୋଟ କରନ୍ତୁ""ବନ୍ଦ କରନ୍ତୁ""ପଛକୁ ଫେରନ୍ତୁ""ହେଣ୍ଡେଲ"
+ "ଆପ ଆଇକନ""ପୂର୍ଣ୍ଣସ୍କ୍ରିନ""ଡେସ୍କଟପ ମୋଡ""ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ""ଅଧିକ""ଫ୍ଲୋଟ"
+ "ଚୟନ କରନ୍ତୁ"
+ "ସ୍କ୍ରିନସଟ"
+ "ବନ୍ଦ କରନ୍ତୁ"
+ "ମେନୁ ବନ୍ଦ କରନ୍ତୁ"
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index ada79d195df2fdf02b578c924d22fb07adb5af8a..e8f5fd70d2a4a8da0d1ce8cdb14805e930be0455 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -47,6 +47,10 @@
"ਉੱਪਰ 50%""ਉੱਪਰ 30%""ਹੇਠਾਂ ਪੂਰੀ ਸਕ੍ਰੀਨ"
+ "ਖੱਬੇ ਪਾਸੇ ਵੰਡੋ"
+ "ਸੱਜੇ ਪਾਸੇ ਵੰਡੋ"
+ "ਸਿਖਰ \'ਤੇ ਵੰਡੋ"
+ "ਹੇਠਾਂ ਵੰਡੋ""ਇੱਕ ਹੱਥ ਮੋਡ ਵਰਤਣਾ""ਬਾਹਰ ਜਾਣ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ ਜਾਂ ਐਪ \'ਤੇ ਕਿਤੇ ਵੀ ਟੈਪ ਕਰੋ""ਇੱਕ ਹੱਥ ਮੋਡ ਸ਼ੁਰੂ ਕਰੋ"
@@ -82,14 +86,25 @@
"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਬਾਹਰ ਡਬਲ ਟੈਪ ਕਰੋ""ਸਮਝ ਲਿਆ""ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"
+ "ਕੀ ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਲਈ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"
+ "ਤੁਸੀਂ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰ ਸਕਦੇ ਹੋ ਤਾਂ ਜੋ ਇਹ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਬਿਹਤਰ ਦਿਸੇ, ਪਰ ਤੁਸੀਂ ਆਪਣੀ ਪ੍ਰਗਤੀ ਜਾਂ ਕਿਸੇ ਅਣਰੱਖਿਅਤ ਤਬਦੀਲੀ ਨੂੰ ਗੁਆ ਸਕਦੇ ਹੋ"
+ "ਰੱਦ ਕਰੋ"
+ "ਮੁੜ-ਸ਼ੁਰੂ ਕਰੋ"
+ "ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"
+ "ਇਸ ਐਪ ਦਾ ਟਿਕਾਣਾ ਬਦਲਣ ਲਈ\nਡਬਲ ਟੈਪ ਕਰੋ""ਵੱਡਾ ਕਰੋ""ਛੋਟਾ ਕਰੋ""ਬੰਦ ਕਰੋ""ਪਿੱਛੇ""ਹੈਂਡਲ"
+ "ਐਪ ਪ੍ਰਤੀਕ""ਪੂਰੀ-ਸਕ੍ਰੀਨ""ਡੈਸਕਟਾਪ ਮੋਡ""ਸਪਲਿਟ ਸਕ੍ਰੀਨ""ਹੋਰ""ਫ਼ਲੋਟ"
+ "ਚੁਣੋ"
+ "ਸਕ੍ਰੀਨਸ਼ਾਟ"
+ "ਬੰਦ ਕਰੋ"
+ "ਮੀਨੂ ਬੰਦ ਕਰੋ"
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 2750550f73471cf62aaef5071e3b096ac8fefa5c..bcead2118327d52a15ed94cb65e1c19c2f7af08e 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -47,6 +47,10 @@
"50% górnej części ekranu""30% górnej części ekranu""Dolna część ekranu na pełnym ekranie"
+ "Podziel po lewej"
+ "Podziel po prawej"
+ "Podziel u góry"
+ "Podziel u dołu""Korzystanie z trybu jednej ręki""Aby zamknąć, przesuń palcem z dołu ekranu w górę lub kliknij dowolne miejsce nad aplikacją""Uruchom tryb jednej ręki"
@@ -82,14 +86,25 @@
"Kliknij dwukrotnie poza aplikacją, aby ją przenieść""OK""Rozwiń, aby wyświetlić więcej informacji."
+ "Uruchomić ponownie dla lepszego wyglądu?"
+ "Możesz ponownie uruchomić aplikację, aby lepiej wyglądała na ekranie, ale istnieje ryzyko, że utracisz postępy i niezapisane zmiany"
+ "Anuluj"
+ "Uruchom ponownie"
+ "Nie pokazuj ponownie"
+ "Aby przenieść aplikację,\nkliknij dwukrotnie""Maksymalizuj""Minimalizuj""Zamknij""Wstecz""Uchwyt"
+ "Ikona aplikacji""Pełny ekran""Tryb pulpitu""Podzielony ekran""Więcej""Pływające"
+ "Wybierz"
+ "Zrzut ekranu"
+ "Zamknij"
+ "Zamknij menu"
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 8edcddff14e2c8eefce53ebc733273b5a62833a3..cc1fb4ea585103a32d4d151c7815aee22953b696 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -47,6 +47,10 @@
"Parte superior a 50%""Parte superior a 30%""Parte inferior em tela cheia"
+ "Dividir para a esquerda"
+ "Dividir para a direita"
+ "Dividir para cima"
+ "Dividir para baixo""Como usar o modo para uma mão""Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app""Iniciar o modo para uma mão"
@@ -82,14 +86,25 @@
"Toque duas vezes fora de um app para reposicionar""Entendi""Abra para ver mais informações."
+ "Reiniciar para melhorar a visualização?"
+ "Você pode reiniciar o app para melhorar a visualização dele, mas talvez perca seu progresso ou mudanças não salvas"
+ "Cancelar"
+ "Reiniciar"
+ "Não mostrar novamente"
+ "Toque duas vezes para\nmover o app""Maximizar""Minimizar""Fechar""Voltar""Alça"
+ "Ícone do app""Tela cheia""Modo área de trabalho""Tela dividida""Mais""Ponto flutuante"
+ "Selecionar"
+ "Captura de tela"
+ "Fechar"
+ "Fechar menu"
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 550abb3fc3210d6214365a3eb3c97b01923f71c9..b5a0a5a0d48dd59ea02c67358a940be5918eff99 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -47,6 +47,10 @@
"50% no ecrã superior""30% no ecrã superior""Ecrã inferior inteiro"
+ "Divisão à esquerda"
+ "Divisão à direita"
+ "Divisão na parte superior"
+ "Divisão na parte inferior""Utilize o modo para uma mão""Para sair, deslize rapidamente para cima a partir da parte inferior do ecrã ou toque em qualquer ponto acima da app.""Iniciar o modo para uma mão"
@@ -82,14 +86,25 @@
"Toque duas vezes fora de uma app para a reposicionar""OK""Expandir para obter mais informações"
+ "Reiniciar para uma melhor visualização?"
+ "Pode reiniciar a app para ficar com um melhor aspeto no seu ecrã, mas pode perder o seu progresso ou eventuais alterações não guardadas"
+ "Cancelar"
+ "Reiniciar"
+ "Não mostrar de novo"
+ "Toque duas vezes\npara mover esta app""Maximizar""Minimizar""Fechar""Anterior""Indicador"
+ "Ícone da app""Ecrã inteiro""Modo de ambiente de trabalho""Ecrã dividido""Mais""Flutuar"
+ "Selecionar"
+ "Captura de ecrã"
+ "Fechar"
+ "Fechar menu"
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 8edcddff14e2c8eefce53ebc733273b5a62833a3..cc1fb4ea585103a32d4d151c7815aee22953b696 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -47,6 +47,10 @@
"Parte superior a 50%""Parte superior a 30%""Parte inferior em tela cheia"
+ "Dividir para a esquerda"
+ "Dividir para a direita"
+ "Dividir para cima"
+ "Dividir para baixo""Como usar o modo para uma mão""Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app""Iniciar o modo para uma mão"
@@ -82,14 +86,25 @@
"Toque duas vezes fora de um app para reposicionar""Entendi""Abra para ver mais informações."
+ "Reiniciar para melhorar a visualização?"
+ "Você pode reiniciar o app para melhorar a visualização dele, mas talvez perca seu progresso ou mudanças não salvas"
+ "Cancelar"
+ "Reiniciar"
+ "Não mostrar novamente"
+ "Toque duas vezes para\nmover o app""Maximizar""Minimizar""Fechar""Voltar""Alça"
+ "Ícone do app""Tela cheia""Modo área de trabalho""Tela dividida""Mais""Ponto flutuante"
+ "Selecionar"
+ "Captura de tela"
+ "Fechar"
+ "Fechar menu"
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index fb4df848669e505bf2c10c224ff05078a356a047..8bcd9c372136a1dcd92a1491a0616bc1fe59c6dd 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -47,6 +47,10 @@
"Partea de sus: 50%""Partea de sus: 30%""Partea de jos pe ecran complet"
+ "Împarte în stânga"
+ "Împarte în dreapta"
+ "Împarte în sus"
+ "Împarte în jos""Folosirea modului cu o mână""Pentru a ieși, glisează în sus din partea de jos a ecranului sau atinge oriunde deasupra ferestrei aplicației""Activează modul cu o mână"
@@ -82,14 +86,25 @@
"Atinge de două ori lângă o aplicație pentru a o repoziționa""OK""Extinde pentru mai multe informații"
+ "Repornești pentru o vizualizare mai bună?"
+ "Poți să repornești aplicația ca să arate mai bine pe ecran, dar este posibil să pierzi progresul sau modificările nesalvate"
+ "Anulează"
+ "Repornește"
+ "Nu mai afișa"
+ "Atinge de două ori\nca să muți aplicația""Maximizează""Minimizează""Închide""Înapoi""Ghidaj"
+ "Pictograma aplicației""Ecran complet""Modul desktop""Ecran împărțit""Mai multe""Flotantă"
+ "Selectează"
+ "Captură de ecran"
+ "Închide"
+ "Închide meniul"
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index f5fa1a4758863595563220a64a7b50c9c0764825..ff2c85031122540bd2aeddd16d300a7687d9a160 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -47,6 +47,10 @@
"Верхний на 50%""Верхний на 30%""Нижний во весь экран"
+ "Приложение слева"
+ "Приложение справа"
+ "Приложение сверху"
+ "Приложение снизу""Использование режима управления одной рукой""Чтобы выйти, проведите по экрану снизу вверх или коснитесь области за пределами приложения.""Запустить режим управления одной рукой"
@@ -82,14 +86,25 @@
"Чтобы переместить приложение, дважды нажмите рядом с ним.""ОК""Развернуть, чтобы узнать больше."
+ "Перезапустить приложение?"
+ "Вы можете перезапустить приложение, чтобы оно лучше смотрелось на экране. При этом ваш прогресс или несохраненные изменения могут быть утеряны."
+ "Отмена"
+ "Перезапустить"
+ "Больше не показывать"
+ "Дважды нажмите, чтобы\nпереместить приложение.""Развернуть""Свернуть""Закрыть""Назад""Маркер"
+ "Значок приложения""Полноэкранный режим""Режим компьютера""Разделить экран""Ещё""Плавающее окно"
+ "Выбрать"
+ "Скриншот"
+ "Закрыть"
+ "Закрыть меню"
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 53e52f28adcf0a178f62abedb7bf3b1d157fdae7..577b2b85ee3b3bd88652562a3bee23a3f7e085e9 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -47,6 +47,10 @@
"ඉහළම 50%""ඉහළම 30%""පහළ පූර්ණ තිරය"
+ "වම බෙදන්න"
+ "දකුණ බෙදන්න"
+ "ඉහළ බෙදන්න"
+ "පහළ බෙදන්න""තනි-අත් ප්රකාරය භාවිත කරමින්""පිටවීමට, තිරයේ පහළ සිට ඉහළට ස්වයිප් කරන්න හෝ යෙදුමට ඉහළින් ඕනෑම තැනක තට්ටු කරන්න""තනි අත් ප්රකාරය ආරම්භ කරන්න"
@@ -82,14 +86,25 @@
"යෙදුමක් නැවත ස්ථානගත කිරීමට පිටතින් දෙවරක් තට්ටු කරන්න""තේරුණා""වැඩිදුර තොරතුරු සඳහා දිග හරින්න"
+ "වඩා හොඳ දසුනක් සඳහා යළි අරඹන්න ද?"
+ "ඔබට එය ඔබේ තිරයෙහි වඩා හොඳින් පෙනෙන පරිදි යෙදුම යළි ඇරඹිය හැකි නමුත්, ඔබට ඔබේ ප්රගතිය හෝ නොසුරකින ලද වෙනස්කම් අහිමි විය හැක"
+ "අවලංගු කරන්න"
+ "යළි අරඹන්න"
+ "නැවත නොපෙන්වන්න"
+ "මෙම යෙදුම ගෙන යාමට\nදෙවරක් තට්ටු කරන්න""විහිදන්න""කුඩා කරන්න""වසන්න""ආපසු""හැඬලය"
+ "යෙදුම් නිරූපකය""පූර්ණ තිරය""ඩෙස්ක්ටොප් ප්රකාරය""බෙදුම් තිරය""තව""පාවෙන"
+ "තෝරන්න"
+ "තිර රුව"
+ "වසන්න"
+ "මෙනුව වසන්න"
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index f004fd472adfcda7bad29ec1f5136e828f7e637d..9a1002c19350d1336cd059d772b26eba5c6bd134 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -47,6 +47,10 @@
"Horná – 50 %""Horná – 30 %""Dolná – na celú obrazovku"
+ "Rozdeliť vľavo"
+ "Rozdeliť vpravo"
+ "Rozdeliť hore"
+ "Rozdeliť dole""Používanie režimu jednej ruky""Ukončíte potiahnutím z dolnej časti obrazovky nahor alebo klepnutím kdekoľvek nad aplikáciu""Spustiť režim jednej ruky"
@@ -82,14 +86,25 @@
"Dvojitým klepnutím mimo aplikácie zmeníte jej pozíciu""Dobre""Po rozbalení sa dozviete viac."
+ "Chcete ju reštartovať, aby mala lepší vzhľad?"
+ "Aplikáciu môžete reštartovať, aby mala na obrazovke lepší vzhľad, ale môžete prísť o postup a všetky neuložené zmeny."
+ "Zrušiť"
+ "Reštartovať"
+ "Už nezobrazovať"
+ "Túto aplikáciu\npresuniete dvojitým klepnutím""Maximalizovať""Minimalizovať""Zavrieť""Späť""Rukoväť"
+ "Ikona aplikácie""Celá obrazovka""Režim počítača""Rozdelená obrazovka""Viac""Plávajúce"
+ "Vybrať"
+ "Snímka obrazovky"
+ "Zavrieť"
+ "Zavrieť ponuku"
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index c4808059a0c46e952b9cd868bd0805a3274dd6ba..18ffe7be40ae4b52fb26688b4cd16297cc5f4946 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -47,6 +47,10 @@
"Zgornji 50 %""Zgornji 30 %""Spodnji v celozaslonski način"
+ "Delitev levo"
+ "Delitev desno"
+ "Delitev zgoraj"
+ "Delitev spodaj""Uporaba enoročnega načina""Za izhod povlecite z dna zaslona navzgor ali se dotaknite na poljubnem mestu nad aplikacijo""Zagon enoročnega načina"
@@ -82,14 +86,25 @@
"Dvakrat se dotaknite zunaj aplikacije, če jo želite prestaviti.""V redu""Razširitev za več informacij"
+ "Želite znova zagnati za boljši pregled?"
+ "Če znova zaženete aplikacijo, bo prikaz na zaslonu boljši, vendar lahko izgubite napredek ali neshranjene spremembe."
+ "Prekliči"
+ "Znova zaženi"
+ "Ne prikaži več"
+ "Dvakrat se dotaknite\nza premik te aplikacije""Maksimiraj""Minimiraj""Zapri""Nazaj""Ročica"
+ "Ikona aplikacije""Celozaslonsko""Namizni način""Razdeljen zaslon""Več""Lebdeče"
+ "Izberi"
+ "Posnetek zaslona"
+ "Zapri"
+ "Zapri meni"
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index b59d4db6413e7fa6b00aa14e242913abd86c81ad..baead599298072e3b36c46924a35c7c11727daa7 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -47,6 +47,10 @@
"Lart 50%""Lart 30%""Ekrani i plotë poshtë"
+ "Ndaj majtas"
+ "Ndaj djathtas"
+ "Ndaj lart"
+ "Ndaj në fund""Po përdor modalitetin e përdorimit me një dorë""Për të dalë, rrëshqit lart nga fundi i ekranit ose trokit diku mbi aplikacion""Modaliteti i përdorimit me një dorë"
@@ -82,14 +86,25 @@
"Trokit dy herë jashtë një aplikacioni për ta ripozicionuar""E kuptova""Zgjeroje për më shumë informacion."
+ "Rinis për një pamje më të mirë?"
+ "Mund të rinisësh aplikacionin në mënyrë që të duket më mirë në ekranin tënd, por mund të humbësh progresin ose çdo ndryshim të paruajtur"
+ "Anulo"
+ "Rinis"
+ "Mos e shfaq përsëri"
+ "Trokit dy herë për të\nlëvizur këtë aplikacion""Maksimizo""Minimizo""Mbyll""Pas""Emërtimi"
+ "Ikona e aplikacionit""Ekrani i plotë""Modaliteti i desktopit""Ekrani i ndarë""Më shumë""Pluskuese"
+ "Zgjidh"
+ "Pamja e ekranit"
+ "Mbyll"
+ "Mbyll menynë"
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 78d74d74ac3f2694ca8a80701e6b30eff023bc2c..517620efd32f1e807010b082e42276d124a050fa 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -47,6 +47,10 @@
"Горњи екран 50%""Горњи екран 30%""Режим целог екрана за доњи екран"
+ "Поделите лево"
+ "Поделите десно"
+ "Поделите у врху"
+ "Поделите у дну""Коришћење режима једном руком""Да бисте изашли, превуците нагоре од дна екрана или додирните било где изнад апликације""Покрените режим једном руком"
@@ -82,14 +86,25 @@
"Двапут додирните изван апликације да бисте променили њену позицију""Важи""Проширите за још информација."
+ "Желите ли да рестартујете ради бољег приказа?"
+ "Можете да рестартујете апликацију да би изгледала боље на екрану, али можете да изгубите напредак или несачуване промене"
+ "Откажи"
+ "Рестартуј"
+ "Не приказуј поново"
+ "Двапут додирните да бисте\nпреместили ову апликацију""Увећајте""Умањите""Затворите""Назад""Идентификатор"
+ "Икона апликације""Преко целог екрана""Режим за рачунаре""Подељени екран""Још""Плутајуће"
+ "Изаберите"
+ "Снимак екрана"
+ "Затворите"
+ "Затворите мени"
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index cda3040dc0560e8638ce80e3e493979d5710f856..133a57b6eebca4394d9e63300fd26882f214c2e5 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -47,6 +47,10 @@
"Övre 50 %""Övre 30 %""Helskärm på nedre skärm"
+ "Till vänster på delad skärm"
+ "Till höger på delad skärm"
+ "Upptill på delad skärm"
+ "Nedtill på delad skärm""Använda enhandsläge""Avsluta genom att svepa uppåt från skärmens nederkant eller trycka ovanför appen""Starta enhandsläge"
@@ -82,14 +86,25 @@
"Tryck snabbt två gånger utanför en app för att flytta den""OK""Utöka för mer information."
+ "Vill du starta om för en bättre vy?"
+ "Du kan starta om appen så den passar bättre på skärmen men du kan förlora dina framsteg och eventuella osparade ändringar."
+ "Avbryt"
+ "Starta om"
+ "Visa inte igen"
+ "Tryck snabbt två gånger\nför att flytta denna app""Utöka""Minimera""Stäng""Tillbaka""Handtag"
+ "Appikon""Helskärm""Datorläge""Delad skärm""Mer""Svävande"
+ "Välj"
+ "Skärmbild"
+ "Stäng"
+ "Stäng menyn"
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index fee34eb28725b36055526c1af200743547e5a8d4..03cc92d1b96a5977b59651e3578cd80c6bb2cec2 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -47,6 +47,10 @@
"Juu 50%""Juu 30%""Skrini nzima ya chini"
+ "Gawanya sehemu ya kushoto"
+ "Gawanya sehemu ya kulia"
+ "Gawanya sehemu ya juu"
+ "Gawanya sehemu ya chini""Kutumia hali ya kutumia kwa mkono mmoja""Ili ufunge, telezesha kidole juu kutoka sehemu ya chini ya skrini au uguse mahali popote juu ya programu""Anzisha hali ya kutumia kwa mkono mmoja"
@@ -82,14 +86,25 @@
"Gusa mara mbili nje ya programu ili uihamishe""Nimeelewa""Panua ili upate maelezo zaidi."
+ "Ungependa kuzima kisha uwashe ili upate mwonekano bora zaidi?"
+ "Unaweza kuzima kisha uwashe programu yako ili ifanye kazi vizuri zaidi kwenye skrini yako, lakini unaweza kupoteza maendeleo yako au mabadiliko yoyote ambayo hayajahifadhiwa"
+ "Ghairi"
+ "Zima kisha uwashe"
+ "Usionyeshe tena"
+ "Gusa mara mbili ili\nusogeze programu hii""Panua""Punguza""Funga""Rudi nyuma""Ncha"
+ "Aikoni ya Programu""Skrini nzima""Hali ya Kompyuta ya mezani""Gawa Skrini""Zaidi""Inayoelea"
+ "Chagua"
+ "Picha ya skrini"
+ "Funga"
+ "Funga Menyu"
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index cddff3cf542ee7dd7607dde3b763259b56195b38..afb0adfac36b6ed5a581a824ab10b4186a859f6b 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -47,6 +47,10 @@
"மேலே 50%""மேலே 30%""கீழ்ப்புறம் முழுத் திரை"
+ "இடதுபுறமாகப் பிரிக்கும்"
+ "வலதுபுறமாகப் பிரிக்கும்"
+ "மேற்புறமாகப் பிரிக்கும்"
+ "கீழ்புறமாகப் பிரிக்கும்""ஒற்றைக் கைப் பயன்முறையைப் பயன்படுத்துதல்""வெளியேற, திரையின் கீழிருந்து மேல்நோக்கி ஸ்வைப் செய்யவும் அல்லது ஆப்ஸுக்கு மேலே ஏதேனும் ஓர் இடத்தில் தட்டவும்""ஒற்றைக் கைப் பயன்முறையைத் தொடங்கும்"
@@ -82,14 +86,25 @@
"ஆப்ஸை இடம் மாற்ற அதன் வெளியில் இருமுறை தட்டலாம்""சரி""கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."
+ "சரியான விதத்தில் காட்டப்பட மீண்டும் தொடங்கவா?"
+ "ஆப்ஸை மீண்டும் தொடங்குவதன் மூலம் திரையில் அது சரியான விதத்தில் தோற்றமளிக்கும். ஆனால் செயல்நிலையையோ சேமிக்கப்படாமல் இருக்கும் மாற்றங்களையோ நீங்கள் இழக்கக்கூடும்."
+ "ரத்துசெய்"
+ "மீண்டும் தொடங்கு"
+ "மீண்டும் காட்டாதே"
+ "இந்த ஆப்ஸை நகர்த்த\nஇருமுறை தட்டவும்""பெரிதாக்கும்""சிறிதாக்கும்""மூடும்""பின்செல்லும்""ஹேண்டில்"
+ "ஆப்ஸ் ஐகான்""முழுத்திரை""டெஸ்க்டாப் பயன்முறை""திரையைப் பிரிக்கும்""கூடுதல் விருப்பத்தேர்வுகள்""மிதக்கும் சாளரம்"
+ "தேர்ந்தெடுக்கும்"
+ "ஸ்கிரீன்ஷாட்"
+ "மூடும்"
+ "மெனுவை மூடும்"
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index f0c8be5c595748c07436958ecacbf163dcf76a10..a09cacb036db6e747a94b10dc373e1ecf1d1a11f 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -47,6 +47,10 @@
"ఎగువ 50%""ఎగువ 30%""దిగువ ఫుల్-స్క్రీన్"
+ "ఎడమ వైపున్న భాగంలో విభజించండి"
+ "కుడి వైపున్న భాగంలో విభజించండి"
+ "ఎగువ భాగంలో విభజించండి"
+ "దిగువ భాగంలో విభజించండి""వన్-హ్యాండెడ్ మోడ్ను ఉపయోగించడం""నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి""వన్-హ్యాండెడ్ మోడ్ను ప్రారంభిస్తుంది"
@@ -82,14 +86,25 @@
"యాప్ స్థానాన్ని మార్చడానికి దాని వెలుపల డబుల్-ట్యాప్ చేయండి""అర్థమైంది""మరింత సమాచారం కోసం విస్తరించండి."
+ "మెరుగైన వీక్షణ కోసం రీస్టార్ట్ చేయాలా?"
+ "మీరు యాప్ని రీస్టార్ట్ చేయవచ్చు, తద్వారా ఇది మీ స్క్రీన్పై మెరుగ్గా కనిపిస్తుంది, కానీ మీరు మీ ప్రోగ్రెస్ను గానీ లేదా సేవ్ చేయని ఏవైనా మార్పులను గానీ కోల్పోవచ్చు"
+ "రద్దు చేయండి"
+ "రీస్టార్ట్ చేయండి"
+ "మళ్లీ చూపవద్దు"
+ "ఈ యాప్ను తరలించడానికి\nడబుల్-ట్యాప్ చేయండి""గరిష్టీకరించండి""కుదించండి""మూసివేయండి""వెనుకకు""హ్యాండిల్"
+ "యాప్ చిహ్నం""ఫుల్-స్క్రీన్""డెస్క్టాప్ మోడ్""స్ప్లిట్ స్క్రీన్""మరిన్ని""ఫ్లోట్"
+ "ఎంచుకోండి"
+ "స్క్రీన్షాట్"
+ "మూసివేయండి"
+ "మెనూను మూసివేయండి"
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 2437e03777804b796cac92b6e4bfc56918b58874..1569e49a039bbb7079cb4f75d7287a49395298b9 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -47,6 +47,10 @@
"ด้านบน 50%""ด้านบน 30%""เต็มหน้าจอด้านล่าง"
+ "แยกไปทางซ้าย"
+ "แยกไปทางขวา"
+ "แยกไปด้านบน"
+ "แยกไปด้านล่าง""การใช้โหมดมือเดียว""หากต้องการออก ให้เลื่อนขึ้นจากด้านล่างของหน้าจอหรือแตะที่ใดก็ได้เหนือแอป""เริ่มโหมดมือเดียว"
@@ -82,14 +86,25 @@
"แตะสองครั้งด้านนอกแอปเพื่อเปลี่ยนตำแหน่ง""รับทราบ""ขยายเพื่อดูข้อมูลเพิ่มเติม"
+ "รีสตาร์ทเพื่อรับมุมมองที่ดียิ่งขึ้นใช่ไหม"
+ "คุณรีสตาร์ทแอปเพื่อรับมุมมองที่ดียิ่งขึ้นบนหน้าจอได้ แต่ความคืบหน้าและการเปลี่ยนแปลงใดๆ ที่ไม่ได้บันทึกอาจหายไป"
+ "ยกเลิก"
+ "รีสตาร์ท"
+ "ไม่ต้องแสดงข้อความนี้อีก"
+ "แตะสองครั้ง\nเพื่อย้ายแอปนี้""ขยายใหญ่สุด""ย่อ""ปิด""กลับ""แฮนเดิล"
+ "ไอคอนแอป""เต็มหน้าจอ""โหมดเดสก์ท็อป""แยกหน้าจอ""เพิ่มเติม""ล่องลอย"
+ "เลือก"
+ "ภาพหน้าจอ"
+ "ปิด"
+ "ปิดเมนู"
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 86ef75718b774926919ef367fb26dbb70ab6ee9d..99a8e11cb31f6fb3feec93aeed45940bb8bb5051 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -47,6 +47,10 @@
"Gawing 50% ang nasa itaas""Gawing 30% ang nasa itaas""I-full screen ang nasa ibaba"
+ "Hatiin sa kaliwa"
+ "Hatiin sa kanan"
+ "Hatiin sa itaas"
+ "Hatiin sa ilalim""Paggamit ng one-hand mode""Para lumabas, mag-swipe pataas mula sa ibaba ng screen o mag-tap kahit saan sa itaas ng app""Simulan ang one-hand mode"
@@ -82,14 +86,25 @@
"Mag-double tap sa labas ng app para baguhin ang posisyon nito""OK""I-expand para sa higit pang impormasyon."
+ "I-restart para sa mas magandang hitsura?"
+ "Puwede mong i-restart ang app para maging mas maganda ang itsura nito sa iyong screen, pero posibleng mawala ang pag-usad mo o anumang hindi na-save na pagbabago"
+ "Kanselahin"
+ "I-restart"
+ "Huwag nang ipakita ulit"
+ "I-double tap para\nilipat ang app na ito""I-maximize""I-minimize""Isara""Bumalik""Handle"
+ "Icon ng App""Fullscreen""Desktop Mode""Split Screen""Higit pa""Float"
+ "Piliin"
+ "Screenshot"
+ "Isara"
+ "Isara ang Menu"
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index c4060cc795abdfbe183de974ae79db189f1f4d5d..8ac29391ea1a8d329a77c017621e3daaac9f430f 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -47,6 +47,10 @@
"Üstte %50""Üstte %30""Altta tam ekran"
+ "Sol tarafta böl"
+ "Sağ tarafta böl"
+ "Üst tarafta böl"
+ "Alt tarafta böl""Tek el modunu kullanma""Çıkmak için ekranın alt kısmından yukarı kaydırın veya uygulamanın üzerinde herhangi bir yere dokunun""Tek el modunu başlat"
@@ -82,14 +86,25 @@
"Yeniden konumlandırmak için uygulamanın dışına iki kez dokunun""Anladım""Daha fazla bilgi için genişletin."
+ "Daha iyi bir görünüm için yeniden başlatılsın mı?"
+ "Ekranınızda daha iyi görünmesi için uygulamayı yeniden başlatabilirsiniz, ancak ilerlemenizi ve kaydedilmemiş değişikliklerinizi kaybedebilirsiniz"
+ "İptal"
+ "Yeniden başlat"
+ "Bir daha gösterme"
+ "Bu uygulamayı taşımak için\niki kez dokunun""Ekranı Kapla""Küçült""Kapat""Geri""Herkese açık kullanıcı adı"
+ "Uygulama Simgesi""Tam Ekran""Masaüstü Modu""Bölünmüş Ekran""Daha Fazla""Havada Süzülen"
+ "Seç"
+ "Ekran görüntüsü"
+ "Kapat"
+ "Menüyü kapat"
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 166041d6d6d88f5b0e9a44776cdac4abbf4422a0..c578283d21b89aae87270fe62eb25dc0d2cdccae 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -47,6 +47,10 @@
"Верхнє вікно на 50%""Верхнє вікно на 30%""Нижнє вікно на весь екран"
+ "Розділити зліва"
+ "Розділити справа"
+ "Розділити вгорі"
+ "Розділити внизу""Як користуватися режимом керування однією рукою""Щоб вийти, проведіть пальцем по екрану знизу вгору або торкніться екрана над додатком""Увімкнути режим керування однією рукою"
@@ -82,14 +86,25 @@
"Щоб перемістити додаток, двічі торкніться області поза ним""ОK""Розгорніть, щоб дізнатися більше."
+ "Перезапустити для зручнішого перегляду?"
+ "Ви можете перезапустити додаток, щоб покращити його вигляд на екрані, але ваші досягнення або незбережені зміни може бути втрачено"
+ "Скасувати"
+ "Перезапустити"
+ "Більше не показувати"
+ "Двічі торкніться, щоб\nперемістити цей додаток""Збільшити""Згорнути""Закрити""Назад""Маркер"
+ "Значок додатка""На весь екран""Режим комп’ютера""Розділити екран""Більше""Плаваюче вікно"
+ "Вибрати"
+ "Знімок екрана"
+ "Закрити"
+ "Закрити меню"
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index b2b82abec5b49ca0edc969e7b56b8459dee444b5..c40f34f4174e2a05a0ebae9fcc4cf1ba9ff58b14 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -47,6 +47,10 @@
"اوپر %50""اوپر %30""نچلی فل اسکرین"
+ "دائیں طرف تقسیم کریں"
+ "بائیں طرف تقسیم کریں"
+ "اوپر کی طرف تقسیم کریں"
+ "نیچے کی طرف تقسیم کریں""ایک ہاتھ کی وضع کا استعمال کرنا""باہر نکلنے کیلئے، اسکرین کے نیچے سے اوپر کی طرف سوائپ کریں یا ایپ کے اوپر کہیں بھی تھپتھپائیں""ایک ہاتھ کی وضع شروع کریں"
@@ -82,14 +86,25 @@
"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس ایپ کے باہر دو بار تھپتھپائیں""سمجھ آ گئی""مزید معلومات کے لیے پھیلائیں۔"
+ "بہتر منظر کے لیے ری سٹارٹ کریں؟"
+ "آپ ایپ کو ری سٹارٹ کر سکتے ہیں تاکہ یہ آپ کی اسکرین پر بہتر نظر آئے، تاہم آپ اپنی پیشرفت سے یا کسی غیر محفوظ شدہ تبدیلیوں سے محروم ہو سکتے ہیں"
+ "منسوخ کریں"
+ "ری اسٹارٹ کریں"
+ "دوبارہ نہ دکھائیں"
+ "اس ایپ کو منتقل کرنے کیلئے\nدو بار تھپتھپائیں""بڑا کریں""چھوٹا کریں""بند کریں""پیچھے""ہینڈل"
+ "ایپ کا آئیکن""مکمل اسکرین""ڈیسک ٹاپ موڈ""اسپلٹ اسکرین""مزید""فلوٹ"
+ "منتخب کریں"
+ "اسکرین شاٹ"
+ "بند کریں"
+ "مینیو بند کریں"
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 8f173d5d1e4bc5a2ee36c33e6bf3d47ea72b28c5..7f97ff2fbc93c58db2b1b0ce40fc40854d0682d3 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -47,6 +47,10 @@
"Tepada 50%""Tepada 30%""Pastda to‘liq ekran"
+ "Chapga ajratish"
+ "Oʻngga ajratish"
+ "Yuqoriga ajratish"
+ "Pastga ajratish""Ixcham rejimdan foydalanish""Chiqish uchun ekran pastidan tepaga suring yoki ilovaning tepasidagi istalgan joyga bosing.""Ixcham rejimni ishga tushirish"
@@ -82,14 +86,25 @@
"Qayta joylash uchun ilova tashqarisiga ikki marta bosing""OK""Batafsil axborot olish uchun kengaytiring."
+ "Yaxshi koʻrinishi uchun qayta ishga tushirilsinmi?"
+ "Ilovani ekranda yaxshiroq koʻrinishi uchun qayta ishga tushirishingiz mumkin. Bunda jarayonlar yoki saqlanmagan oʻzgarishlar yoʻqolishi mumkin."
+ "Bekor qilish"
+ "Qaytadan"
+ "Boshqa chiqmasin"
+ "Bu ilovani siljitish uchun\nikki marta bosing""Yoyish""Kichraytirish""Yopish""Orqaga""Identifikator"
+ "Ilova belgisi""Butun ekran""Desktop rejimi""Ekranni ikkiga ajratish""Yana""Pufakli"
+ "Tanlash"
+ "Skrinshot"
+ "Yopish"
+ "Menyuni yopish"
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 527afabfa8f28214e32d5b2b7bd7bbf771f1278c..4956c230d76319ead1dc4164670aaceb573395ed 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -47,6 +47,10 @@
"Trên 50%""Trên 30%""Toàn màn hình phía dưới"
+ "Chia đôi màn hình về bên trái"
+ "Chia đôi màn hình về bên phải"
+ "Chia đôi màn hình lên trên cùng"
+ "Chia đôi màn hình xuống dưới cùng""Cách dùng chế độ một tay""Để thoát, hãy vuốt lên từ cuối màn hình hoặc nhấn vào vị trí bất kỳ phía trên ứng dụng""Bắt đầu chế độ một tay"
@@ -82,14 +86,25 @@
"Nhấn đúp bên ngoài ứng dụng để đặt lại vị trí""OK""Mở rộng để xem thêm thông tin."
+ "Khởi động lại để ứng dụng trông vừa vặn hơn?"
+ "Bạn có thể khởi động lại ứng dụng để ứng dụng nhìn đẹp hơn trên màn hình. Tuy nhiên, nếu làm vậy, bạn có thể mất tiến trình hoặc mọi thay đổi chưa lưu"
+ "Huỷ"
+ "Khởi động lại"
+ "Không hiện lại"
+ "Nhấn đúp để\ndi chuyển ứng dụng này""Phóng to""Thu nhỏ""Đóng""Quay lại""Xử lý"
+ "Biểu tượng ứng dụng""Toàn màn hình""Chế độ máy tính""Chia đôi màn hình""Tuỳ chọn khác""Nổi"
+ "Chọn"
+ "Ảnh chụp màn hình"
+ "Đóng"
+ "Đóng trình đơn"
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 87f2973aa6182092b11737a255e2c9016f50b9c1..3acbeebe8d7907848d23ff4ad12dfd9a107107e8 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -47,6 +47,10 @@
"顶部 50%""顶部 30%""底部全屏"
+ "左分屏"
+ "右分屏"
+ "上分屏"
+ "下分屏""使用单手模式""如需退出,请从屏幕底部向上滑动,或点按应用上方的任意位置""启动单手模式"
@@ -82,14 +86,25 @@
"在某个应用外连续点按两次,即可调整它的位置""知道了""展开即可了解详情。"
+ "重启以改进外观?"
+ "您可以重启应用,使其在屏幕上的显示效果更好,但您可能会丢失进度或任何未保存的更改"
+ "取消"
+ "重启"
+ "不再显示"
+ "点按两次\n即可移动此应用""最大化""最小化""关闭""返回""处理"
+ "应用图标""全屏""桌面模式""分屏""更多""悬浮"
+ "选择"
+ "屏幕截图"
+ "关闭"
+ "关闭菜单"
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index cec2399f679b2496d59ed699c57935fb46e0d2aa..e67dde03a283823b966f90d71248195202bee72a 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -47,6 +47,10 @@
"頂部 50%""頂部 30%""底部全螢幕"
+ "分割左側區域"
+ "分割右側區域"
+ "分割上方區域"
+ "分割下方區域""使用單手模式""如要退出,請從螢幕底部向上滑動,或輕按應用程式上方的任何位置""開始單手模式"
@@ -82,14 +86,25 @@
"在應用程式外輕按兩下即可調整位置""知道了""展開即可查看詳情。"
+ "要重新啟動以改善檢視畫面嗎?"
+ "你可重新啟動應用程式,讓系統更新檢視畫面;但系統可能不會儲存目前進度及你作出的任何變更"
+ "取消"
+ "重新啟動"
+ "不要再顯示"
+ "輕按兩下\n即可移動此應用程式""最大化""最小化""關閉""返去""控點"
+ "應用程式圖示""全螢幕""桌面模式""分割螢幕""更多""浮動"
+ "選取"
+ "螢幕截圖"
+ "關閉"
+ "關閉選單"
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 1438e52ccb4a3b3e7f693a098d2d3498b0bdf794..a9926b3f895fa9b8c7f2c3fd0f14924fd1324e2b 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -47,6 +47,10 @@
"以 50% 的螢幕空間顯示頂端畫面""以 30% 的螢幕空間顯示頂端畫面""以全螢幕顯示底部畫面"
+ "分割左側區域"
+ "分割右側區域"
+ "分割上方區域"
+ "分割下方區域""使用單手模式""如要退出,請從螢幕底部向上滑動,或輕觸應用程式上方的任何位置""啟動單手模式"
@@ -82,14 +86,25 @@
"在應用程式外輕觸兩下即可調整位置""我知道了""展開即可查看詳細資訊。"
+ "要重新啟動改善檢視畫面嗎?"
+ "你可以重新啟動應用程式,讓系統更新檢視畫面。不過,系統可能不會儲存目前進度及你所做的任何變更"
+ "取消"
+ "重新啟動"
+ "不要再顯示"
+ "輕觸兩下即可\n移動這個應用程式""最大化""最小化""關閉""返回""控點"
+ "應用程式圖示""全螢幕""電腦模式""分割畫面""更多""浮動"
+ "選取"
+ "螢幕截圖"
+ "關閉"
+ "關閉選單"
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index e9238dc0833a5a1a8fd4a16975228922b93f81f7..4169122c7f7c51e2181c3de01468497bc7e49731 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -47,6 +47,10 @@
"Okuphezulu okungu-50%""Okuphezulu okungu-30%""Ngaphansi kwesikrini esigcwele"
+ "Hlukanisa ngakwesobunxele"
+ "Hlukanisa ngakwesokudla"
+ "Hlukanisa phezulu"
+ "Hlukanisa phansi""Ukusebenzisa imodi yesandla esisodwa""Ukuze uphume, swayipha ngaphezulu kusuka ngezansi kwesikrini noma thepha noma kuphi ngenhla kohlelo lokusebenza""Qalisa imodi yesandla esisodwa"
@@ -82,14 +86,25 @@
"Thepha kabili ngaphandle kwe-app ukuze uyimise kabusha""Ngiyezwa""Nweba ukuze uthole ulwazi olwengeziwe"
+ "Qala kabusha ukuze uthole ukubuka okungcono?"
+ "Ungakwazi ukuqala kabusha i-app ukuze ibukeke kangcono esikrinini sakho, kodwa ungase ulahlekelwe ukuqhubeka kwakho nanoma yiziphi izinguquko ezingalondoloziwe"
+ "Khansela"
+ "Qala kabusha"
+ "Ungabonisi futhi"
+ "Thepha kabili ukuze\nuhambise le-app""Khulisa""Nciphisa""Vala""Emuva""Isibambo"
+ "Isithonjana Se-app""Isikrini esigcwele""Imodi Yedeskithophu""Hlukanisa isikrini""Okwengeziwe""Iflowuthi"
+ "Khetha"
+ "Isithombe-skrini"
+ "Vala"
+ "Vala Imenyu"
diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml
index 2aad4c1c18051e20da62929dc54f97b28da8f82b..fbb5caa508de82751d7d17ea91195087d763859b 100644
--- a/libs/WindowManager/Shell/res/values/attrs.xml
+++ b/libs/WindowManager/Shell/res/values/attrs.xml
@@ -1,5 +1,5 @@
- #ffffff
+ #000000
+ @color/taskbar_background#59000000#60000000#00000000
@@ -41,6 +42,9 @@
@android:color/system_accent1_100@android:color/system_neutral2_200
+
+ @android:color/system_neutral1_900
+
#E8EAED#5F6368
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 774f6c6379b2c3fbe2f312350bb4f7e7c2c4ac64..76eb0945d9903c5967e60df024293f911f8614fc 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -105,6 +105,10 @@
1.777778
+
+ 0.5625
+
0x55
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 3ee20ea95ee5425dfe75f954e4b9270b1e6949fc..9fd16461fa01d3c207964c48761171feffa8cc48 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -228,7 +228,7 @@
16dp
- 16dp
+ 24dp28dp
@@ -273,6 +273,66 @@
24dp
+
+ 12dp
+
+
+ 16dp
+
+
+ 8dp
+
+
+ 6dp
+
+
+ 24dp
+
+
+ 28dp
+
+
+ 348dp
+
+
+ 32dp
+
+
+ 32dp
+
+
+ 40dp
+
+
+ 32dp
+
+
+ 24dp
+
+
+ 82dp
+
+
+ 36dp
+
+
+ 18dp
+
+
+ 6dp
+
+
+ 16dp
+
+
+ 8dp
+
+
+ 16dp
+
+
+ 24dp
+
200dp
@@ -298,30 +358,6 @@
-->
48dp
-
- 32dp
-
-
- 24dp
-
-
- 5dp
-
-
- 32dp
-
-
- 8dp
-
-
- 8dp
-
-
- 150dp
-
-
- 120dp
-
20dp
@@ -331,11 +367,19 @@
42dp
-
- 216dp
+
+ 256dp
+
+ 250dp30dp44dp
+
+ 4dp
+
+
+ 20dp
+
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 25eddf834f3da54d3ccf2695fec5702cca27d9a7..a201536da25a6f05e34beeb9e1c16ac4b723ad39 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -100,6 +100,15 @@
Bottom full screen
+
+ Split left
+
+ Split right
+
+ Split top
+
+ Split bottom
+
Using one-handed mode
@@ -188,6 +197,35 @@
Expand for more information.
+
+ Restart for a better view?
+
+
+ You can restart the app so it looks better on
+ your screen, but you may lose your progress or any unsaved changes
+
+
+
+ Cancel
+
+
+ Restart
+
+
+ Don\u2019t show again
+
+
+ Double-tap to\nmove this app
+
Maximize
@@ -199,6 +237,8 @@
BackHandle
+
+ App IconFullscreen
@@ -209,4 +249,12 @@
MoreFloat
+
+ Select
+
+ Screenshot
+
+ Close
+
+ Close Menu
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index a8597210d72e7108b319f6deb90d6c4f9c5d6631..6e1185559a50b3ab7affbe8b0f15b9dd70c8929e 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -37,6 +37,20 @@
4dp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
index d2760022a01538af7b6983b69c4e6b0cf6d9d617..88525aabe53b3f7e1f2e8c2cb01ddb0d5f977fd8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -84,6 +84,15 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio
String[] groups = Arrays.copyOfRange(args, 1, args.length);
return mShellProtoLog.stopTextLogging(groups, pw) == 0;
}
+ case "save-for-bugreport": {
+ if (!mShellProtoLog.isProtoEnabled()) {
+ pw.println("Logging to proto is not enabled for WMShell.");
+ return false;
+ }
+ mShellProtoLog.stopProtoLog(pw, true /* writeToFile */);
+ mShellProtoLog.startProtoLog(pw);
+ return true;
+ }
default: {
pw.println("Invalid command: " + args[0]);
printShellCommandHelp(pw, "");
@@ -108,5 +117,7 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio
pw.println(prefix + " Enable logcat logging for given groups.");
pw.println(prefix + "disable-text [group...]");
pw.println(prefix + " Disable logcat logging for given groups.");
+ pw.println(prefix + "save-for-bugreport");
+ pw.println(prefix + " Flush proto logging to file, only if it's enabled.");
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index e58e785850faca020a7ac6d1859333be9a8c44fb..97a9fede22d54cc466b186eeb57dbf2b1035645d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -256,12 +256,30 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
}
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ * @param displayId The display to create the root task on.
+ * @param windowingMode Windowing mode to put the root task in.
+ * @param listener The listener to get the created task callback.
+ */
public void createRootTask(int displayId, int windowingMode, TaskListener listener) {
- ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s",
+ createRootTask(displayId, windowingMode, listener, false /* removeWithTaskOrganizer */);
+ }
+
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ * @param displayId The display to create the root task on.
+ * @param windowingMode Windowing mode to put the root task in.
+ * @param listener The listener to get the created task callback.
+ * @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed.
+ */
+ public void createRootTask(int displayId, int windowingMode, TaskListener listener,
+ boolean removeWithTaskOrganizer) {
+ ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s" ,
displayId, windowingMode, listener.toString());
final IBinder cookie = new Binder();
setPendingLaunchCookieListener(cookie, listener);
- super.createRootTask(displayId, windowingMode, cookie);
+ super.createRootTask(displayId, windowingMode, cookie, removeWithTaskOrganizer);
}
/**
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 cbcd9498fe55e758c3bf4e5556f23a4615869143..aaeef196b6182f5a9b3e23017179a75b3f7d540a 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
@@ -51,6 +51,7 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackAnimationAdaptor;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationRunner;
import android.window.IBackNaviAnimationController;
@@ -81,7 +82,7 @@ public class BackAnimationController implements RemoteCallable= 0
? PROGRESS_THRESHOLD : mProgressThreshold;
progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
@@ -109,8 +110,8 @@ class TouchTracker {
return createProgressEvent(progress);
}
- BackEvent createProgressEvent(float progress) {
- return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+ BackMotionEvent createProgressEvent(float progress) {
+ return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
}
public void setProgressThreshold(float progressThreshold) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 09dc68a4cceaa3402be027373d973709fbaabc64..e24c2286013de2ab154355551f12ca1e2b5b2d33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -21,6 +21,7 @@ import static android.os.AsyncTask.Status.FINISHED;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import android.annotation.DimenRes;
+import android.annotation.Hide;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
@@ -125,7 +126,7 @@ public class Bubble implements BubbleViewProvider {
private Icon mIcon;
private boolean mIsBubble;
private boolean mIsTextChanged;
- private boolean mIsClearable;
+ private boolean mIsDismissable;
private boolean mShouldSuppressNotificationDot;
private boolean mShouldSuppressNotificationList;
private boolean mShouldSuppressPeek;
@@ -180,7 +181,7 @@ public class Bubble implements BubbleViewProvider {
@VisibleForTesting(visibility = PRIVATE)
public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
final int desiredHeight, final int desiredHeightResId, @Nullable final String title,
- int taskId, @Nullable final String locus, Executor mainExecutor,
+ int taskId, @Nullable final String locus, boolean isDismissable, Executor mainExecutor,
final Bubbles.BubbleMetadataFlagListener listener) {
Objects.requireNonNull(key);
Objects.requireNonNull(shortcutInfo);
@@ -189,6 +190,7 @@ public class Bubble implements BubbleViewProvider {
mKey = key;
mGroupKey = null;
mLocusId = locus != null ? new LocusId(locus) : null;
+ mIsDismissable = isDismissable;
mFlags = 0;
mUser = shortcutInfo.getUserHandle();
mPackageName = shortcutInfo.getPackage();
@@ -245,6 +247,11 @@ public class Bubble implements BubbleViewProvider {
return mKey;
}
+ @Hide
+ public boolean isDismissable() {
+ return mIsDismissable;
+ }
+
/**
* @see StatusBarNotification#getGroupKey()
* @return the group key for this bubble, if one exists.
@@ -526,7 +533,7 @@ public class Bubble implements BubbleViewProvider {
mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
}
- mIsClearable = entry.isClearable();
+ mIsDismissable = entry.isDismissable();
mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
mShouldSuppressPeek = entry.shouldSuppressPeek();
@@ -605,7 +612,7 @@ public class Bubble implements BubbleViewProvider {
* Whether this notification should be shown in the shade.
*/
boolean showInShade() {
- return !shouldSuppressNotification() || !mIsClearable;
+ return !shouldSuppressNotification() || !mIsDismissable;
}
/**
@@ -870,7 +877,7 @@ public class Bubble implements BubbleViewProvider {
pw.print(" desiredHeight: "); pw.println(getDesiredHeightString());
pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification());
pw.print(" autoExpand: "); pw.println(shouldAutoExpand());
- pw.print(" isClearable: "); pw.println(mIsClearable);
+ pw.print(" isDismissable: "); pw.println(mIsDismissable);
pw.println(" bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null));
if (mExpandedView != null) {
mExpandedView.dump(pw);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index dd8afff0df2cf32e27803316fa8617ce5a920248..541c0f04b9b9d9a7944e0bdb8d00821d32159b2c 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
@@ -61,6 +61,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.NotificationListenerService;
@@ -125,6 +126,39 @@ public class BubbleController implements ConfigurationChangeListener {
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+ // TODO(b/256873975) Should use proper flag when available to shell/launcher
+ /**
+ * Whether bubbles are showing in the bubble bar from launcher. This is only available
+ * on large screens and {@link BubbleController#isShowingAsBubbleBar()} should be used
+ * to check all conditions that indicate if the bubble bar is in use.
+ */
+ private static final boolean BUBBLE_BAR_ENABLED =
+ SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+
+
+ /**
+ * Common interface to send updates to bubble views.
+ */
+ public interface BubbleViewCallback {
+ /** Called when the provided bubble should be removed. */
+ void removeBubble(Bubble removedBubble);
+ /** Called when the provided bubble should be added. */
+ void addBubble(Bubble addedBubble);
+ /** Called when the provided bubble should be updated. */
+ void updateBubble(Bubble updatedBubble);
+ /** Called when the provided bubble should be selected. */
+ void selectionChanged(BubbleViewProvider selectedBubble);
+ /** Called when the provided bubble's suppression state has changed. */
+ void suppressionChanged(Bubble bubble, boolean isSuppressed);
+ /** Called when the expansion state of bubbles has changed. */
+ void expansionChanged(boolean isExpanded);
+ /**
+ * Called when the order of the bubble list has changed. Depending on the expanded state
+ * the pointer might need to be updated.
+ */
+ void bubbleOrderChanged(List bubbleOrder, boolean updatePointer);
+ }
+
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
@@ -147,12 +181,8 @@ public class BubbleController implements ConfigurationChangeListener {
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
-
private final ShellExecutor mBackgroundExecutor;
- // Whether or not we should show bubbles pinned at the bottom of the screen.
- private boolean mIsBubbleBarEnabled;
-
private BubbleLogger mLogger;
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
@@ -533,10 +563,10 @@ public class BubbleController implements ConfigurationChangeListener {
mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
}
- // TODO(b/256873975): Should pass this into the constructor once flags are available to shell.
- /** Sets whether the bubble bar is enabled (i.e. bubbles pinned to bottom on large screens). */
- public void setBubbleBarEnabled(boolean enabled) {
- mIsBubbleBarEnabled = enabled;
+ /** Whether bubbles are showing in the bubble bar. */
+ public boolean isShowingAsBubbleBar() {
+ // TODO(b/269670598): should also check that we're in gesture nav
+ return BUBBLE_BAR_ENABLED && mBubblePositioner.isLargeScreen();
}
/** Whether this userId belongs to the current user. */
@@ -605,12 +635,6 @@ public class BubbleController implements ConfigurationChangeListener {
mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
- if (mIsBubbleBarEnabled && mBubblePositioner.isLargeScreen()) {
- mBubblePositioner.setUsePinnedLocation(true);
- } else {
- mBubblePositioner.setUsePinnedLocation(false);
- }
-
addToWindowManagerMaybe();
}
@@ -973,21 +997,59 @@ public class BubbleController implements ConfigurationChangeListener {
}
/**
- * Adds and expands bubble for a specific intent. These bubbles are not backed by a n
- * otification and remain until the user dismisses the bubble or bubble stack. Only one intent
- * bubble is supported at a time.
+ * This method has different behavior depending on:
+ * - if an app bubble exists
+ * - if an app bubble is expanded
+ *
+ * If no app bubble exists, this will add and expand a bubble with the provided intent. The
+ * intent must be explicit (i.e. include a package name or fully qualified component class name)
+ * and the activity for it should be resizable.
+ *
+ * If an app bubble exists, this will toggle the visibility of it, i.e. if the app bubble is
+ * expanded, calling this method will collapse it. If the app bubble is not expanded, calling
+ * this method will expand it.
+ *
+ * These bubbles are not backed by a notification and remain until the user dismisses
+ * the bubble or bubble stack.
+ *
+ * Some notes:
+ * - Only one app bubble is supported at a time
+ * - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
*/
- public void showAppBubble(Intent intent) {
- if (intent == null || intent.getPackage() == null) return;
+ public void showOrHideAppBubble(Intent intent) {
+ if (intent == null || intent.getPackage() == null) {
+ Log.w(TAG, "App bubble failed to show, invalid intent: " + intent
+ + ((intent != null) ? " with package: " + intent.getPackage() : " "));
+ return;
+ }
PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
- Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
- b.setShouldAutoExpand(true);
- inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
+ Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
+ if (existingAppBubble != null) {
+ BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
+ if (isStackExpanded()) {
+ if (selectedBubble != null && KEY_APP_BUBBLE.equals(selectedBubble.getKey())) {
+ // App bubble is expanded, lets collapse
+ collapseStack();
+ } else {
+ // App bubble is not selected, select it
+ mBubbleData.setSelectedBubble(existingAppBubble);
+ }
+ } else {
+ // App bubble is not selected, select it & expand
+ mBubbleData.setSelectedBubble(existingAppBubble);
+ mBubbleData.setExpanded(true);
+ }
+ } else {
+ // App bubble does not exist, lets add and expand it
+ Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
+ b.setShouldAutoExpand(true);
+ inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
+ }
}
/**
@@ -1246,6 +1308,58 @@ public class BubbleController implements ConfigurationChangeListener {
});
}
+ private final BubbleViewCallback mBubbleViewCallback = new BubbleViewCallback() {
+ @Override
+ public void removeBubble(Bubble removedBubble) {
+ if (mStackView != null) {
+ mStackView.removeBubble(removedBubble);
+ }
+ }
+
+ @Override
+ public void addBubble(Bubble addedBubble) {
+ if (mStackView != null) {
+ mStackView.addBubble(addedBubble);
+ }
+ }
+
+ @Override
+ public void updateBubble(Bubble updatedBubble) {
+ if (mStackView != null) {
+ mStackView.updateBubble(updatedBubble);
+ }
+ }
+
+ @Override
+ public void bubbleOrderChanged(List bubbleOrder, boolean updatePointer) {
+ if (mStackView != null) {
+ mStackView.updateBubbleOrder(bubbleOrder, updatePointer);
+ }
+ }
+
+ @Override
+ public void suppressionChanged(Bubble bubble, boolean isSuppressed) {
+ if (mStackView != null) {
+ mStackView.setBubbleSuppressed(bubble, isSuppressed);
+ }
+ }
+
+ @Override
+ public void expansionChanged(boolean isExpanded) {
+ if (mStackView != null) {
+ mStackView.setExpanded(isExpanded);
+ }
+ }
+
+ @Override
+ public void selectionChanged(BubbleViewProvider selectedBubble) {
+ if (mStackView != null) {
+ mStackView.setSelectedBubble(selectedBubble);
+ }
+
+ }
+ };
+
@SuppressWarnings("FieldCanBeLocal")
private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
@@ -1268,7 +1382,8 @@ public class BubbleController implements ConfigurationChangeListener {
// Lazy load overflow bubbles from disk
loadOverflowBubblesFromDisk();
- mStackView.updateOverflowButtonDot();
+ // If bubbles in the overflow have a dot, make sure the overflow shows a dot
+ updateOverflowButtonDot();
// Update bubbles in overflow.
if (mOverflowListener != null) {
@@ -1283,9 +1398,7 @@ public class BubbleController implements ConfigurationChangeListener {
final Bubble bubble = removed.first;
@Bubbles.DismissReason final int reason = removed.second;
- if (mStackView != null) {
- mStackView.removeBubble(bubble);
- }
+ mBubbleViewCallback.removeBubble(bubble);
// Leave the notification in place if we're dismissing due to user switching, or
// because DND is suppressing the bubble. In both of those cases, we need to be able
@@ -1315,49 +1428,47 @@ public class BubbleController implements ConfigurationChangeListener {
}
mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
- if (update.addedBubble != null && mStackView != null) {
+ if (update.addedBubble != null) {
mDataRepository.addBubble(mCurrentUserId, update.addedBubble);
- mStackView.addBubble(update.addedBubble);
+ mBubbleViewCallback.addBubble(update.addedBubble);
}
- if (update.updatedBubble != null && mStackView != null) {
- mStackView.updateBubble(update.updatedBubble);
+ if (update.updatedBubble != null) {
+ mBubbleViewCallback.updateBubble(update.updatedBubble);
}
- if (update.suppressedBubble != null && mStackView != null) {
- mStackView.setBubbleSuppressed(update.suppressedBubble, true);
+ if (update.suppressedBubble != null) {
+ mBubbleViewCallback.suppressionChanged(update.suppressedBubble, true);
}
- if (update.unsuppressedBubble != null && mStackView != null) {
- mStackView.setBubbleSuppressed(update.unsuppressedBubble, false);
+ if (update.unsuppressedBubble != null) {
+ mBubbleViewCallback.suppressionChanged(update.unsuppressedBubble, false);
}
boolean collapseStack = update.expandedChanged && !update.expanded;
// At this point, the correct bubbles are inflated in the stack.
// Make sure the order in bubble data is reflected in bubble row.
- if (update.orderChanged && mStackView != null) {
+ if (update.orderChanged) {
mDataRepository.addBubbles(mCurrentUserId, update.bubbles);
// if the stack is going to be collapsed, do not update pointer position
// after reordering
- mStackView.updateBubbleOrder(update.bubbles, !collapseStack);
+ mBubbleViewCallback.bubbleOrderChanged(update.bubbles, !collapseStack);
}
if (collapseStack) {
- mStackView.setExpanded(false);
+ mBubbleViewCallback.expansionChanged(/* expanded= */ false);
mSysuiProxy.requestNotificationShadeTopUi(false, TAG);
}
- if (update.selectionChanged && mStackView != null) {
- mStackView.setSelectedBubble(update.selectedBubble);
+ if (update.selectionChanged) {
+ mBubbleViewCallback.selectionChanged(update.selectedBubble);
}
// Expanding? Apply this last.
if (update.expandedChanged && update.expanded) {
- if (mStackView != null) {
- mStackView.setExpanded(true);
- mSysuiProxy.requestNotificationShadeTopUi(true, TAG);
- }
+ mBubbleViewCallback.expansionChanged(/* expanded= */ true);
+ mSysuiProxy.requestNotificationShadeTopUi(true, TAG);
}
mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate");
@@ -1368,6 +1479,19 @@ public class BubbleController implements ConfigurationChangeListener {
}
};
+ private void updateOverflowButtonDot() {
+ BubbleOverflow overflow = mBubbleData.getOverflow();
+ if (overflow == null) return;
+
+ for (Bubble b : mBubbleData.getOverflowBubbles()) {
+ if (b.showDot()) {
+ overflow.setShowDot(true);
+ return;
+ }
+ }
+ overflow.setShowDot(false);
+ }
+
private boolean handleDismissalInterception(BubbleEntry entry,
@Nullable List children, IntConsumer removeCallback) {
if (isSummaryOfBubbles(entry)) {
@@ -1697,9 +1821,9 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public void showAppBubble(Intent intent) {
+ public void showOrHideAppBubble(Intent intent) {
mMainExecutor.execute(() -> {
- BubbleController.this.showAppBubble(intent);
+ BubbleController.this.showOrHideAppBubble(intent);
});
}
@@ -1813,13 +1937,6 @@ public class BubbleController implements ConfigurationChangeListener {
});
}
- @Override
- public void setBubbleBarEnabled(boolean enabled) {
- mMainExecutor.execute(() -> {
- BubbleController.this.setBubbleBarEnabled(enabled);
- });
- }
-
@Override
public void onNotificationPanelExpandedChanged(boolean expanded) {
mMainExecutor.execute(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index af31391fec965540ab11d29fce042a1657d578bc..3fd09675a245c22860b02a3e5f7595a0b12961d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -17,6 +17,7 @@ package com.android.wm.shell.bubbles;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -282,7 +283,7 @@ public class BubbleData {
}
boolean isShowingOverflow() {
- return mShowingOverflow && (isExpanded() || mPositioner.showingInTaskbar());
+ return mShowingOverflow && isExpanded();
}
/**
@@ -684,7 +685,8 @@ public class BubbleData {
if (bubble.getPendingIntentCanceled()
|| !(reason == Bubbles.DISMISS_AGED
|| reason == Bubbles.DISMISS_USER_GESTURE
- || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
+ || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)
+ || KEY_APP_BUBBLE.equals(bubble.getKey())) {
return;
}
if (DEBUG_BUBBLE_DATA) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index 3a5961462c87c5a0d7083a86b555bd6cac7f0bb5..e37c785f15f57481a27c7f6d60e51731423f8083 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -109,7 +109,8 @@ internal class BubbleDataRepository(
b.rawDesiredHeightResId,
b.title,
b.taskId,
- b.locusId?.id
+ b.locusId?.id,
+ b.isDismissable
)
}
}
@@ -205,6 +206,7 @@ internal class BubbleDataRepository(
entity.title,
entity.taskId,
entity.locus,
+ entity.isDismissable,
mainExecutor,
bubbleMetadataFlagListener
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
index 5f428269fb06bd81318cffe2e12972714f025891..afe19c4b7363e2ecf6ef2e9d33a27b3319fcb13f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
@@ -38,18 +38,18 @@ public class BubbleEntry {
private StatusBarNotification mSbn;
private Ranking mRanking;
- private boolean mIsClearable;
+ private boolean mIsDismissable;
private boolean mShouldSuppressNotificationDot;
private boolean mShouldSuppressNotificationList;
private boolean mShouldSuppressPeek;
public BubbleEntry(@NonNull StatusBarNotification sbn,
- Ranking ranking, boolean isClearable, boolean shouldSuppressNotificationDot,
+ Ranking ranking, boolean isDismissable, boolean shouldSuppressNotificationDot,
boolean shouldSuppressNotificationList, boolean shouldSuppressPeek) {
mSbn = sbn;
mRanking = ranking;
- mIsClearable = isClearable;
+ mIsDismissable = isDismissable;
mShouldSuppressNotificationDot = shouldSuppressNotificationDot;
mShouldSuppressNotificationList = shouldSuppressNotificationList;
mShouldSuppressPeek = shouldSuppressPeek;
@@ -115,9 +115,9 @@ public class BubbleEntry {
return mRanking.canBubble();
}
- /** @return true if this notification is clearable. */
- public boolean isClearable() {
- return mIsClearable;
+ /** @return true if this notification can be dismissed. */
+ public boolean isDismissable() {
+ return mIsDismissable;
}
/** @return true if {@link Policy#SUPPRESSED_EFFECT_BADGE} set for this notification. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 07c58527a815a6ada289f0abadd0cd6773c0134d..5ea2450114f0856eb931e6214e2c58eb782a9a29 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
@@ -18,9 +18,6 @@ package com.android.wm.shell.bubbles;
import static android.view.View.LAYOUT_DIRECTION_RTL;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -39,8 +36,6 @@ import androidx.annotation.VisibleForTesting;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
-import java.lang.annotation.Retention;
-
/**
* Keeps track of display size, configuration, and specific bubble sizes. One place for all
* placement and positioning calculations to refer to.
@@ -50,15 +45,6 @@ public class BubblePositioner {
? "BubblePositioner"
: BubbleDebugConfig.TAG_BUBBLES;
- @Retention(SOURCE)
- @IntDef({TASKBAR_POSITION_NONE, TASKBAR_POSITION_RIGHT, TASKBAR_POSITION_LEFT,
- TASKBAR_POSITION_BOTTOM})
- @interface TaskbarPosition {}
- public static final int TASKBAR_POSITION_NONE = -1;
- public static final int TASKBAR_POSITION_RIGHT = 0;
- public static final int TASKBAR_POSITION_LEFT = 1;
- public static final int TASKBAR_POSITION_BOTTOM = 2;
-
/** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
public static final int NUM_VISIBLE_WHEN_RESTING = 2;
/** Indicates a bubble's height should be the maximum available space. **/
@@ -108,15 +94,9 @@ public class BubblePositioner {
private int mOverflowHeight;
private int mMinimumFlyoutWidthLargeScreen;
- private PointF mPinLocation;
private PointF mRestingStackPosition;
private int[] mPaddings = new int[4];
- private boolean mShowingInTaskbar;
- private @TaskbarPosition int mTaskbarPosition = TASKBAR_POSITION_NONE;
- private int mTaskbarIconSize;
- private int mTaskbarSize;
-
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
mWindowManager = windowManager;
@@ -153,27 +133,11 @@ public class BubblePositioner {
+ " insets: " + insets
+ " isLargeScreen: " + mIsLargeScreen
+ " isSmallTablet: " + mIsSmallTablet
- + " bounds: " + bounds
- + " showingInTaskbar: " + mShowingInTaskbar);
+ + " bounds: " + bounds);
}
updateInternal(mRotation, insets, bounds);
}
- /**
- * Updates position information to account for taskbar state.
- *
- * @param taskbarPosition which position the taskbar is displayed in.
- * @param showingInTaskbar whether the taskbar is being shown.
- */
- public void updateForTaskbar(int iconSize,
- @TaskbarPosition int taskbarPosition, boolean showingInTaskbar, int taskbarSize) {
- mShowingInTaskbar = showingInTaskbar;
- mTaskbarIconSize = iconSize;
- mTaskbarPosition = taskbarPosition;
- mTaskbarSize = taskbarSize;
- update();
- }
-
@VisibleForTesting
public void updateInternal(int rotation, Insets insets, Rect bounds) {
mRotation = rotation;
@@ -232,10 +196,6 @@ public class BubblePositioner {
R.dimen.bubbles_flyout_min_width_large_screen);
mMaxBubbles = calculateMaxBubbles();
-
- if (mShowingInTaskbar) {
- adjustForTaskbar();
- }
}
/**
@@ -260,30 +220,6 @@ public class BubblePositioner {
return mDefaultMaxBubbles;
}
- /**
- * Taskbar insets appear as navigationBar insets, however, unlike navigationBar this should
- * not inset bubbles UI as bubbles floats above the taskbar. This adjust the available space
- * and insets to account for the taskbar.
- */
- // TODO(b/171559950): When the insets are reported correctly we can remove this logic
- private void adjustForTaskbar() {
- // When bar is showing on edges... subtract that inset because we appear on top
- if (mShowingInTaskbar && mTaskbarPosition != TASKBAR_POSITION_BOTTOM) {
- WindowInsets metricInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
- Insets navBarInsets = metricInsets.getInsetsIgnoringVisibility(
- WindowInsets.Type.navigationBars());
- int newInsetLeft = mInsets.left;
- int newInsetRight = mInsets.right;
- if (mTaskbarPosition == TASKBAR_POSITION_LEFT) {
- mPositionRect.left -= navBarInsets.left;
- newInsetLeft -= navBarInsets.left;
- } else if (mTaskbarPosition == TASKBAR_POSITION_RIGHT) {
- mPositionRect.right += navBarInsets.right;
- newInsetRight -= navBarInsets.right;
- }
- mInsets = Insets.of(newInsetLeft, mInsets.top, newInsetRight, mInsets.bottom);
- }
- }
/**
* @return a rect of available screen space accounting for orientation, system bars and cutouts.
@@ -327,14 +263,12 @@ public class BubblePositioner {
* to the left or right side.
*/
public boolean showBubblesVertically() {
- return isLandscape() || mShowingInTaskbar || mIsLargeScreen;
+ return isLandscape() || mIsLargeScreen;
}
/** Size of the bubble. */
public int getBubbleSize() {
- return (mShowingInTaskbar && mTaskbarIconSize > 0)
- ? mTaskbarIconSize
- : mBubbleSize;
+ return mBubbleSize;
}
/** The amount of padding at the top of the screen that the bubbles avoid when being placed. */
@@ -699,9 +633,6 @@ public class BubblePositioner {
/** The position the bubble stack should rest at when collapsed. */
public PointF getRestingPosition() {
- if (mPinLocation != null) {
- return mPinLocation;
- }
if (mRestingStackPosition == null) {
return getDefaultStartPosition();
}
@@ -713,9 +644,6 @@ public class BubblePositioner {
* is being shown.
*/
public PointF getDefaultStartPosition() {
- if (mPinLocation != null) {
- return mPinLocation;
- }
// Start on the left if we're in LTR, right otherwise.
final boolean startOnLeft =
mContext.getResources().getConfiguration().getLayoutDirection()
@@ -730,7 +658,6 @@ public class BubblePositioner {
1 /* default starts with 1 bubble */));
}
-
/**
* Returns the region that the stack position must stay within. This goes slightly off the left
* and right sides of the screen, below the status bar/cutout and above the navigation bar.
@@ -750,39 +677,6 @@ public class BubblePositioner {
return allowableRegion;
}
- /**
- * @return whether the bubble stack is pinned to the taskbar.
- */
- public boolean showingInTaskbar() {
- return mShowingInTaskbar;
- }
-
- /**
- * @return the taskbar position if set.
- */
- public int getTaskbarPosition() {
- return mTaskbarPosition;
- }
-
- public int getTaskbarSize() {
- return mTaskbarSize;
- }
-
- /**
- * In some situations bubbles will be pinned to a specific onscreen location. This sets whether
- * bubbles should be pinned or not.
- */
- public void setUsePinnedLocation(boolean usePinnedLocation) {
- if (usePinnedLocation) {
- mShowingInTaskbar = true;
- mPinLocation = new PointF(mPositionRect.right - mBubbleSize,
- mPositionRect.bottom - mBubbleSize);
- } else {
- mPinLocation = null;
- mShowingInTaskbar = false;
- }
- }
-
/**
* Navigation bar has an area where system gestures can be started from.
*
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 f2afefe243bc208513383c4e96ffbe333029ed71..5ecbd6b596b6f741acb42490b20ed434313250d2 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
@@ -680,8 +680,6 @@ public class BubbleStackView extends FrameLayout
// Re-show the expanded view if we hid it.
showExpandedViewIfNeeded();
- } else if (mPositioner.showingInTaskbar()) {
- mStackAnimationController.snapStackBack();
} else {
// Fling the stack to the edge, and save whether or not it's going to end up on
// the left side of the screen.
@@ -1362,16 +1360,6 @@ public class BubbleStackView extends FrameLayout
updateOverflowVisibility();
}
- void updateOverflowButtonDot() {
- for (Bubble b : mBubbleData.getOverflowBubbles()) {
- if (b.showDot()) {
- mBubbleOverflow.setShowDot(true);
- return;
- }
- }
- mBubbleOverflow.setShowDot(false);
- }
-
/**
* Handle theme changes.
*/
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 465d1abe0a3dfc99d5b6e330f28a2a35bd9eda00..a5deac5a51da88245bc4d28471216752b5844599 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
@@ -109,13 +109,28 @@ public interface Bubbles {
void expandStackAndSelectBubble(Bubble bubble);
/**
- * Adds and expands bubble that is not notification based, but instead based on an intent from
- * the app. The intent must be explicit (i.e. include a package name or fully qualified
- * component class name) and the activity for it should be resizable.
+ * This method has different behavior depending on:
+ * - if an app bubble exists
+ * - if an app bubble is expanded
*
- * @param intent the intent to populate the bubble.
+ * If no app bubble exists, this will add and expand a bubble with the provided intent. The
+ * intent must be explicit (i.e. include a package name or fully qualified component class name)
+ * and the activity for it should be resizable.
+ *
+ * If an app bubble exists, this will toggle the visibility of it, i.e. if the app bubble is
+ * expanded, calling this method will collapse it. If the app bubble is not expanded, calling
+ * this method will expand it.
+ *
+ * These bubbles are not backed by a notification and remain until the user dismisses
+ * the bubble or bubble stack.
+ *
+ * Some notes:
+ * - Only one app bubble is supported at a time
+ * - Calling this method with a different intent than the existing app bubble will do nothing
+ *
+ * @param intent the intent to display in the bubble expanded view.
*/
- void showAppBubble(Intent intent);
+ void showOrHideAppBubble(Intent intent);
/**
* @return a bubble that matches the provided shortcutId, if one exists.
@@ -242,11 +257,6 @@ public interface Bubbles {
*/
void onUserRemoved(int removedUserId);
- /**
- * Sets whether bubble bar should be enabled or not.
- */
- void setBubbleBarEnabled(boolean enabled);
-
/** Listener to find out about stack expansion / collapse events. */
interface BubbleExpandListener {
/**
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 0ee0ea60a1bc755cb2aea1b857c8ed8435060313..5533842f2d8934b9feaa2b9c95f41396d2276b4f 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
@@ -416,24 +416,10 @@ public class StackAnimationController extends
return destinationRelativeX;
}
- /**
- * 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() {
- if (mPositioner.showingInTaskbar()) {
- return mPositioner.getRestingPosition();
- }
final PointF stackPos = getStackPosition();
final boolean onLeft = mLayout.isFirstChildXLeftOfCenter(stackPos.x);
final RectF bounds = mPositioner.getAllowableStackPositionRegion(getBubbleCount());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
index 186b9b1efa9a5c72ac10ccf41036948439dbd1a1..9b2e263946057651695c5380976e22c2f3bcc97c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
@@ -27,5 +27,6 @@ data class BubbleEntity(
@DimenRes val desiredHeightResId: Int,
val title: String? = null,
val taskId: Int,
- val locus: String? = null
+ val locus: String? = null,
+ val isDismissable: Boolean = false
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
index f4fa1835b7a52e5ab09e0bd460cfa2275fd031c2..48d8ccf401740b9a466c9ca5cfe1c699689e2e9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
@@ -43,6 +43,7 @@ private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid"
private const val ATTR_TITLE = "t"
private const val ATTR_TASK_ID = "tid"
private const val ATTR_LOCUS = "l"
+private const val ATTR_DISMISSABLE = "d"
/**
* Writes the bubbles in xml format into given output stream.
@@ -84,6 +85,7 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) {
bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) }
serializer.attribute(null, ATTR_TASK_ID, bubble.taskId.toString())
bubble.locus?.let { serializer.attribute(null, ATTR_LOCUS, it) }
+ serializer.attribute(null, ATTR_DISMISSABLE, bubble.isDismissable.toString())
serializer.endTag(null, TAG_BUBBLE)
} catch (e: IOException) {
throw RuntimeException(e)
@@ -142,7 +144,8 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? {
parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null,
parser.getAttributeWithName(ATTR_TITLE),
parser.getAttributeWithName(ATTR_TASK_ID)?.toInt() ?: INVALID_TASK_ID,
- parser.getAttributeWithName(ATTR_LOCUS)
+ parser.getAttributeWithName(ATTR_LOCUS),
+ parser.getAttributeWithName(ATTR_DISMISSABLE)?.toBoolean() ?: false
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b4ac1a8dc796c20e289d02230af899d2c3058c7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Wrapper class to track the device posture change on Fold-ables.
+ * See also Foldable states and postures for reference.
+ *
+ * Note that most of the implementation here inherits from
+ * {@link com.android.systemui.statusbar.policy.DevicePostureController}.
+ *
+ * Use the {@link TabletopModeController} if you are interested in tabletop mode change only,
+ * which is more common.
+ */
+public class DevicePostureController {
+ @IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
+ DEVICE_POSTURE_UNKNOWN,
+ DEVICE_POSTURE_CLOSED,
+ DEVICE_POSTURE_HALF_OPENED,
+ DEVICE_POSTURE_OPENED,
+ DEVICE_POSTURE_FLIPPED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DevicePostureInt {}
+
+ // NOTE: These constants **must** match those defined for Jetpack Sidecar. This is because we
+ // use the Device State -> Jetpack Posture map to translate between the two.
+ public static final int DEVICE_POSTURE_UNKNOWN = 0;
+ public static final int DEVICE_POSTURE_CLOSED = 1;
+ public static final int DEVICE_POSTURE_HALF_OPENED = 2;
+ public static final int DEVICE_POSTURE_OPENED = 3;
+ public static final int DEVICE_POSTURE_FLIPPED = 4;
+
+ private final Context mContext;
+ private final ShellExecutor mMainExecutor;
+ private final List mListeners = new ArrayList<>();
+ private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
+
+ private int mDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+ public DevicePostureController(
+ Context context, ShellInit shellInit, ShellExecutor mainExecutor) {
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ // Most of this is borrowed from WindowManager/Jetpack/DeviceStateManagerPostureProducer.
+ // Using the sidecar/extension libraries directly brings in a new dependency that it'd be
+ // good to avoid (along with the fact that sidecar is deprecated, and extensions isn't fully
+ // ready yet), and we'd have to make our own layer over the sidecar library anyway to easily
+ // allow the implementation to change, so it was easier to just interface with
+ // DeviceStateManager directly.
+ String[] deviceStatePosturePairs = mContext.getResources()
+ .getStringArray(R.array.config_device_state_postures);
+ for (String deviceStatePosturePair : deviceStatePosturePairs) {
+ String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
+ if (deviceStatePostureMapping.length != 2) {
+ continue;
+ }
+
+ int deviceState;
+ int posture;
+ try {
+ deviceState = Integer.parseInt(deviceStatePostureMapping[0]);
+ posture = Integer.parseInt(deviceStatePostureMapping[1]);
+ } catch (NumberFormatException e) {
+ continue;
+ }
+
+ mDeviceStateToPostureMap.put(deviceState, posture);
+ }
+
+ final DeviceStateManager deviceStateManager = mContext.getSystemService(
+ DeviceStateManager.class);
+ if (deviceStateManager != null) {
+ deviceStateManager.registerCallback(mMainExecutor, state -> onDevicePostureChanged(
+ mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN)));
+ }
+ }
+
+ @VisibleForTesting
+ void onDevicePostureChanged(int devicePosture) {
+ if (devicePosture == mDevicePosture) return;
+ mDevicePosture = devicePosture;
+ mListeners.forEach(l -> l.onDevicePostureChanged(mDevicePosture));
+ }
+
+ /**
+ * Register {@link OnDevicePostureChangedListener} for device posture changes.
+ * The listener will receive callback with current device posture upon registration.
+ */
+ public void registerOnDevicePostureChangedListener(
+ @NonNull OnDevicePostureChangedListener listener) {
+ if (mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ listener.onDevicePostureChanged(mDevicePosture);
+ }
+
+ /**
+ * Unregister {@link OnDevicePostureChangedListener} for device posture changes.
+ */
+ public void unregisterOnDevicePostureChangedListener(
+ @NonNull OnDevicePostureChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Listener interface for device posture change.
+ */
+ public interface OnDevicePostureChangedListener {
+ /**
+ * Callback when device posture changes.
+ * See {@link DevicePostureInt} for callback values.
+ */
+ void onDevicePostureChanged(@DevicePostureInt int posture);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 96efeeb0c5ebd2f734d9811088afc80a1726cf32..84840139d1a3d1f000cc1bd52d76f309185474f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -96,8 +96,7 @@ public class DisplayLayout {
/**
* Different from {@link #equals(Object)}, this method compares the basic geometry properties
- * of two {@link DisplayLayout} objects including width, height, rotation, density, cutout and
- * insets.
+ * of two {@link DisplayLayout} objects including width, height, rotation, density, cutout.
* @return {@code true} if the given {@link DisplayLayout} is identical geometry wise.
*/
public boolean isSameGeometry(@NonNull DisplayLayout other) {
@@ -105,8 +104,7 @@ public class DisplayLayout {
&& mHeight == other.mHeight
&& mRotation == other.mRotation
&& mDensityDpi == other.mDensityDpi
- && Objects.equals(mCutout, other.mCutout)
- && Objects.equals(mStableInsets, other.mStableInsets);
+ && Objects.equals(mCutout, other.mCutout);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac6e4c2a65211febe8ce51f6a759026b6539e54f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.SystemProperties;
+import android.util.ArraySet;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Wrapper class to track the tabletop (aka. flex) mode change on Fold-ables.
+ * See also Foldable states and postures for reference.
+ *
+ * Use the {@link DevicePostureController} for more detailed posture changes.
+ */
+public class TabletopModeController implements
+ DevicePostureController.OnDevicePostureChangedListener,
+ DisplayController.OnDisplaysChangedListener {
+ /**
+ * When {@code true}, floating windows like PiP would auto move to the position
+ * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode.
+ */
+ private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
+ SystemProperties.getBoolean(
+ "persist.wm.debug.enable_move_floating_window_in_tabletop", true);
+
+ /**
+ * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
+ * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise.
+ * See also {@link #getPreferredHalfInTabletopMode()}.
+ */
+ private static final boolean PREFER_TOP_HALF_IN_TABLETOP =
+ SystemProperties.getBoolean("persist.wm.debug.prefer_top_half_in_tabletop", true);
+
+ private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
+
+ @IntDef(prefix = {"PREFERRED_TABLETOP_HALF_"}, value = {
+ PREFERRED_TABLETOP_HALF_TOP,
+ PREFERRED_TABLETOP_HALF_BOTTOM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PreferredTabletopHalf {}
+
+ public static final int PREFERRED_TABLETOP_HALF_TOP = 0;
+ public static final int PREFERRED_TABLETOP_HALF_BOTTOM = 1;
+
+ private final Context mContext;
+
+ private final DevicePostureController mDevicePostureController;
+
+ private final DisplayController mDisplayController;
+
+ private final ShellExecutor mMainExecutor;
+
+ private final Set mTabletopModeRotations = new ArraySet<>();
+
+ private final List mListeners = new ArrayList<>();
+
+ @VisibleForTesting
+ final Runnable mOnEnterTabletopModeCallback = () -> {
+ if (isInTabletopMode()) {
+ // We are still in tabletop mode, go ahead.
+ mayBroadcastOnTabletopModeChange(true /* isInTabletopMode */);
+ }
+ };
+
+ @DevicePostureController.DevicePostureInt
+ private int mDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+ @Surface.Rotation
+ private int mDisplayRotation = WindowConfiguration.ROTATION_UNDEFINED;
+
+ /**
+ * Track the last callback value for {@link OnTabletopModeChangedListener}.
+ * This is to avoid duplicated {@code false} callback to {@link #mListeners}.
+ */
+ private Boolean mLastIsInTabletopModeForCallback;
+
+ public TabletopModeController(Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ mContext = context;
+ mDevicePostureController = postureController;
+ mDisplayController = displayController;
+ mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ @VisibleForTesting
+ void onInit() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(this);
+ mDisplayController.addDisplayWindowListener(this);
+ // Aligns with what's in {@link com.android.server.wm.DisplayRotation}.
+ final int[] deviceTabletopRotations = mContext.getResources().getIntArray(
+ com.android.internal.R.array.config_deviceTabletopRotations);
+ if (deviceTabletopRotations == null || deviceTabletopRotations.length == 0) {
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "No valid config_deviceTabletopRotations, can not tell"
+ + " tabletop mode in WMShell");
+ return;
+ }
+ for (int angle : deviceTabletopRotations) {
+ switch (angle) {
+ case 0:
+ mTabletopModeRotations.add(Surface.ROTATION_0);
+ break;
+ case 90:
+ mTabletopModeRotations.add(Surface.ROTATION_90);
+ break;
+ case 180:
+ mTabletopModeRotations.add(Surface.ROTATION_180);
+ break;
+ case 270:
+ mTabletopModeRotations.add(Surface.ROTATION_270);
+ break;
+ default:
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "Invalid surface rotation angle in "
+ + "config_deviceTabletopRotations: %d",
+ angle);
+ break;
+ }
+ }
+ }
+
+ /**
+ * @return {@code true} if floating windows like PiP would auto move to the position
+ * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode.
+ */
+ public boolean enableMoveFloatingWindowInTabletop() {
+ return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP;
+ }
+
+ /** @return Preferred half for floating windows like PiP when in tabletop mode. */
+ @PreferredTabletopHalf
+ public int getPreferredHalfInTabletopMode() {
+ return PREFER_TOP_HALF_IN_TABLETOP
+ ? PREFERRED_TABLETOP_HALF_TOP
+ : PREFERRED_TABLETOP_HALF_BOTTOM;
+ }
+
+ /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
+ public void registerOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ if (listener == null || mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ listener.onTabletopModeChanged(isInTabletopMode());
+ }
+
+ /** Unregister {@link OnTabletopModeChangedListener} for tabletop mode change. */
+ public void unregisterOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) {
+ if (mDevicePosture != posture) {
+ onDevicePostureOrDisplayRotationChanged(posture, mDisplayRotation);
+ }
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ final int newDisplayRotation = newConfig.windowConfiguration.getDisplayRotation();
+ if (displayId == DEFAULT_DISPLAY && newDisplayRotation != mDisplayRotation) {
+ onDevicePostureOrDisplayRotationChanged(mDevicePosture, newDisplayRotation);
+ }
+ }
+
+ private void onDevicePostureOrDisplayRotationChanged(
+ @DevicePostureController.DevicePostureInt int newPosture,
+ @Surface.Rotation int newDisplayRotation) {
+ final boolean wasInTabletopMode = isInTabletopMode();
+ mDevicePosture = newPosture;
+ mDisplayRotation = newDisplayRotation;
+ final boolean couldBeInTabletopMode = isInTabletopMode();
+ mMainExecutor.removeCallbacks(mOnEnterTabletopModeCallback);
+ if (!wasInTabletopMode && couldBeInTabletopMode) {
+ // May enter tabletop mode, but we need to wait for additional time since this
+ // could be an intermediate state.
+ mMainExecutor.executeDelayed(mOnEnterTabletopModeCallback, TABLETOP_MODE_DELAY_MILLIS);
+ } else {
+ // Cancel entering tabletop mode if any condition's changed.
+ mayBroadcastOnTabletopModeChange(false /* isInTabletopMode */);
+ }
+ }
+
+ private boolean isHalfOpened(@DevicePostureController.DevicePostureInt int posture) {
+ return posture == DEVICE_POSTURE_HALF_OPENED;
+ }
+
+ private boolean isInTabletopMode() {
+ return isHalfOpened(mDevicePosture) && mTabletopModeRotations.contains(mDisplayRotation);
+ }
+
+ private void mayBroadcastOnTabletopModeChange(boolean isInTabletopMode) {
+ if (mLastIsInTabletopModeForCallback == null
+ || mLastIsInTabletopModeForCallback != isInTabletopMode) {
+ mListeners.forEach(l -> l.onTabletopModeChanged(isInTabletopMode));
+ mLastIsInTabletopModeForCallback = isInTabletopMode;
+ }
+ }
+
+ /**
+ * Listener interface for tabletop mode change.
+ */
+ public interface OnTabletopModeChangedListener {
+ /**
+ * Callback when tabletop mode changes. Expect duplicated callbacks with {@code false}.
+ * @param isInTabletopMode {@code true} if enters tabletop mode, {@code false} otherwise.
+ */
+ void onTabletopModeChanged(boolean isInTabletopMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
index 9e0a48b13413be4ac11e0647fb6d0a728ab722d2..e2106e478bb3dab7b82d2cedf116b8b0caf96a62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
@@ -216,7 +216,6 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler.
args.argi1 = homeTaskVisible ? 1 : 0;
args.argi2 = clearedTask ? 1 : 0;
args.argi3 = wasVisible ? 1 : 0;
- mMainHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT);
mMainHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index a9d3c9f154cded99d42ffcb1369ffcb001d5e236..bdf0ac2ed30cf0152b44282d312b1bc2212ce0a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -78,6 +78,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
private final Rect mResizingBounds = new Rect();
private final Rect mTempRect = new Rect();
private ValueAnimator mFadeAnimator;
+ private ValueAnimator mScreenshotAnimator;
private int mIconSize;
private int mOffsetX;
@@ -135,8 +136,17 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Releases the surfaces for split decor. */
public void release(SurfaceControl.Transaction t) {
- if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
- mFadeAnimator.cancel();
+ if (mFadeAnimator != null) {
+ if (mFadeAnimator.isRunning()) {
+ mFadeAnimator.cancel();
+ }
+ mFadeAnimator = null;
+ }
+ if (mScreenshotAnimator != null) {
+ if (mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+ mScreenshotAnimator = null;
}
if (mViewHost != null) {
mViewHost.release();
@@ -237,17 +247,21 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Stops showing resizing hint. */
public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
if (mScreenshot != null) {
t.setPosition(mScreenshot, mOffsetX, mOffsetY);
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
- final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
- va.addUpdateListener(valueAnimator -> {
+ mScreenshotAnimator = ValueAnimator.ofFloat(1, 0);
+ mScreenshotAnimator.addUpdateListener(valueAnimator -> {
final float progress = (float) valueAnimator.getAnimatedValue();
animT.setAlpha(mScreenshot, progress);
animT.apply();
});
- va.addListener(new AnimatorListenerAdapter() {
+ mScreenshotAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mRunningAnimationCount++;
@@ -266,7 +280,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
}
}
});
- va.start();
+ mScreenshotAnimator.start();
}
if (mResizingIconView == null) {
@@ -292,9 +306,6 @@ public class SplitDecorManager extends WindowlessWindowManager {
});
return;
}
-
- // If fade-in animation is running, cancel it and re-run fade-out one.
- mFadeAnimator.cancel();
}
if (mShown) {
fadeOutDecor(animFinishedCallback);
@@ -310,6 +321,10 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Screenshot host leash and attach on it if meet some conditions */
public void screenshotIfNeeded(SurfaceControl.Transaction t) {
if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
mTempRect.set(mOldBounds);
mTempRect.offsetTo(0, 0);
mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
@@ -322,6 +337,10 @@ public class SplitDecorManager extends WindowlessWindowManager {
if (screenshot == null || !screenshot.isValid()) return;
if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
mScreenshot = screenshot;
t.reparent(screenshot, mHostLeash);
t.setLayer(screenshot, Integer.MAX_VALUE - 1);
@@ -332,6 +351,11 @@ public class SplitDecorManager extends WindowlessWindowManager {
* directly. */
public void fadeOutDecor(Runnable finishedCallback) {
if (mShown) {
+ // If previous animation is running, just cancel it.
+ if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ mFadeAnimator.cancel();
+ }
+
startFadeAnimation(false /* show */, true, finishedCallback);
mShown = false;
} else {
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 45b234a6398a79625d01a3d9879000ad282c21c1..b4acd60461829237cf027bf8336be68cb333c165 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
@@ -120,6 +120,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private int mOrientation;
private int mRotation;
private int mDensity;
+ private int mUiMode;
private final boolean mDimNonImeSide;
private ValueAnimator mDividerFlingAnimator;
@@ -295,10 +296,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
final Rect rootBounds = configuration.windowConfiguration.getBounds();
final int orientation = configuration.orientation;
final int density = configuration.densityDpi;
+ final int uiMode = configuration.uiMode;
if (mOrientation == orientation
&& mRotation == rotation
&& mDensity == density
+ && mUiMode == uiMode
&& mRootBounds.equals(rootBounds)) {
return false;
}
@@ -310,6 +313,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mRootBounds.set(rootBounds);
mRotation = rotation;
mDensity = density;
+ mUiMode = uiMode;
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
updateDividerConfig(mContext);
initDividerPosition(mTempRect);
@@ -699,19 +703,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return bounds.width() > bounds.height();
}
- /** Reverse the split position. */
- @SplitPosition
- public static int reversePosition(@SplitPosition int position) {
- switch (position) {
- case SPLIT_POSITION_TOP_OR_LEFT:
- return SPLIT_POSITION_BOTTOM_OR_RIGHT;
- case SPLIT_POSITION_BOTTOM_OR_RIGHT:
- return SPLIT_POSITION_TOP_OR_LEFT;
- default:
- return SPLIT_POSITION_UNDEFINED;
- }
- }
-
/**
* Return if this layout is landscape.
*/
@@ -736,6 +727,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
getRefBounds2(mTempRect);
t.setPosition(leash2, mTempRect.left, mTempRect.top)
.setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
+ // Make right or bottom side surface always higher than left or top side to avoid weird
+ // animation when dismiss split. e.g. App surface fling above on decor surface.
+ t.setLayer(leash1, 1);
+ t.setLayer(leash2, 2);
if (mImePositionProcessor.adjustSurfaceLayoutForIme(
t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..042721c97053bcb121885c222a28fa3080d606c4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.wm.shell.ShellTaskOrganizer;
+
+/** Helper utility class for split screen components to use. */
+public class SplitScreenUtils {
+ /** Reverse the split position. */
+ @SplitScreenConstants.SplitPosition
+ public static int reverseSplitPosition(@SplitScreenConstants.SplitPosition int position) {
+ switch (position) {
+ case SPLIT_POSITION_TOP_OR_LEFT:
+ return SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ case SPLIT_POSITION_BOTTOM_OR_RIGHT:
+ return SPLIT_POSITION_TOP_OR_LEFT;
+ case SPLIT_POSITION_UNDEFINED:
+ default:
+ return SPLIT_POSITION_UNDEFINED;
+ }
+ }
+
+ /** Returns true if the task is valid for split screen. */
+ public static boolean isValidToSplit(ActivityManager.RunningTaskInfo taskInfo) {
+ return taskInfo != null && taskInfo.supportsMultiWindow
+ && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
+ && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
+ }
+
+ /** Retrieve package name from an intent */
+ @Nullable
+ public static String getPackageName(Intent intent) {
+ if (intent == null || intent.getComponent() == null) {
+ return null;
+ }
+ return intent.getComponent().getPackageName();
+ }
+
+ /** Retrieve package name from a PendingIntent */
+ @Nullable
+ public static String getPackageName(PendingIntent pendingIntent) {
+ if (pendingIntent == null || pendingIntent.getIntent() == null) {
+ return null;
+ }
+ return getPackageName(pendingIntent.getIntent());
+ }
+
+ /** Retrieve package name from a taskId */
+ @Nullable
+ public static String getPackageName(int taskId, ShellTaskOrganizer taskOrganizer) {
+ final ActivityManager.RunningTaskInfo taskInfo = taskOrganizer.getRunningTaskInfo(taskId);
+ return taskInfo != null ? getPackageName(taskInfo.baseIntent) : null;
+ }
+
+ /** Returns true if they are the same package. */
+ public static boolean samePackage(String packageName1, String packageName2) {
+ return packageName1 != null && packageName1.equals(packageName2);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 4f33a71b80d50e14803a832969e78098be206309..09d99b204bdba1c071be464a6a13b34bb500afe5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -16,11 +16,12 @@
package com.android.wm.shell.compatui;
+import android.annotation.NonNull;
+import android.app.TaskInfo;
import android.content.Context;
+import android.content.SharedPreferences;
import android.provider.DeviceConfig;
-import androidx.annotation.NonNull;
-
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellMainThread;
@@ -34,10 +35,52 @@ import javax.inject.Inject;
@WMSingleton
public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener {
- static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = "enable_letterbox_restart_dialog";
+ private static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG =
+ "enable_letterbox_restart_confirmation_dialog";
+
+ private static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
+ "enable_letterbox_education_for_reachability";
+
+ private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG = true;
- static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
- "enable_letterbox_reachability_education";
+ private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = true;
+
+ /**
+ * The name of the {@link SharedPreferences} that holds information about compat ui.
+ */
+ private static final String COMPAT_UI_SHARED_PREFERENCES = "dont_show_restart_dialog";
+
+ /**
+ * The name of the {@link SharedPreferences} that holds which user has seen the Letterbox
+ * Education dialog.
+ */
+ private static final String HAS_SEEN_LETTERBOX_EDUCATION_SHARED_PREFERENCES =
+ "has_seen_letterbox_education";
+
+ /**
+ * Key prefix for the {@link SharedPreferences} entries related to the horizontal
+ * reachability education.
+ */
+ private static final String HAS_SEEN_HORIZONTAL_REACHABILITY_EDUCATION_KEY_PREFIX =
+ "has_seen_horizontal_reachability_education";
+
+ /**
+ * Key prefix for the {@link SharedPreferences} entries related to the vertical reachability
+ * education.
+ */
+ private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
+ "has_seen_vertical_reachability_education";
+
+ /**
+ * The {@link SharedPreferences} instance for the restart dialog and the reachability
+ * education.
+ */
+ private final SharedPreferences mCompatUISharedPreferences;
+
+ /**
+ * The {@link SharedPreferences} instance for the letterbox education dialog.
+ */
+ private final SharedPreferences mLetterboxEduSharedPreferences;
// Whether the extended restart dialog is enabled
private boolean mIsRestartDialogEnabled;
@@ -64,12 +107,17 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
mIsReachabilityEducationEnabled = context.getResources().getBoolean(
R.bool.config_letterboxIsReachabilityEducationEnabled);
mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, false);
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
+ DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION,
- false);
+ DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor,
this);
+ mCompatUISharedPreferences = context.getSharedPreferences(getCompatUISharedPreferenceName(),
+ Context.MODE_PRIVATE);
+ mLetterboxEduSharedPreferences = context.getSharedPreferences(
+ getHasSeenLetterboxEducationSharedPreferencedName(), Context.MODE_PRIVATE);
}
/**
@@ -87,14 +135,6 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
mIsRestartDialogOverrideEnabled = enabled;
}
- /**
- * @return {@value true} if the reachability education is enabled.
- */
- boolean isReachabilityEducationEnabled() {
- return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
- && mIsLetterboxReachabilityEducationAllowed);
- }
-
/**
* Enables/Disables the reachability education
*/
@@ -102,18 +142,100 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
mIsReachabilityEducationOverrideEnabled = enabled;
}
+ void setDontShowRestartDialogAgain(TaskInfo taskInfo) {
+ mCompatUISharedPreferences.edit().putBoolean(
+ dontShowAgainRestartKey(taskInfo.userId, taskInfo.topActivity.getPackageName()),
+ true).apply();
+ }
+
+ boolean shouldShowRestartDialogAgain(TaskInfo taskInfo) {
+ return !mCompatUISharedPreferences.getBoolean(dontShowAgainRestartKey(taskInfo.userId,
+ taskInfo.topActivity.getPackageName()), /* default= */ false);
+ }
+
+ void setUserHasSeenHorizontalReachabilityEducation(TaskInfo taskInfo) {
+ mCompatUISharedPreferences.edit().putBoolean(
+ hasSeenHorizontalReachabilityEduKey(taskInfo.userId), true).apply();
+ }
+
+ void setUserHasSeenVerticalReachabilityEducation(TaskInfo taskInfo) {
+ mCompatUISharedPreferences.edit().putBoolean(
+ hasSeenVerticalReachabilityEduKey(taskInfo.userId), true).apply();
+ }
+
+ boolean hasSeenHorizontalReachabilityEducation(@NonNull TaskInfo taskInfo) {
+ return mCompatUISharedPreferences.getBoolean(
+ hasSeenHorizontalReachabilityEduKey(taskInfo.userId), /* default= */false);
+ }
+
+ boolean hasSeenVerticalReachabilityEducation(@NonNull TaskInfo taskInfo) {
+ return mCompatUISharedPreferences.getBoolean(
+ hasSeenVerticalReachabilityEduKey(taskInfo.userId), /* default= */false);
+ }
+
+ boolean shouldShowReachabilityEducation(@NonNull TaskInfo taskInfo) {
+ return isReachabilityEducationEnabled()
+ && (!hasSeenHorizontalReachabilityEducation(taskInfo)
+ || !hasSeenVerticalReachabilityEducation(taskInfo));
+ }
+
+ boolean getHasSeenLetterboxEducation(int userId) {
+ return mLetterboxEduSharedPreferences
+ .getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
+ }
+
+ void setSeenLetterboxEducation(int userId) {
+ mLetterboxEduSharedPreferences.edit().putBoolean(dontShowLetterboxEduKey(userId),
+ true).apply();
+ }
+
+ protected String getCompatUISharedPreferenceName() {
+ return COMPAT_UI_SHARED_PREFERENCES;
+ }
+
+ protected String getHasSeenLetterboxEducationSharedPreferencedName() {
+ return HAS_SEEN_LETTERBOX_EDUCATION_SHARED_PREFERENCES;
+ }
+
+ /**
+ * Updates the {@link DeviceConfig} state for the CompatUI
+ * @param properties Contains the complete collection of properties which have changed for a
+ * single namespace. This includes only those which were added, updated,
+ */
@Override
public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
- // TODO(b/263349751): Update flag and default value to true
if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_RESTART_DIALOG)) {
mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
- false);
+ DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
}
+ // TODO(b/263349751): Update flag and default value to true
if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION)) {
mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, false);
+ KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION,
+ DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION);
}
}
-}
+
+ private boolean isReachabilityEducationEnabled() {
+ return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
+ && mIsLetterboxReachabilityEducationAllowed);
+ }
+
+ private static String hasSeenHorizontalReachabilityEduKey(int userId) {
+ return HAS_SEEN_HORIZONTAL_REACHABILITY_EDUCATION_KEY_PREFIX + "@" + userId;
+ }
+
+ private static String hasSeenVerticalReachabilityEduKey(int userId) {
+ return HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX + "@" + userId;
+ }
+
+ private static String dontShowLetterboxEduKey(int userId) {
+ return String.valueOf(userId);
+ }
+
+ private String dontShowAgainRestartKey(int userId, String packageName) {
+ return packageName + "@" + userId;
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 6627de58cce350bfc3e7a8d4e70e6a005afe44b0..838e37a905db3eb7fa0d6a3a3ac17164ce0ba5da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -24,6 +24,7 @@ import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import android.view.Display;
import android.view.InsetsSourceControl;
@@ -41,7 +42,6 @@ import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
-import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -49,6 +49,7 @@ import com.android.wm.shell.transition.Transitions;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -90,6 +91,18 @@ public class CompatUIController implements OnDisplaysChangedListener,
*/
private final SparseArray mActiveCompatLayouts = new SparseArray<>(0);
+ /**
+ * {@link SparseArray} that maps task ids to {@link RestartDialogWindowManager} that are
+ * currently visible
+ */
+ private final SparseArray mTaskIdToRestartDialogWindowManagerMap =
+ new SparseArray<>(0);
+
+ /**
+ * {@link Set} of task ids for which we need to display a restart confirmation dialog
+ */
+ private Set mSetOfTaskIdsShowingRestartDialog = new HashSet<>();
+
/**
* The active Letterbox Education layout if there is one (there can be at most one active).
*
@@ -99,6 +112,12 @@ public class CompatUIController implements OnDisplaysChangedListener,
@Nullable
private LetterboxEduWindowManager mActiveLetterboxEduLayout;
+ /**
+ * The active Reachability UI layout.
+ */
+ @Nullable
+ private ReachabilityEduWindowManager mActiveReachabilityEduLayout;
+
/** Avoid creating display context frequently for non-default display. */
private final SparseArray> mDisplayContextCache = new SparseArray<>(0);
@@ -111,11 +130,12 @@ public class CompatUIController implements OnDisplaysChangedListener,
private final ShellExecutor mMainExecutor;
private final Lazy mTransitionsLazy;
private final DockStateReader mDockStateReader;
-
- private CompatUICallback mCallback;
-
+ private final CompatUIConfiguration mCompatUIConfiguration;
// Only show each hint once automatically in the process life.
private final CompatUIHintsState mCompatUIHintsState;
+ private final CompatUIShellCommandHandler mCompatUIShellCommandHandler;
+
+ private CompatUICallback mCallback;
// Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
// be shown.
@@ -130,7 +150,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
SyncTransactionQueue syncQueue,
ShellExecutor mainExecutor,
Lazy transitionsLazy,
- DockStateReader dockStateReader) {
+ DockStateReader dockStateReader,
+ CompatUIConfiguration compatUIConfiguration,
+ CompatUIShellCommandHandler compatUIShellCommandHandler) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -140,14 +162,17 @@ public class CompatUIController implements OnDisplaysChangedListener,
mMainExecutor = mainExecutor;
mTransitionsLazy = transitionsLazy;
mCompatUIHintsState = new CompatUIHintsState();
- shellInit.addInitCallback(this::onInit, this);
mDockStateReader = dockStateReader;
+ mCompatUIConfiguration = compatUIConfiguration;
+ mCompatUIShellCommandHandler = compatUIShellCommandHandler;
+ shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mShellController.addKeyguardChangeListener(this);
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
+ mCompatUIShellCommandHandler.onInit();
}
/** Sets the callback for UI interactions. */
@@ -164,6 +189,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
*/
public void onCompatInfoChanged(TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ if (taskInfo != null && !taskInfo.topActivityInSizeCompat) {
+ mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
+ }
if (taskInfo.configuration == null || taskListener == null) {
// Null token means the current foreground activity is not in compatibility mode.
removeLayouts(taskInfo.taskId);
@@ -172,6 +200,10 @@ public class CompatUIController implements OnDisplaysChangedListener,
createOrUpdateCompatLayout(taskInfo, taskListener);
createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
+ createOrUpdateRestartDialogLayout(taskInfo, taskListener);
+ if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
+ createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ }
}
@Override
@@ -278,7 +310,21 @@ public class CompatUIController implements OnDisplaysChangedListener,
ShellTaskOrganizer.TaskListener taskListener) {
return new CompatUIWindowManager(context,
taskInfo, mSyncQueue, mCallback, taskListener,
- mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState);
+ mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
+ mCompatUIConfiguration, this::onRestartButtonClicked);
+ }
+
+ private void onRestartButtonClicked(
+ Pair taskInfoState) {
+ if (mCompatUIConfiguration.isRestartDialogEnabled()
+ && mCompatUIConfiguration.shouldShowRestartDialogAgain(
+ taskInfoState.first)) {
+ // We need to show the dialog
+ mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
+ onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
+ } else {
+ mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
+ }
}
private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
@@ -318,15 +364,112 @@ public class CompatUIController implements OnDisplaysChangedListener,
ShellTaskOrganizer.TaskListener taskListener) {
return new LetterboxEduWindowManager(context, taskInfo,
mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
- mTransitionsLazy.get(),
- this::onLetterboxEduDismissed,
- mDockStateReader);
+ mTransitionsLazy.get(), this::onLetterboxEduDismissed, mDockStateReader,
+ mCompatUIConfiguration);
}
- private void onLetterboxEduDismissed() {
+ private void onLetterboxEduDismissed(
+ Pair stateInfo) {
mActiveLetterboxEduLayout = null;
+ // We need to update the UI
+ createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second);
+ }
+
+ private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ RestartDialogWindowManager layout =
+ mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
+ if (layout != null) {
+ if (layout.needsToBeRecreated(taskInfo, taskListener)) {
+ mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId);
+ layout.release();
+ } else {
+ layout.setRequestRestartDialog(
+ mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId));
+ // UI already exists, update the UI layout.
+ if (!layout.updateCompatInfo(taskInfo, taskListener,
+ showOnDisplay(layout.getDisplayId()))) {
+ // The layout is no longer eligible to be shown, remove from active layouts.
+ mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId);
+ }
+ return;
+ }
+ }
+ // Create a new UI layout.
+ final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+ if (context == null) {
+ return;
+ }
+ layout = createRestartDialogWindowManager(context, taskInfo, taskListener);
+ layout.setRequestRestartDialog(
+ mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId));
+ if (layout.createLayout(showOnDisplay(taskInfo.displayId))) {
+ // The new layout is eligible to be shown, add it the active layouts.
+ mTaskIdToRestartDialogWindowManagerMap.put(taskInfo.taskId, layout);
+ }
}
+ @VisibleForTesting
+ RestartDialogWindowManager createRestartDialogWindowManager(Context context, TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ return new RestartDialogWindowManager(context, taskInfo, mSyncQueue, taskListener,
+ mDisplayController.getDisplayLayout(taskInfo.displayId), mTransitionsLazy.get(),
+ this::onRestartDialogCallback, this::onRestartDialogDismissCallback,
+ mCompatUIConfiguration);
+ }
+
+ private void onRestartDialogCallback(
+ Pair stateInfo) {
+ mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
+ mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
+ }
+
+ private void onRestartDialogDismissCallback(
+ Pair stateInfo) {
+ mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId);
+ onCompatInfoChanged(stateInfo.first, stateInfo.second);
+ }
+
+ private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ if (mActiveReachabilityEduLayout != null) {
+ // UI already exists, update the UI layout.
+ if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener,
+ showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) {
+ // The layout is no longer eligible to be shown, remove from active layouts.
+ mActiveReachabilityEduLayout = null;
+ }
+ return;
+ }
+ // Create a new UI layout.
+ final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+ if (context == null) {
+ return;
+ }
+ ReachabilityEduWindowManager newLayout = createReachabilityEduWindowManager(context,
+ taskInfo, taskListener);
+ if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) {
+ // The new layout is eligible to be shown, make it the active layout.
+ if (mActiveReachabilityEduLayout != null) {
+ // Release the previous layout since at most one can be active.
+ // Since letterbox reachability education is only shown once to the user,
+ // releasing the previous layout is only a precaution.
+ mActiveReachabilityEduLayout.release();
+ }
+ mActiveReachabilityEduLayout = newLayout;
+ }
+ }
+
+ @VisibleForTesting
+ ReachabilityEduWindowManager createReachabilityEduWindowManager(Context context,
+ TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue,
+ taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
+ mCompatUIConfiguration, mMainExecutor);
+ }
+
+
private void removeLayouts(int taskId) {
final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
if (layout != null) {
@@ -338,6 +481,19 @@ public class CompatUIController implements OnDisplaysChangedListener,
mActiveLetterboxEduLayout.release();
mActiveLetterboxEduLayout = null;
}
+
+ final RestartDialogWindowManager restartLayout =
+ mTaskIdToRestartDialogWindowManagerMap.get(taskId);
+ if (restartLayout != null) {
+ restartLayout.release();
+ mTaskIdToRestartDialogWindowManagerMap.remove(taskId);
+ mSetOfTaskIdsShowingRestartDialog.remove(taskId);
+ }
+ if (mActiveReachabilityEduLayout != null
+ && mActiveReachabilityEduLayout.getTaskId() == taskId) {
+ mActiveReachabilityEduLayout.release();
+ mActiveReachabilityEduLayout = null;
+ }
}
private Context getOrCreateDisplayContext(int displayId) {
@@ -382,6 +538,17 @@ public class CompatUIController implements OnDisplaysChangedListener,
if (mActiveLetterboxEduLayout != null && condition.test(mActiveLetterboxEduLayout)) {
callback.accept(mActiveLetterboxEduLayout);
}
+ for (int i = 0; i < mTaskIdToRestartDialogWindowManagerMap.size(); i++) {
+ final int taskId = mTaskIdToRestartDialogWindowManagerMap.keyAt(i);
+ final RestartDialogWindowManager layout =
+ mTaskIdToRestartDialogWindowManagerMap.get(taskId);
+ if (layout != null && condition.test(layout)) {
+ callback.accept(layout);
+ }
+ }
+ if (mActiveReachabilityEduLayout != null && condition.test(mActiveReachabilityEduLayout)) {
+ callback.accept(mActiveReachabilityEduLayout);
+ }
}
/** An implementation of {@link OnInsetsChangedListener} for a given display id. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
index d44b4d8f63b6fb4abeb313ecc58937b2aeaebabd..f65c26ada04dc656ce26fb47198f5942e9750b99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -21,6 +21,7 @@ import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
@@ -111,6 +112,14 @@ class CompatUILayout extends LinearLayout {
mWindowManager.relayout();
}
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mWindowManager.relayout();
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index bce3ec4128e8396e399e3b1ec0fbd89e4596376f..170c0ee91b4086f04a6be9b1944abe50cab268b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -21,12 +21,14 @@ import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,7 +38,8 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
-import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+
+import java.util.function.Consumer;
/**
* Window manager for the Size Compat restart button and Camera Compat control.
@@ -50,6 +53,13 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
private final CompatUICallback mCallback;
+ private final CompatUIConfiguration mCompatUIConfiguration;
+
+ private final Consumer> mOnRestartButtonClicked;
+
+ @NonNull
+ private TaskInfo mTaskInfo;
+
// Remember the last reported states in case visibility changes due to keyguard or IME updates.
@VisibleForTesting
boolean mHasSizeCompat;
@@ -68,12 +78,16 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
CompatUIWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, CompatUICallback callback,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
- CompatUIHintsState compatUIHintsState) {
+ CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration,
+ Consumer> onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mTaskInfo = taskInfo;
mCallback = callback;
mHasSizeCompat = taskInfo.topActivityInSizeCompat;
mCameraCompatControlState = taskInfo.cameraCompatControlState;
mCompatUIHintsState = compatUIHintsState;
+ mCompatUIConfiguration = compatUIConfiguration;
+ mOnRestartButtonClicked = onRestartButtonClicked;
}
@Override
@@ -119,6 +133,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@Override
public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
boolean canShow) {
+ mTaskInfo = taskInfo;
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
mHasSizeCompat = taskInfo.topActivityInSizeCompat;
@@ -138,7 +153,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
/** Called when the restart button is clicked. */
void onRestartButtonClicked() {
- mCallback.onSizeCompatRestartButtonClicked(mTaskId);
+ mOnRestartButtonClicked.accept(Pair.create(mTaskInfo, getTaskListener()));
}
/** Called when the camera treatment button is clicked. */
@@ -199,8 +214,14 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
: taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
final int positionY = taskStableBounds.bottom - taskBounds.top
- mLayout.getMeasuredHeight();
-
+ // To secure a proper visualisation, we hide the layout while updating the position of
+ // the {@link SurfaceControl} it belongs.
+ final int oldVisibility = mLayout.getVisibility();
+ if (oldVisibility == View.VISIBLE) {
+ mLayout.setVisibility(View.GONE);
+ }
updateSurfacePosition(positionX, positionY);
+ mLayout.setVisibility(oldVisibility);
}
private void updateVisibilityOfViews() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index face24340a4e5b312cef471f98c8a6e1236e9738..346cd940e6782ff0af0798eee4922789cff53190 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -169,6 +169,10 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
initSurface(mLeash);
}
+ protected ShellTaskOrganizer.TaskListener getTaskListener() {
+ return mTaskListener;
+ }
+
/** Inits the z-order of the surface. */
private void initSurface(SurfaceControl leash) {
final int z = getZOrder();
@@ -206,7 +210,8 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
}
View layout = getLayout();
- if (layout == null || prevTaskListener != taskListener) {
+ if (layout == null || prevTaskListener != taskListener
+ || mTaskConfig.uiMode != prevTaskConfig.uiMode) {
// Layout wasn't created yet or TaskListener changed, recreate the layout for new
// surface parent.
release();
@@ -379,7 +384,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
// Cannot be wrap_content as this determines the actual window size
width, height,
TYPE_APPLICATION_OVERLAY,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
+ getWindowManagerLayoutParamsFlags(),
PixelFormat.TRANSLUCENT);
winParams.token = new Binder();
winParams.setTitle(getClass().getSimpleName() + mTaskId);
@@ -387,6 +392,13 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
return winParams;
}
+ /**
+ * @return Flags to use for the {@link WindowManager} layout
+ */
+ protected int getWindowManagerLayoutParamsFlags() {
+ return FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL;
+ }
+
protected final String getTag() {
return getClass().getSimpleName();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java
similarity index 95%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java
index 02197f644a39fb49a9004d800c85a5f29ba912c6..9974295123b783fdcf79e1c807122b3d6a556579 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import android.content.Context;
import android.content.res.TypedArray;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java
similarity index 95%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java
index 9232f36cf9397e84144472f7399d8efb0fc144b1..df2f6ce24ebcedfc727b459b7cc86102c1bc943d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import android.annotation.Nullable;
import android.content.Context;
@@ -26,7 +26,6 @@ import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.wm.shell.R;
-import com.android.wm.shell.compatui.DialogContainerSupplier;
/**
* Container for Letterbox Education Dialog and background dim.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
similarity index 82%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
index c14c009721a1f70d429f5126edca4dcef034f973..0c21c8ccd68687f09aaf87c75167d1b7d7d1c8a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.content.Context;
-import android.content.SharedPreferences;
import android.graphics.Rect;
import android.provider.Settings;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
@@ -36,14 +37,14 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
-import com.android.wm.shell.compatui.DialogAnimationController;
import com.android.wm.shell.transition.Transitions;
+import java.util.function.Consumer;
+
/**
* Window manager for the Letterbox Education.
*/
-public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
+class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
/**
* The Letterbox Education should be the topmost child of the Task in case there can be more
@@ -51,19 +52,6 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
*/
public static final int Z_ORDER = Integer.MAX_VALUE;
- /**
- * The name of the {@link SharedPreferences} that holds which user has seen the Letterbox
- * Education dialog.
- */
- @VisibleForTesting
- static final String HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME =
- "has_seen_letterbox_education";
-
- /**
- * The {@link SharedPreferences} instance for {@link #HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME}.
- */
- private final SharedPreferences mSharedPreferences;
-
private final DialogAnimationController mAnimationController;
private final Transitions mTransitions;
@@ -75,6 +63,10 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
*/
private final int mUserId;
+ private final Consumer> mOnDismissCallback;
+
+ private final CompatUIConfiguration mCompatUIConfiguration;
+
// Remember the last reported state in case visibility changes due to keyguard or IME updates.
private boolean mEligibleForLetterboxEducation;
@@ -82,7 +74,8 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
@VisibleForTesting
LetterboxEduDialogLayout mLayout;
- private final Runnable mOnDismissCallback;
+ @NonNull
+ private TaskInfo mTaskInfo;
/**
* The vertical margin between the dialog container and the task stable bounds (excluding
@@ -92,33 +85,35 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
private final DockStateReader mDockStateReader;
- public LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
+ LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
DisplayLayout displayLayout, Transitions transitions,
- Runnable onDismissCallback, DockStateReader dockStateReader) {
+ Consumer> onDismissCallback,
+ DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
onDismissCallback,
new DialogAnimationController<>(context, /* tag */ "LetterboxEduWindowManager"),
- dockStateReader);
+ dockStateReader, compatUIConfiguration);
}
@VisibleForTesting
LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
- DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback,
+ DisplayLayout displayLayout, Transitions transitions,
+ Consumer> onDismissCallback,
DialogAnimationController animationController,
- DockStateReader dockStateReader) {
+ DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mTaskInfo = taskInfo;
mTransitions = transitions;
mOnDismissCallback = onDismissCallback;
mAnimationController = animationController;
mUserId = taskInfo.userId;
- mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
- mSharedPreferences = mContext.getSharedPreferences(HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME,
- Context.MODE_PRIVATE);
mDialogVerticalMargin = (int) mContext.getResources().getDimension(
R.dimen.letterbox_education_dialog_margin);
mDockStateReader = dockStateReader;
+ mCompatUIConfiguration = compatUIConfiguration;
+ mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
}
@Override
@@ -144,8 +139,8 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
// the controller will create a new instance of this class since this one isn't eligible).
// - If the layout isn't null then it was previously showing, and we shouldn't check if the
// user has seen the letterbox education before.
- return mEligibleForLetterboxEducation && !isTaskbarEduShowing()
- && (mLayout != null || !getHasSeenLetterboxEducation())
+ return mEligibleForLetterboxEducation && !isTaskbarEduShowing() && (mLayout != null
+ || !mCompatUIConfiguration.getHasSeenLetterboxEducation(mUserId))
&& !mDockStateReader.isDocked();
}
@@ -194,7 +189,6 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
// Dialog has already been released.
return;
}
- setSeenLetterboxEducation();
mLayout.setDismissOnClickListener(this::onDismiss);
// Focus on the dialog title for accessibility.
mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
@@ -204,10 +198,11 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
if (mLayout == null) {
return;
}
+ mCompatUIConfiguration.setSeenLetterboxEducation(mUserId);
mLayout.setDismissOnClickListener(null);
mAnimationController.startExitAnimation(mLayout, () -> {
release();
- mOnDismissCallback.run();
+ mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
});
}
@@ -220,6 +215,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
@Override
public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
boolean canShow) {
+ mTaskInfo = taskInfo;
mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
return super.updateCompatInfo(taskInfo, taskListener, canShow);
@@ -250,18 +246,6 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
taskBounds.height());
}
- private boolean getHasSeenLetterboxEducation() {
- return mSharedPreferences.getBoolean(getPrefKey(), /* default= */ false);
- }
-
- private void setSeenLetterboxEducation() {
- mSharedPreferences.edit().putBoolean(getPrefKey(), true).apply();
- }
-
- private String getPrefKey() {
- return String.valueOf(mUserId);
- }
-
@VisibleForTesting
boolean isTaskbarEduShowing() {
return Settings.Secure.getInt(mContext.getContentResolver(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc3d1d35fecb72480a9615cca315da6fbdcbb76a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.app.TaskInfo.PROPERTY_VALUE_UNSET;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.wm.shell.R;
+
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+/**
+ * Container for reachability education which handles all the show/hide animations.
+ */
+public class ReachabilityEduLayout extends FrameLayout {
+
+ private static final float ALPHA_FULL_TRANSPARENT = 0f;
+
+ private static final float ALPHA_FULL_OPAQUE = 1f;
+
+ private static final long VISIBILITY_ANIMATION_DURATION_MS = 400;
+
+ private static final long MARGINS_ANIMATION_DURATION_MS = 250;
+
+ private static final String ALPHA_PROPERTY_NAME = "alpha";
+
+ private ReachabilityEduWindowManager mWindowManager;
+
+ private View mMoveLeftButton;
+ private View mMoveRightButton;
+ private View mMoveUpButton;
+ private View mMoveDownButton;
+
+ private int mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ private int mLastRightMargin = PROPERTY_VALUE_UNSET;
+ private int mLastTopMargin = PROPERTY_VALUE_UNSET;
+ private int mLastBottomMargin = PROPERTY_VALUE_UNSET;
+
+ public ReachabilityEduLayout(Context context) {
+ this(context, null);
+ }
+
+ public ReachabilityEduLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ReachabilityEduLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ReachabilityEduLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ void inject(ReachabilityEduWindowManager windowManager) {
+ mWindowManager = windowManager;
+ }
+
+ void handleVisibility(boolean horizontalEnabled, boolean verticalEnabled,
+ int letterboxVerticalPosition,
+ int letterboxHorizontalPosition, int availableWidth, int availableHeight,
+ CompatUIConfiguration compatUIConfiguration, TaskInfo taskInfo) {
+ hideAllImmediately();
+ if (horizontalEnabled && letterboxHorizontalPosition != PROPERTY_VALUE_UNSET) {
+ handleLetterboxHorizontalPosition(availableWidth, letterboxHorizontalPosition);
+ compatUIConfiguration.setUserHasSeenHorizontalReachabilityEducation(taskInfo);
+ } else if (verticalEnabled && letterboxVerticalPosition != PROPERTY_VALUE_UNSET) {
+ handleLetterboxVerticalPosition(availableHeight, letterboxVerticalPosition);
+ compatUIConfiguration.setUserHasSeenVerticalReachabilityEducation(taskInfo);
+ }
+ }
+
+ void hideAllImmediately() {
+ hideImmediately(mMoveLeftButton);
+ hideImmediately(mMoveRightButton);
+ hideImmediately(mMoveUpButton);
+ hideImmediately(mMoveDownButton);
+ mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ mLastRightMargin = PROPERTY_VALUE_UNSET;
+ mLastTopMargin = PROPERTY_VALUE_UNSET;
+ mLastBottomMargin = PROPERTY_VALUE_UNSET;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mMoveLeftButton = findViewById(R.id.reachability_move_left_button);
+ mMoveRightButton = findViewById(R.id.reachability_move_right_button);
+ mMoveUpButton = findViewById(R.id.reachability_move_up_button);
+ mMoveDownButton = findViewById(R.id.reachability_move_down_button);
+ mMoveLeftButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ mMoveRightButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ mMoveUpButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ mMoveDownButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ }
+
+ private void hideImmediately(View view) {
+ view.setAlpha(0);
+ view.setVisibility(View.INVISIBLE);
+ }
+
+ private Animator marginAnimator(View view, Function marginSupplier,
+ BiConsumer marginConsumer, int from, int to) {
+ final LayoutParams layoutParams = ((LayoutParams) view.getLayoutParams());
+ ValueAnimator animator = ValueAnimator.ofInt(marginSupplier.apply(layoutParams), from, to);
+ animator.addUpdateListener(valueAnimator -> {
+ marginConsumer.accept(layoutParams, (Integer) valueAnimator.getAnimatedValue());
+ view.requestLayout();
+ });
+ animator.setDuration(MARGINS_ANIMATION_DURATION_MS);
+ return animator;
+ }
+
+ private void handleLetterboxHorizontalPosition(int availableWidth,
+ int letterboxHorizontalPosition) {
+ hideItem(mMoveUpButton);
+ hideItem(mMoveDownButton);
+ mLastTopMargin = PROPERTY_VALUE_UNSET;
+ mLastBottomMargin = PROPERTY_VALUE_UNSET;
+ // We calculate the available space on the left and right
+ final int horizontalGap = availableWidth / 2;
+ final int leftAvailableSpace = letterboxHorizontalPosition * horizontalGap;
+ final int rightAvailableSpace = availableWidth - leftAvailableSpace;
+ // We show the button if we have enough space
+ if (leftAvailableSpace >= mMoveLeftButton.getMeasuredWidth()) {
+ int newLeftMargin = (horizontalGap - mMoveLeftButton.getMeasuredWidth()) / 2;
+ if (mLastLeftMargin == PROPERTY_VALUE_UNSET) {
+ mLastLeftMargin = newLeftMargin;
+ }
+ if (mLastLeftMargin != newLeftMargin) {
+ marginAnimator(mMoveLeftButton, layoutParams -> layoutParams.leftMargin,
+ (layoutParams, margin) -> layoutParams.leftMargin = margin,
+ mLastLeftMargin, newLeftMargin).start();
+ } else {
+ final LayoutParams leftParams = ((LayoutParams) mMoveLeftButton.getLayoutParams());
+ leftParams.leftMargin = mLastLeftMargin;
+ mMoveLeftButton.setLayoutParams(leftParams);
+ }
+ showItem(mMoveLeftButton);
+ } else {
+ hideItem(mMoveLeftButton);
+ mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ }
+ if (rightAvailableSpace >= mMoveRightButton.getMeasuredWidth()) {
+ int newRightMargin = (horizontalGap - mMoveRightButton.getMeasuredWidth()) / 2;
+ if (mLastRightMargin == PROPERTY_VALUE_UNSET) {
+ mLastRightMargin = newRightMargin;
+ }
+ if (mLastRightMargin != newRightMargin) {
+ marginAnimator(mMoveRightButton, layoutParams -> layoutParams.rightMargin,
+ (layoutParams, margin) -> layoutParams.rightMargin = margin,
+ mLastRightMargin, newRightMargin).start();
+ } else {
+ final LayoutParams rightParams =
+ ((LayoutParams) mMoveRightButton.getLayoutParams());
+ rightParams.rightMargin = mLastRightMargin;
+ mMoveRightButton.setLayoutParams(rightParams);
+ }
+ showItem(mMoveRightButton);
+ } else {
+ hideItem(mMoveRightButton);
+ mLastRightMargin = PROPERTY_VALUE_UNSET;
+ }
+ }
+
+ private void handleLetterboxVerticalPosition(int availableHeight,
+ int letterboxVerticalPosition) {
+ hideItem(mMoveLeftButton);
+ hideItem(mMoveRightButton);
+ mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ mLastRightMargin = PROPERTY_VALUE_UNSET;
+ // We calculate the available space on the left and right
+ final int verticalGap = availableHeight / 2;
+ final int topAvailableSpace = letterboxVerticalPosition * verticalGap;
+ final int bottomAvailableSpace = availableHeight - topAvailableSpace;
+ if (topAvailableSpace >= mMoveUpButton.getMeasuredHeight()) {
+ int newTopMargin = (verticalGap - mMoveUpButton.getMeasuredHeight()) / 2;
+ if (mLastTopMargin == PROPERTY_VALUE_UNSET) {
+ mLastTopMargin = newTopMargin;
+ }
+ if (mLastTopMargin != newTopMargin) {
+ marginAnimator(mMoveUpButton, layoutParams -> layoutParams.topMargin,
+ (layoutParams, margin) -> layoutParams.topMargin = margin,
+ mLastTopMargin, newTopMargin).start();
+ } else {
+ final LayoutParams topParams = ((LayoutParams) mMoveUpButton.getLayoutParams());
+ topParams.topMargin = mLastTopMargin;
+ mMoveUpButton.setLayoutParams(topParams);
+ }
+ showItem(mMoveUpButton);
+ } else {
+ hideItem(mMoveUpButton);
+ mLastTopMargin = PROPERTY_VALUE_UNSET;
+ }
+ if (bottomAvailableSpace >= mMoveDownButton.getMeasuredHeight()) {
+ int newBottomMargin = (verticalGap - mMoveDownButton.getMeasuredHeight()) / 2;
+ if (mLastBottomMargin == PROPERTY_VALUE_UNSET) {
+ mLastBottomMargin = newBottomMargin;
+ }
+ if (mLastBottomMargin != newBottomMargin) {
+ marginAnimator(mMoveDownButton, layoutParams -> layoutParams.bottomMargin,
+ (layoutParams, margin) -> layoutParams.bottomMargin = margin,
+ mLastBottomMargin, newBottomMargin).start();
+ } else {
+ final LayoutParams bottomParams =
+ ((LayoutParams) mMoveDownButton.getLayoutParams());
+ bottomParams.bottomMargin = mLastBottomMargin;
+ mMoveDownButton.setLayoutParams(bottomParams);
+ }
+ showItem(mMoveDownButton);
+ } else {
+ hideItem(mMoveDownButton);
+ mLastBottomMargin = PROPERTY_VALUE_UNSET;
+ }
+ }
+
+ private void showItem(View view) {
+ view.setVisibility(View.VISIBLE);
+ ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+ ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE);
+ fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS);
+ fadeIn.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.VISIBLE);
+ }
+ });
+ fadeIn.start();
+ }
+
+ private void hideItem(View view) {
+ ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+ ALPHA_FULL_OPAQUE, ALPHA_FULL_TRANSPARENT);
+ fadeOut.setDuration(VISIBILITY_ANIMATION_DURATION_MS);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.INVISIBLE);
+ }
+ });
+ fadeOut.start();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..b6e396d12512424e9b21b3cffca44fe1de923193
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Window manager for the reachability education
+ */
+class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
+
+ /**
+ * The Compat UI should be below the Letterbox Education.
+ */
+ private static final int Z_ORDER = LetterboxEduWindowManager.Z_ORDER - 1;
+
+ // The time to wait before hiding the education
+ private static final long DISAPPEAR_DELAY_MS = 4000L;
+
+ private static final int REACHABILITY_LEFT_OR_UP_POSITION = 0;
+ private static final int REACHABILITY_RIGHT_OR_BOTTOM_POSITION = 2;
+
+ private final CompatUIConfiguration mCompatUIConfiguration;
+
+ private final ShellExecutor mMainExecutor;
+
+ @NonNull
+ private TaskInfo mTaskInfo;
+
+ private boolean mIsActivityLetterboxed;
+
+ private int mLetterboxVerticalPosition;
+
+ private int mLetterboxHorizontalPosition;
+
+ private int mTopActivityLetterboxWidth;
+
+ private int mTopActivityLetterboxHeight;
+
+ private long mNextHideTime = -1L;
+
+ private boolean mForceUpdate = false;
+
+ // We decided to force the visualization of the double-tap animated icons every time the user
+ // double-taps.
+ private boolean mHasUserDoubleTapped;
+
+ // When the size of the letterboxed app changes and the icons are visible
+ // we need to animate them.
+ private boolean mHasLetterboxSizeChanged;
+
+ @Nullable
+ @VisibleForTesting
+ ReachabilityEduLayout mLayout;
+
+ ReachabilityEduWindowManager(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue,
+ ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
+ CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) {
+ super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mTaskInfo = taskInfo;
+ mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
+ mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
+ mLetterboxHorizontalPosition = taskInfo.topActivityLetterboxHorizontalPosition;
+ mTopActivityLetterboxWidth = taskInfo.topActivityLetterboxWidth;
+ mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
+ mCompatUIConfiguration = compatUIConfiguration;
+ mMainExecutor = mainExecutor;
+ }
+
+ @Override
+ protected int getZOrder() {
+ return Z_ORDER;
+ }
+
+ @Override
+ protected @Nullable View getLayout() {
+ return mLayout;
+ }
+
+ @Override
+ protected void removeLayout() {
+ mLayout = null;
+ }
+
+ @Override
+ protected boolean eligibleToShowLayout() {
+ return mIsActivityLetterboxed
+ && (mLetterboxVerticalPosition != -1 || mLetterboxHorizontalPosition != -1);
+ }
+
+ @Override
+ protected View createLayout() {
+ mLayout = inflateLayout();
+ mLayout.inject(this);
+
+ updateVisibilityOfViews();
+
+ return mLayout;
+ }
+
+ @VisibleForTesting
+ ReachabilityEduLayout inflateLayout() {
+ return (ReachabilityEduLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.reachability_ui_layout, null);
+ }
+
+ @Override
+ public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
+ boolean canShow) {
+ mTaskInfo = taskInfo;
+ final boolean prevIsActivityLetterboxed = mIsActivityLetterboxed;
+ final int prevLetterboxVerticalPosition = mLetterboxVerticalPosition;
+ final int prevLetterboxHorizontalPosition = mLetterboxHorizontalPosition;
+ final int prevTopActivityLetterboxWidth = mTopActivityLetterboxWidth;
+ final int prevTopActivityLetterboxHeight = mTopActivityLetterboxHeight;
+ mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
+ mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
+ mLetterboxHorizontalPosition = taskInfo.topActivityLetterboxHorizontalPosition;
+ mTopActivityLetterboxWidth = taskInfo.topActivityLetterboxWidth;
+ mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
+ mHasUserDoubleTapped = taskInfo.isFromLetterboxDoubleTap;
+
+ if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
+ return false;
+ }
+
+ mHasLetterboxSizeChanged = prevTopActivityLetterboxWidth != mTopActivityLetterboxWidth
+ || prevTopActivityLetterboxHeight != mTopActivityLetterboxHeight;
+
+ if (mHasUserDoubleTapped || prevIsActivityLetterboxed != mIsActivityLetterboxed
+ || prevLetterboxVerticalPosition != mLetterboxVerticalPosition
+ || prevLetterboxHorizontalPosition != mLetterboxHorizontalPosition
+ || prevTopActivityLetterboxWidth != mTopActivityLetterboxWidth
+ || prevTopActivityLetterboxHeight != mTopActivityLetterboxHeight) {
+ updateVisibilityOfViews();
+ }
+
+ return true;
+ }
+
+ void forceUpdate(boolean forceUpdate) {
+ mForceUpdate = forceUpdate;
+ }
+
+ @Override
+ protected void onParentBoundsChanged() {
+ if (mLayout == null) {
+ return;
+ }
+ // Both the layout dimensions and dialog margins depend on the parent bounds.
+ WindowManager.LayoutParams windowLayoutParams = getWindowLayoutParams();
+ mLayout.setLayoutParams(windowLayoutParams);
+ relayout(windowLayoutParams);
+ }
+
+ /** Gets the layout params. */
+ protected WindowManager.LayoutParams getWindowLayoutParams() {
+ View layout = getLayout();
+ if (layout == null) {
+ return new WindowManager.LayoutParams();
+ }
+ // Measure how big the hint is since its size depends on the text size.
+ final Rect taskBounds = getTaskBounds();
+ layout.measure(View.MeasureSpec.makeMeasureSpec(taskBounds.width(),
+ View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(taskBounds.height(),
+ View.MeasureSpec.EXACTLY));
+ return getWindowLayoutParams(layout.getMeasuredWidth(), layout.getMeasuredHeight());
+ }
+
+ /**
+ * @return Flags to use for the WindowManager layout
+ */
+ @Override
+ protected int getWindowManagerLayoutParamsFlags() {
+ return FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE;
+ }
+
+ @Override
+ @VisibleForTesting
+ public void updateSurfacePosition() {
+ if (mLayout == null) {
+ return;
+ }
+ updateSurfacePosition(0, 0);
+ }
+
+ void updateHideTime() {
+ mNextHideTime = SystemClock.uptimeMillis() + DISAPPEAR_DELAY_MS;
+ }
+
+ private void updateVisibilityOfViews() {
+ if (mLayout == null) {
+ return;
+ }
+
+ final boolean eligibleForDisplayHorizontalEducation = mForceUpdate
+ || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(mTaskInfo)
+ || (mHasUserDoubleTapped
+ && (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION
+ || mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
+ final boolean eligibleForDisplayVerticalEducation = mForceUpdate
+ || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(mTaskInfo)
+ || (mHasUserDoubleTapped
+ && (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION
+ || mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
+
+ if (mIsActivityLetterboxed && (eligibleForDisplayHorizontalEducation
+ || eligibleForDisplayVerticalEducation)) {
+ int availableWidth = getTaskBounds().width() - mTopActivityLetterboxWidth;
+ int availableHeight = getTaskBounds().height() - mTopActivityLetterboxHeight;
+ mLayout.handleVisibility(eligibleForDisplayHorizontalEducation,
+ eligibleForDisplayVerticalEducation,
+ mLetterboxVerticalPosition, mLetterboxHorizontalPosition, availableWidth,
+ availableHeight, mCompatUIConfiguration, mTaskInfo);
+ if (!mHasLetterboxSizeChanged) {
+ updateHideTime();
+ mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS);
+ }
+ mHasUserDoubleTapped = false;
+ } else {
+ mLayout.hideAllImmediately();
+ }
+ }
+
+ private void hideReachability() {
+ if (mLayout == null || !shouldHideEducation()) {
+ return;
+ }
+ mLayout.hideAllImmediately();
+ }
+
+ private boolean shouldHideEducation() {
+ return SystemClock.uptimeMillis() >= mNextHideTime;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java
new file mode 100644
index 0000000000000000000000000000000000000000..c53e6389331a0321926a70359e29c33b2f04ac51
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.wm.shell.R;
+
+import java.util.function.Consumer;
+
+/**
+ * Container for a SCM restart confirmation dialog and background dim.
+ */
+public class RestartDialogLayout extends ConstraintLayout implements DialogContainerSupplier {
+
+ private View mDialogContainer;
+ private TextView mDialogTitle;
+ private Drawable mBackgroundDim;
+
+ public RestartDialogLayout(Context context) {
+ this(context, null);
+ }
+
+ public RestartDialogLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public View getDialogContainerView() {
+ return mDialogContainer;
+ }
+
+ TextView getDialogTitle() {
+ return mDialogTitle;
+ }
+
+ @Override
+ public Drawable getBackgroundDimDrawable() {
+ return mBackgroundDim;
+ }
+
+ /**
+ * Register a callback for the dismiss button and background dim.
+ *
+ * @param callback The callback to register or null if all on click listeners should be removed.
+ */
+ void setDismissOnClickListener(@Nullable Runnable callback) {
+ final OnClickListener listener = callback == null ? null : view -> callback.run();
+ findViewById(R.id.letterbox_restart_dialog_dismiss_button).setOnClickListener(listener);
+ }
+
+ /**
+ * Register a callback for the restart button
+ *
+ * @param callback The callback to register or null if all on click listeners should be removed.
+ */
+ void setRestartOnClickListener(@Nullable Consumer callback) {
+ final CheckBox dontShowAgainCheckbox = findViewById(R.id.letterbox_restart_dialog_checkbox);
+ final OnClickListener listener = callback == null ? null : view -> callback.accept(
+ dontShowAgainCheckbox.isChecked());
+ findViewById(R.id.letterbox_restart_dialog_restart_button).setOnClickListener(listener);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDialogContainer = findViewById(R.id.letterbox_restart_dialog_container);
+ mDialogTitle = findViewById(R.id.letterbox_restart_dialog_title);
+ mBackgroundDim = getBackground().mutate();
+ // Set the alpha of the background dim to 0 for enter animation.
+ mBackgroundDim.setAlpha(0);
+ // We add a no-op on-click listener to the dialog container so that clicks on it won't
+ // propagate to the listener of the layout (which represents the background dim).
+ mDialogContainer.setOnClickListener(view -> {});
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..aab123a843ea3472bdff5fc10ee17cc2046c9534
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.provider.Settings;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.function.Consumer;
+
+/**
+ * Window manager for the Restart Dialog.
+ *
+ * TODO(b/263484314): Create abstraction of RestartDialogWindowManager and LetterboxEduWindowManager
+ */
+class RestartDialogWindowManager extends CompatUIWindowManagerAbstract {
+
+ /**
+ * The restart dialog should be the topmost child of the Task in case there can be more
+ * than one child.
+ */
+ private static final int Z_ORDER = Integer.MAX_VALUE;
+
+ private final DialogAnimationController mAnimationController;
+
+ private final Transitions mTransitions;
+
+ // Remember the last reported state in case visibility changes due to keyguard or IME updates.
+ private boolean mRequestRestartDialog;
+
+ private final Consumer> mOnDismissCallback;
+
+ private final Consumer> mOnRestartCallback;
+
+ private final CompatUIConfiguration mCompatUIConfiguration;
+
+ /**
+ * The vertical margin between the dialog container and the task stable bounds (excluding
+ * insets).
+ */
+ private final int mDialogVerticalMargin;
+
+ @NonNull
+ private TaskInfo mTaskInfo;
+
+ @Nullable
+ @VisibleForTesting
+ RestartDialogLayout mLayout;
+
+ RestartDialogWindowManager(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+ DisplayLayout displayLayout, Transitions transitions,
+ Consumer> onRestartCallback,
+ Consumer> onDismissCallback,
+ CompatUIConfiguration compatUIConfiguration) {
+ this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
+ onRestartCallback, onDismissCallback,
+ new DialogAnimationController<>(context, "RestartDialogWindowManager"),
+ compatUIConfiguration);
+ }
+
+ @VisibleForTesting
+ RestartDialogWindowManager(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+ DisplayLayout displayLayout, Transitions transitions,
+ Consumer> onRestartCallback,
+ Consumer> onDismissCallback,
+ DialogAnimationController animationController,
+ CompatUIConfiguration compatUIConfiguration) {
+ super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mTaskInfo = taskInfo;
+ mTransitions = transitions;
+ mOnDismissCallback = onDismissCallback;
+ mOnRestartCallback = onRestartCallback;
+ mAnimationController = animationController;
+ mDialogVerticalMargin = (int) mContext.getResources().getDimension(
+ R.dimen.letterbox_restart_dialog_margin);
+ mCompatUIConfiguration = compatUIConfiguration;
+ }
+
+ @Override
+ protected int getZOrder() {
+ return Z_ORDER;
+ }
+
+ @Override
+ @Nullable
+ protected View getLayout() {
+ return mLayout;
+ }
+
+ @Override
+ protected void removeLayout() {
+ mLayout = null;
+ }
+
+ @Override
+ protected boolean eligibleToShowLayout() {
+ // We don't show this dialog if the user has explicitly selected so clicking on a checkbox.
+ return mRequestRestartDialog && !isTaskbarEduShowing() && (mLayout != null
+ || mCompatUIConfiguration.shouldShowRestartDialogAgain(mTaskInfo));
+ }
+
+ @Override
+ protected View createLayout() {
+ mLayout = inflateLayout();
+ updateDialogMargins();
+
+ // startEnterAnimation will be called immediately if shell-transitions are disabled.
+ mTransitions.runOnIdle(this::startEnterAnimation);
+
+ return mLayout;
+ }
+
+ void setRequestRestartDialog(boolean enabled) {
+ mRequestRestartDialog = enabled;
+ }
+
+ @Override
+ public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
+ boolean canShow) {
+ mTaskInfo = taskInfo;
+ return super.updateCompatInfo(taskInfo, taskListener, canShow);
+ }
+
+ boolean needsToBeRecreated(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+ return taskInfo.configuration.uiMode != mTaskInfo.configuration.uiMode
+ || !getTaskListener().equals(taskListener);
+ }
+
+ private void updateDialogMargins() {
+ if (mLayout == null) {
+ return;
+ }
+ final View dialogContainer = mLayout.getDialogContainerView();
+ ViewGroup.MarginLayoutParams marginParams =
+ (ViewGroup.MarginLayoutParams) dialogContainer.getLayoutParams();
+
+ final Rect taskBounds = getTaskBounds();
+ final Rect taskStableBounds = getTaskStableBounds();
+
+ marginParams.topMargin = taskStableBounds.top - taskBounds.top + mDialogVerticalMargin;
+ marginParams.bottomMargin =
+ taskBounds.bottom - taskStableBounds.bottom + mDialogVerticalMargin;
+ dialogContainer.setLayoutParams(marginParams);
+ }
+
+ private RestartDialogLayout inflateLayout() {
+ return (RestartDialogLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.letterbox_restart_dialog_layout, null);
+ }
+
+ private void startEnterAnimation() {
+ if (mLayout == null) {
+ // Dialog has already been released.
+ return;
+ }
+ mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
+ this::onDialogEnterAnimationEnded);
+ }
+
+ private void onDialogEnterAnimationEnded() {
+ if (mLayout == null) {
+ // Dialog has already been released.
+ return;
+ }
+ mLayout.setDismissOnClickListener(this::onDismiss);
+ mLayout.setRestartOnClickListener(dontShowAgain -> {
+ if (mLayout != null) {
+ mLayout.setDismissOnClickListener(null);
+ mAnimationController.startExitAnimation(mLayout, () -> {
+ release();
+ });
+ }
+ if (dontShowAgain) {
+ mCompatUIConfiguration.setDontShowRestartDialogAgain(mTaskInfo);
+ }
+ mOnRestartCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
+ });
+ // Focus on the dialog title for accessibility.
+ mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+
+ private void onDismiss() {
+ if (mLayout == null) {
+ return;
+ }
+
+ mLayout.setDismissOnClickListener(null);
+ mAnimationController.startExitAnimation(mLayout, () -> {
+ release();
+ mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
+ });
+ }
+
+ @Override
+ public void release() {
+ mAnimationController.cancelAnimation();
+ super.release();
+ }
+
+ @Override
+ protected void onParentBoundsChanged() {
+ if (mLayout == null) {
+ return;
+ }
+ // Both the layout dimensions and dialog margins depend on the parent bounds.
+ WindowManager.LayoutParams windowLayoutParams = getWindowLayoutParams();
+ mLayout.setLayoutParams(windowLayoutParams);
+ updateDialogMargins();
+ relayout(windowLayoutParams);
+ }
+
+ @Override
+ protected void updateSurfacePosition() {
+ // Nothing to do, since the position of the surface is fixed to the top left corner (0,0)
+ // of the task (parent surface), which is the default position of a surface.
+ }
+
+ @Override
+ protected WindowManager.LayoutParams getWindowLayoutParams() {
+ final Rect taskBounds = getTaskBounds();
+ return getWindowLayoutParams(/* width= */ taskBounds.width(), /* height= */
+ taskBounds.height());
+ }
+
+ @VisibleForTesting
+ boolean isTaskbarEduShowing() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ LAUNCHER_TASKBAR_EDUCATION_SHOWING, /* def= */ 0) == 1;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 8022e9b1cd810db9b8e11f86760d34b6927df26e..b0756a0afe5dc72180813c4ad1b8da25e8cd1122 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -39,6 +39,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipBoundsController;
import com.android.wm.shell.pip.tv.TvPipBoundsState;
@@ -69,6 +70,7 @@ public abstract class TvPipModule {
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -88,6 +90,7 @@ public abstract class TvPipModule {
shellInit,
shellController,
tvPipBoundsState,
+ pipSizeSpecHandler,
tvPipBoundsAlgorithm,
tvPipBoundsController,
pipAppOpsListener,
@@ -127,14 +130,23 @@ public abstract class TvPipModule {
@WMSingleton
@Provides
static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
- TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
- return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm);
+ TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+ PipSizeSpecHandler pipSizeSpecHandler) {
+ return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
+ pipSizeSpecHandler);
}
@WMSingleton
@Provides
- static TvPipBoundsState provideTvPipBoundsState(Context context) {
- return new TvPipBoundsState(context);
+ static TvPipBoundsState provideTvPipBoundsState(Context context,
+ PipSizeSpecHandler pipSizeSpecHandler) {
+ return new TvPipBoundsState(context, pipSizeSpecHandler);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
+ return new PipSizeSpecHandler(context);
}
// Handler needed for loadDrawableAsync() in PipControlsViewController
@@ -194,6 +206,7 @@ public abstract class TvPipModule {
TvPipMenuController tvPipMenuController,
SyncTransactionQueue syncTransactionQueue,
TvPipBoundsState tvPipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipTransitionState pipTransitionState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
@@ -205,10 +218,11 @@ public abstract class TvPipModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new TvPipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, tvPipBoundsState, tvPipBoundsAlgorithm,
- tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
- pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional,
- displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipSizeSpecHandler,
+ tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
+ pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ splitScreenControllerOptional, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
}
@WMSingleton
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 09f5cf1d31e48abea42645edc95486b5f36a420e..ef21c7e9ec0c21caae9faeef082a482ad64aaaf9 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
@@ -41,6 +41,7 @@ import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.common.DevicePostureController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -50,13 +51,16 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
@@ -156,6 +160,28 @@ public abstract class WMShellBaseModule {
return new DisplayLayout();
}
+ @WMSingleton
+ @Provides
+ static DevicePostureController provideDevicePostureController(
+ Context context,
+ ShellInit shellInit,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return new DevicePostureController(context, shellInit, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static TabletopModeController provideTabletopModeController(
+ Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new TabletopModeController(
+ context, shellInit, postureController, displayController, mainExecutor);
+ }
+
@WMSingleton
@Provides
static DragAndDropController provideDragAndDropController(Context context,
@@ -196,10 +222,11 @@ public abstract class WMShellBaseModule {
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor, Lazy transitionsLazy,
- DockStateReader dockStateReader) {
+ DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
+ CompatUIShellCommandHandler compatUIShellCommandHandler) {
return new CompatUIController(context, shellInit, shellController, displayController,
displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
- dockStateReader);
+ dockStateReader, compatUIConfiguration, compatUIShellCommandHandler);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index d3b9fa5e628d69d78a3c333dd7d6c60f8c41e058..8f1e074ec1834eae4bc314a6acac96f22022801f 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
@@ -44,11 +44,13 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
@@ -76,6 +78,7 @@ import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -93,6 +96,7 @@ import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition;
import com.android.wm.shell.unfold.qualifier.UnfoldTransition;
+import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -191,8 +195,10 @@ public abstract class WMShellModule {
DisplayController displayController,
SyncTransactionQueue syncQueue,
Optional desktopModeController,
- Optional desktopTasksController) {
- return new DesktopModeWindowDecorViewModel(
+ Optional desktopTasksController,
+ Optional splitScreenController) {
+ if (DesktopModeStatus.isAnyEnabled()) {
+ return new DesktopModeWindowDecorViewModel(
context,
mainHandler,
mainChoreographer,
@@ -200,7 +206,16 @@ public abstract class WMShellModule {
displayController,
syncQueue,
desktopModeController,
- desktopTasksController);
+ desktopTasksController,
+ splitScreenController);
+ }
+ return new CaptionWindowDecorViewModel(
+ context,
+ mainHandler,
+ mainChoreographer,
+ taskOrganizer,
+ displayController,
+ syncQueue);
}
//
@@ -327,6 +342,7 @@ public abstract class WMShellModule {
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -338,22 +354,24 @@ public abstract class WMShellModule {
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController pipTabletopController,
Optional oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(
context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
- phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler,
- pipTransitionController, windowManagerShellWrapper, taskStackListener,
- pipParamsChangedForwarder, displayInsetsController, oneHandedController,
- mainExecutor));
+ pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipMotionHelper,
+ pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
+ pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
+ taskStackListener, pipParamsChangedForwarder, displayInsetsController,
+ pipTabletopController, oneHandedController, mainExecutor));
}
@WMSingleton
@Provides
- static PipBoundsState providePipBoundsState(Context context) {
- return new PipBoundsState(context);
+ static PipBoundsState providePipBoundsState(Context context,
+ PipSizeSpecHandler pipSizeSpecHandler) {
+ return new PipBoundsState(context, pipSizeSpecHandler);
}
@WMSingleton
@@ -368,13 +386,20 @@ public abstract class WMShellModule {
return new PhonePipKeepClearAlgorithm(context);
}
+ @WMSingleton
+ @Provides
+ static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
+ return new PipSizeSpecHandler(context);
+ }
+
@WMSingleton
@Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
- PhonePipKeepClearAlgorithm pipKeepClearAlgorithm) {
+ PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipSizeSpecHandler pipSizeSpecHandler) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
- pipKeepClearAlgorithm);
+ pipKeepClearAlgorithm, pipSizeSpecHandler);
}
// Handler is used by Icon.loadDrawableAsync
@@ -398,13 +423,14 @@ public abstract class WMShellModule {
PhonePipMenuController menuPhoneController,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
- pipBoundsState, pipTaskOrganizer, pipMotionHelper,
+ pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper,
floatingContentCoordinator, pipUiEventLogger, mainExecutor);
}
@@ -420,6 +446,7 @@ public abstract class WMShellModule {
SyncTransactionQueue syncTransactionQueue,
PipTransitionState pipTransitionState,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipMenuController menuPhoneController,
PipAnimationController pipAnimationController,
@@ -431,10 +458,11 @@ public abstract class WMShellModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
- menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
- pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional,
- displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
+ pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
+ pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ splitScreenControllerOptional, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
}
@WMSingleton
@@ -449,13 +477,14 @@ public abstract class WMShellModule {
static PipTransitionController providePipTransitionController(Context context,
ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipTransitionState pipTransitionState,
- PhonePipMenuController pipMenuController,
+ PipBoundsState pipBoundsState, PipSizeSpecHandler pipSizeSpecHandler,
+ PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional splitScreenOptional) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, pipTransitionState, pipMenuController, pipBoundsAlgorithm,
- pipAnimationController, pipSurfaceTransactionHelper, splitScreenOptional);
+ pipBoundsState, pipSizeSpecHandler, pipTransitionState, pipMenuController,
+ pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+ splitScreenOptional);
}
@WMSingleton
@@ -533,16 +562,18 @@ public abstract class WMShellModule {
static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator(
Context context,
UnfoldBackgroundController unfoldBackgroundController,
+ ShellController shellController,
DisplayInsetsController displayInsetsController
) {
return new FullscreenUnfoldTaskAnimator(context, unfoldBackgroundController,
- displayInsetsController);
+ shellController, displayInsetsController);
}
@Provides
static SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimatorBase(
Context context,
UnfoldBackgroundController backgroundController,
+ ShellController shellController,
@ShellMainThread ShellExecutor executor,
Lazy> splitScreenOptional,
DisplayInsetsController displayInsetsController
@@ -552,7 +583,7 @@ public abstract class WMShellModule {
// controller directly once we refactor ShellTaskOrganizer to not depend on the unfold
// animation controller directly.
return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional,
- backgroundController, displayInsetsController);
+ shellController, backgroundController, displayInsetsController);
}
@WMSingleton
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 f5f3573252ec3f2f56f56b98108845e9992231ca..bc8171058776d22ec4f7b1629d75c84219c0978f 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
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -36,6 +37,7 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
@@ -251,18 +253,26 @@ public class DesktopModeController implements RemoteCallable activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
@@ -275,16 +285,11 @@ public class DesktopModeController implements RemoteCallable mDesktopModeTaskRepository.isVisibleTask(info.taskId));
- if (allActiveTasksAreVisible) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "bringDesktopAppsToFront: active tasks are already in front, skipping.");
- return wct;
- }
+ moveHomeTaskToFront(wct);
+
ProtoLog.d(WM_SHELL_DESKTOP_MODE,
"bringDesktopAppsToFront: reordering all active tasks to the front");
final List allTasksInZOrder =
@@ -295,7 +300,15 @@ public class DesktopModeController implements RemoteCallable result[0] = controller.getVisibleTaskCount(),
+ true /* blocking */
+ );
+ return result[0];
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 600ccc17ecaaf7a0b444c95e382b1e8e439b3b4d..47342c9f21ee6d04a9ae2e5c0000ce9589d1cb91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -142,6 +142,13 @@ class DesktopModeTaskRepository {
}
}
+ /**
+ * Get number of tasks that are marked as visible
+ */
+ fun getVisibleTaskCount(): Int {
+ return visibleTasks.size
+ }
+
/**
* Add (or move if it already exists) the task to the top of the ordered list.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 3341470efe4df081fe482d1d8fe810b4c467f8a4..31c5e33f21e32f605f0963c182401e20db17c534 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -25,12 +25,15 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
import android.os.IBinder
+import android.os.SystemProperties
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
+import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.protolog.common.ProtoLog
@@ -84,19 +87,24 @@ class DesktopTasksController(
fun showDesktopApps() {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
val wct = WindowContainerTransaction()
-
bringDesktopAppsToFront(wct)
// Execute transaction if there are pending operations
if (!wct.isEmpty) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
+ // TODO(b/268662477): add animation for the transition
+ transitions.startTransition(TRANSIT_NONE, wct, null /* handler */)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
}
+ /** Get number of tasks that are marked as visible */
+ fun getVisibleTaskCount(): Int {
+ return desktopModeTaskRepository.getVisibleTaskCount()
+ }
+
/** Move a task with given `taskId` to desktop */
fun moveToDesktop(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) }
@@ -109,10 +117,7 @@ class DesktopTasksController(
val wct = WindowContainerTransaction()
// Bring other apps to front first
bringDesktopAppsToFront(wct)
-
- wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FREEFORM)
- wct.reorder(task.getToken(), true /* onTop */)
-
+ addMoveToDesktopChanges(wct, task.token)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -130,8 +135,7 @@ class DesktopTasksController(
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
val wct = WindowContainerTransaction()
- wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FULLSCREEN)
- wct.setBounds(task.getToken(), null)
+ addMoveToFullscreenChanges(wct, task.token)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -151,18 +155,8 @@ class DesktopTasksController(
}
private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
- val activeTasks = desktopModeTaskRepository.getActiveTasks()
-
- // Skip if all tasks are already visible
- if (activeTasks.isNotEmpty() && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "bringDesktopAppsToFront: active tasks are already in front, skipping."
- )
- return
- }
-
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront")
+ val activeTasks = desktopModeTaskRepository.getActiveTasks()
// First move home to front and then other tasks on top of it
moveHomeTaskToFront(wct)
@@ -238,8 +232,8 @@ class DesktopTasksController(
" taskId=%d",
task.taskId
)
- return WindowContainerTransaction().apply {
- setWindowingMode(task.token, WINDOWING_MODE_FREEFORM)
+ return WindowContainerTransaction().also { wct ->
+ addMoveToDesktopChanges(wct, task.token)
}
}
}
@@ -255,15 +249,44 @@ class DesktopTasksController(
" taskId=%d",
task.taskId
)
- return WindowContainerTransaction().apply {
- setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN)
- setBounds(task.token, null)
+ return WindowContainerTransaction().also { wct ->
+ addMoveToFullscreenChanges(wct, task.token)
}
}
}
return null
}
+ private fun addMoveToDesktopChanges(
+ wct: WindowContainerTransaction,
+ token: WindowContainerToken
+ ) {
+ wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM)
+ wct.reorder(token, true /* onTop */)
+ if (isDesktopDensityOverrideSet()) {
+ wct.setDensityDpi(token, getDesktopDensityDpi())
+ }
+ }
+
+ private fun addMoveToFullscreenChanges(
+ wct: WindowContainerTransaction,
+ token: WindowContainerToken
+ ) {
+ wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
+ wct.setBounds(token, null)
+ if (isDesktopDensityOverrideSet()) {
+ wct.setDensityDpi(token, getFullscreenDensityDpi())
+ }
+ }
+
+ private fun getFullscreenDensityDpi(): Int {
+ return context.resources.displayMetrics.densityDpi
+ }
+
+ private fun getDesktopDensityDpi(): Int {
+ return DESKTOP_DENSITY_OVERRIDE
+ }
+
/** Creates a new instance of the external interface to pass to another process. */
private fun createExternalInterface(): ExternalInterfaceBinder {
return IDesktopModeImpl(this)
@@ -310,5 +333,30 @@ class DesktopTasksController(
Consumer(DesktopTasksController::showDesktopApps)
)
}
+
+ override fun getVisibleTaskCount(): Int {
+ val result = IntArray(1)
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "getVisibleTaskCount",
+ { controller -> result[0] = controller.getVisibleTaskCount() },
+ true /* blocking */
+ )
+ return result[0]
+ }
+ }
+
+ companion object {
+ private val DESKTOP_DENSITY_OVERRIDE =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
+ private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
+
+ /**
+ * Check if desktop density override is enabled
+ */
+ @JvmStatic
+ fun isDesktopDensityOverrideSet(): Boolean {
+ return DESKTOP_DENSITY_OVERRIDE in DESKTOP_DENSITY_ALLOWED_RANGE
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 5042bd6f2d6596f74fb2b1bc99162d2f60e850ea..d0739e14675fbbfc874e70d67187912f897b934d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -23,4 +23,7 @@ interface IDesktopMode {
/** Show apps on the desktop */
void showDesktopApps();
+
+ /** Get count of visible desktop tasks */
+ int getVisibleTaskCount();
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java
new file mode 100644
index 0000000000000000000000000000000000000000..20da54efd286ecfd2f32cd1f1a9ca42d098d3a92
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+/** Constants that can be used by both Shell and other users of the library, e.g. Launcher */
+public class DragAndDropConstants {
+
+ /**
+ * An Intent extra that Launcher can use to specify a region of the screen where Shell should
+ * ignore drag events.
+ */
+ public static final String EXTRA_DISALLOW_HIT_REGION = "DISALLOW_HIT_REGION";
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index b59fe1818780e345ed0dfb850e56d8e7b3bc7ec8..4cfaae6e51c727b8c6b8f7949bae813cdd325dda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -36,6 +36,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_U
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import android.content.ClipDescription;
+import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -58,9 +59,9 @@ import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -70,7 +71,7 @@ import java.util.ArrayList;
* Handles the global drag and drop handling for the Shell.
*/
public class DragAndDropController implements DisplayController.OnDisplaysChangedListener,
- View.OnDragListener, ConfigurationChangeListener {
+ View.OnDragListener, ComponentCallbacks2 {
private static final String TAG = DragAndDropController.class.getSimpleName();
@@ -119,7 +120,6 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
mMainExecutor.executeDelayed(() -> {
mDisplayController.addDisplayWindowListener(this);
}, 0);
- mShellController.addConfigurationChangeListener(this);
}
/**
@@ -180,6 +180,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
try {
wm.addView(rootView, layoutParams);
addDisplayDropTarget(displayId, context, wm, rootView, dragLayout);
+ context.registerComponentCallbacks(this);
} catch (WindowManager.InvalidDisplayException e) {
Slog.w(TAG, "Unable to add view for display id: " + displayId);
}
@@ -209,6 +210,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
if (pd == null) {
return;
}
+ pd.context.unregisterComponentCallbacks(this);
pd.wm.removeViewImmediate(pd.rootView);
mDisplayDropTargets.remove(displayId);
}
@@ -328,18 +330,29 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
return mimeTypes;
}
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
@Override
- public void onThemeChanged() {
- for (int i = 0; i < mDisplayDropTargets.size(); i++) {
- mDisplayDropTargets.get(i).dragLayout.onThemeChange();
- }
+ public void onConfigurationChanged(Configuration newConfig) {
+ mMainExecutor.execute(() -> {
+ for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+ mDisplayDropTargets.get(i).dragLayout.onConfigChanged(newConfig);
+ }
+ });
}
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
@Override
- public void onConfigurationChanged(Configuration newConfig) {
- for (int i = 0; i < mDisplayDropTargets.size(); i++) {
- mDisplayDropTargets.get(i).dragLayout.onConfigChanged(newConfig);
- }
+ public void onTrimMemory(int level) {
+ // Do nothing
+ }
+
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
+ @Override
+ public void onLowMemory() {
+ // Do nothing
}
private static class PerDisplay {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index d93a9012c8f1e8f7cef498da2831fea2033ac5e9..df94b414c092bf9390bf836d78debcb9a04b8b1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -34,6 +34,7 @@ import static android.content.Intent.EXTRA_USER;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
@@ -53,6 +54,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -86,6 +88,7 @@ public class DragAndDropPolicy {
private final Starter mStarter;
private final SplitScreenController mSplitScreen;
private final ArrayList mTargets = new ArrayList<>();
+ private final RectF mDisallowHitRegion = new RectF();
private InstanceId mLoggerSessionId;
private DragSession mSession;
@@ -111,6 +114,12 @@ public class DragAndDropPolicy {
mSession = new DragSession(mActivityTaskManager, displayLayout, data);
// TODO(b/169894807): Also update the session data with task stack changes
mSession.update();
+ RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION);
+ if (disallowHitRegion == null) {
+ mDisallowHitRegion.setEmpty();
+ } else {
+ mDisallowHitRegion.set(disallowHitRegion);
+ }
}
/**
@@ -218,6 +227,9 @@ public class DragAndDropPolicy {
*/
@Nullable
Target getTargetAtLocation(int x, int y) {
+ if (mDisallowHitRegion.contains(x, y)) {
+ return null;
+ }
for (int i = mTargets.size() - 1; i >= 0; i--) {
DragAndDropPolicy.Target t = mTargets.get(i);
if (t.hitRegion.contains(x, y)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 55378a826385d50c846322cd8ee172c014ef66da..fe42822ab6a1acc65bd05d310cc7613527202310 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -18,10 +18,16 @@ package com.android.wm.shell.draganddrop;
import static android.app.StatusBarManager.DISABLE_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
+import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -68,6 +74,7 @@ public class DragLayout extends LinearLayout {
private final SplitScreenController mSplitScreenController;
private final IconProvider mIconProvider;
private final StatusBarManager mStatusBarManager;
+ private final Configuration mLastConfiguration = new Configuration();
private DragAndDropPolicy.Target mCurrentTarget = null;
private DropZoneView mDropZoneView1;
@@ -88,6 +95,7 @@ public class DragLayout extends LinearLayout {
mIconProvider = iconProvider;
mPolicy = new DragAndDropPolicy(context, splitScreenController);
mStatusBarManager = context.getSystemService(StatusBarManager.class);
+ mLastConfiguration.setTo(context.getResources().getConfiguration());
mDisplayMargin = context.getResources().getDimensionPixelSize(
R.dimen.drop_layout_display_margin);
@@ -114,7 +122,7 @@ public class DragLayout extends LinearLayout {
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mInsets = insets.getInsets(Type.systemBars() | Type.displayCutout());
+ mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
recomputeDropTargets();
final int orientation = getResources().getConfiguration().orientation;
@@ -128,11 +136,6 @@ public class DragLayout extends LinearLayout {
return super.onApplyWindowInsets(insets);
}
- public void onThemeChange() {
- mDropZoneView1.onThemeChange();
- mDropZoneView2.onThemeChange();
- }
-
public void onConfigChanged(Configuration newConfig) {
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
&& getOrientation() != HORIZONTAL) {
@@ -143,6 +146,15 @@ public class DragLayout extends LinearLayout {
setOrientation(LinearLayout.VERTICAL);
updateContainerMargins(newConfig.orientation);
}
+
+ final int diff = newConfig.diff(mLastConfiguration);
+ final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0
+ || (diff & CONFIG_UI_MODE) != 0;
+ if (themeChanged) {
+ mDropZoneView1.onThemeChange();
+ mDropZoneView2.onThemeChange();
+ }
+ mLastConfiguration.setTo(newConfig);
}
private void updateContainerMarginsForSingleTask() {
@@ -315,6 +327,25 @@ public class DragLayout extends LinearLayout {
// Switching between targets
mDropZoneView1.animateSwitch();
mDropZoneView2.animateSwitch();
+ // Announce for accessibility.
+ switch (target.type) {
+ case TYPE_SPLIT_LEFT:
+ mDropZoneView1.announceForAccessibility(
+ mContext.getString(R.string.accessibility_split_left));
+ break;
+ case TYPE_SPLIT_RIGHT:
+ mDropZoneView2.announceForAccessibility(
+ mContext.getString(R.string.accessibility_split_right));
+ break;
+ case TYPE_SPLIT_TOP:
+ mDropZoneView1.announceForAccessibility(
+ mContext.getString(R.string.accessibility_split_top));
+ break;
+ case TYPE_SPLIT_BOTTOM:
+ mDropZoneView2.announceForAccessibility(
+ mContext.getString(R.string.accessibility_split_bottom));
+ break;
+ }
}
mCurrentTarget = target;
}
@@ -346,7 +377,9 @@ public class DragLayout extends LinearLayout {
// Start animating the drop UI out with the drag surface
hide(event, dropCompleteCallback);
- hideDragSurface(dragSurface);
+ if (handledDrop) {
+ hideDragSurface(dragSurface);
+ }
return handledDrop;
}
@@ -424,12 +457,10 @@ public class DragLayout extends LinearLayout {
}
private void animateHighlight(DragAndDropPolicy.Target target) {
- if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_LEFT
- || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_TOP) {
+ if (target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP) {
mDropZoneView1.setShowingHighlight(true);
mDropZoneView2.setShowingHighlight(false);
- } else if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT
- || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM) {
+ } else if (target.type == TYPE_SPLIT_RIGHT || target.type == TYPE_SPLIT_BOTTOM) {
mDropZoneView1.setShowingHighlight(false);
mDropZoneView2.setShowingHighlight(true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index b9caf62012a61831bf80d0f78b4092a356fcd1a6..d094c229e0f865cc47f323065ed61506a5c574e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -108,13 +108,19 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
}
if (!createdWindowDecor) {
mSyncQueue.runInSync(t -> {
+ if (!leash.isValid()) {
+ // Task vanished before sync completion
+ return;
+ }
// Reset several properties back to fullscreen (PiP, for example, leaves all these
// properties in a bad state).
t.setWindowCrop(leash, null);
t.setPosition(leash, positionInParent.x, positionInParent.y);
t.setAlpha(leash, 1f);
t.setMatrix(leash, 1, 0, 0, 1);
- t.show(leash);
+ if (taskInfo.isVisible) {
+ t.show(leash);
+ }
});
}
}
@@ -134,6 +140,10 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
final Point positionInParent = state.mTaskInfo.positionInParent;
if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
mSyncQueue.runInSync(t -> {
+ if (!state.mLeash.isValid()) {
+ // Task vanished before sync completion
+ return;
+ }
t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index e91987dab972fff918f1b1d067604b9fb9c9cb79..77fe7a869f8e1dc84dff6522a214b6cc4801f76e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -16,10 +16,14 @@
package com.android.wm.shell.kidsmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
@@ -32,6 +36,7 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.view.Display;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -42,6 +47,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -68,7 +74,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
private static final String TAG = "KidsModeTaskOrganizer";
private static final int[] CONTROLLED_ACTIVITY_TYPES =
- {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD};
+ {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_HOME};
private static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
@@ -79,6 +85,12 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
private final DisplayController mDisplayController;
private final DisplayInsetsController mDisplayInsetsController;
+ /**
+ * The value of the {@link R.bool.config_reverseDefaultRotation} property which defines how
+ * {@link Display#getRotation} values are mapped to screen orientations
+ */
+ private final boolean mReverseDefaultRotationEnabled;
+
@VisibleForTesting
ActivityManager.RunningTaskInfo mLaunchRootTask;
@VisibleForTesting
@@ -93,6 +105,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
private KidsModeSettingsObserver mKidsModeSettingsObserver;
private boolean mEnabled;
+ private ActivityManager.RunningTaskInfo mHomeTask;
+
private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -165,6 +179,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mDisplayInsetsController = displayInsetsController;
mKidsModeSettingsObserver = kidsModeSettingsObserver;
shellInit.addInitCallback(this::onInit, this);
+ mReverseDefaultRotationEnabled = context.getResources().getBoolean(
+ R.bool.config_reverseDefaultRotation);
}
public KidsModeTaskOrganizer(
@@ -188,6 +204,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
shellInit.addInitCallback(this::onInit, this);
+ mReverseDefaultRotationEnabled = context.getResources().getBoolean(
+ R.bool.config_reverseDefaultRotation);
}
/**
@@ -219,6 +237,13 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
}
super.onTaskAppeared(taskInfo, leash);
+ // Only allow home to draw under system bars.
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+ final WindowContainerTransaction wct = getWindowContainerTransaction();
+ wct.setBounds(taskInfo.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight));
+ mSyncQueue.queue(wct);
+ mHomeTask = taskInfo;
+ }
mSyncQueue.runInSync(t -> {
// Reset several properties back to fullscreen (PiP, for example, leaves all these
// properties in a bad state).
@@ -237,6 +262,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mLaunchRootTask = taskInfo;
}
+ if (mHomeTask != null && mHomeTask.taskId == taskInfo.taskId
+ && !taskInfo.equals(mHomeTask)) {
+ mHomeTask = taskInfo;
+ }
+
super.onTaskInfoChanged(taskInfo);
}
@@ -259,7 +289,17 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
// Needed since many Kids apps aren't optimised to support both orientations and it will be
// hard for kids to understand the app compat mode.
// TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once possible.
- setIsIgnoreOrientationRequestDisabled(true);
+ if (mReverseDefaultRotationEnabled) {
+ setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
+ /* fromOrientations */
+ new int[]{SCREEN_ORIENTATION_LANDSCAPE, SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
+ /* toOrientations */
+ new int[]{SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
+ SCREEN_ORIENTATION_SENSOR_LANDSCAPE});
+ } else {
+ setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
+ /* fromOrientations */ null, /* toOrientations */ null);
+ }
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
if (displayLayout != null) {
mDisplayWidth = displayLayout.width();
@@ -280,7 +320,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
@VisibleForTesting
void disable() {
- setIsIgnoreOrientationRequestDisabled(false);
+ setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ false,
+ /* fromOrientations */ null, /* toOrientations */ null);
mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY,
mOnInsetsChangedListener);
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
@@ -291,6 +332,13 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
}
mLaunchRootTask = null;
mLaunchRootLeash = null;
+ if (mHomeTask != null && mHomeTask.token != null) {
+ final WindowContainerToken homeToken = mHomeTask.token;
+ final WindowContainerTransaction wct = getWindowContainerTransaction();
+ wct.setBounds(homeToken, null);
+ mSyncQueue.queue(wct);
+ }
+ mHomeTask = null;
unregisterOrganizer();
}
@@ -320,7 +368,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
final SurfaceControl rootLeash = mLaunchRootLeash;
mSyncQueue.runInSync(t -> {
t.setPosition(rootLeash, taskBounds.left, taskBounds.top);
- t.setWindowCrop(rootLeash, taskBounds.width(), taskBounds.height());
+ t.setWindowCrop(rootLeash, mDisplayWidth, mDisplayHeight);
});
}
}
@@ -347,11 +395,12 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
final WindowContainerTransaction wct = getWindowContainerTransaction();
final Rect taskBounds = calculateBounds();
wct.setBounds(mLaunchRootTask.token, taskBounds);
+ wct.setBounds(mHomeTask.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight));
mSyncQueue.queue(wct);
final SurfaceControl finalLeash = mLaunchRootLeash;
mSyncQueue.runInSync(t -> {
t.setPosition(finalLeash, taskBounds.left, taskBounds.top);
- t.setWindowCrop(finalLeash, taskBounds.width(), taskBounds.height());
+ t.setWindowCrop(finalLeash, mDisplayWidth, mDisplayHeight);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 2624ee536b5836c731bf20225265800f07ff9f2e..78de5f3e7a1f3185674238563a94e2f5883a4a18 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -70,4 +70,14 @@ interface IPip {
* Sets the next pip animation type to be the alpha animation.
*/
oneway void setPipAnimationTypeToAlpha() = 5;
+
+ /**
+ * Sets the height and visibility of the Launcher keep clear area.
+ */
+ oneway void setLauncherKeepClearAreaHeight(boolean visible, int height) = 6;
+
+ /**
+ * Sets the app icon size in pixel used by Launcher
+ */
+ oneway void setLauncherAppIconSize(int iconSizePx) = 7;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 6728c00af51b9d2fafb547cb0d05a5394392e4a5..4c53f607a5f836f21ad13942d1c3e5ce0ae754af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -28,6 +28,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.TaskInfo;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -35,6 +36,7 @@ import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.transition.Transitions;
@@ -361,22 +363,28 @@ public class PipAnimationController {
}
void setColorContentOverlay(Context context) {
- final SurfaceControl.Transaction tx =
- mSurfaceControlTransactionFactory.getTransaction();
- if (mContentOverlay != null) {
- mContentOverlay.detach(tx);
- }
- mContentOverlay = new PipContentOverlay.PipColorOverlay(context);
- mContentOverlay.attach(tx, mLeash);
+ reattachContentOverlay(new PipContentOverlay.PipColorOverlay(context));
}
void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
+ reattachContentOverlay(
+ new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint));
+ }
+
+ void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo,
+ int appIconSizePx) {
+ reattachContentOverlay(
+ new PipContentOverlay.PipAppIconOverlay(context, bounds,
+ new IconProvider(context).getIcon(activityInfo), appIconSizePx));
+ }
+
+ private void reattachContentOverlay(PipContentOverlay overlay) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) {
mContentOverlay.detach(tx);
}
- mContentOverlay = new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint);
+ mContentOverlay = overlay;
mContentOverlay.attach(tx, mLeash);
}
@@ -570,8 +578,9 @@ public class PipAnimationController {
final Rect base = getBaseValue();
final Rect start = getStartValue();
final Rect end = getEndValue();
+ Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
if (mContentOverlay != null) {
- mContentOverlay.onAnimationUpdate(tx, fraction);
+ mContentOverlay.onAnimationUpdate(tx, bounds, fraction);
}
if (rotatedEndRect != null) {
// Animate the bounds in a different orientation. It only happens when
@@ -579,7 +588,6 @@ public class PipAnimationController {
applyRotation(tx, leash, fraction, start, end);
return;
}
- Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
float angle = (1.0f - fraction) * startingAngle;
setCurrentValue(bounds);
if (inScaleTransition() || sourceHintRect == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index cd61dbb5b7d1e23516f75add44294f58e5faad9f..24d0b996a3cb22c9f595b4bedbc987251068a7ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -16,24 +16,19 @@
package com.android.wm.shell.pip;
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.Size;
-import android.util.TypedValue;
import android.view.Gravity;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.io.PrintWriter;
@@ -45,33 +40,29 @@ public class PipBoundsAlgorithm {
private static final String TAG = PipBoundsAlgorithm.class.getSimpleName();
private static final float INVALID_SNAP_FRACTION = -1f;
- private final @NonNull PipBoundsState mPipBoundsState;
+ @NonNull private final PipBoundsState mPipBoundsState;
+ @NonNull protected final PipSizeSpecHandler mPipSizeSpecHandler;
private final PipSnapAlgorithm mSnapAlgorithm;
- private final PipKeepClearAlgorithm mPipKeepClearAlgorithm;
+ private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
- private float mDefaultSizePercent;
- private float mMinAspectRatioForMinSize;
- private float mMaxAspectRatioForMinSize;
private float mDefaultAspectRatio;
private float mMinAspectRatio;
private float mMaxAspectRatio;
private int mDefaultStackGravity;
- private int mDefaultMinSize;
- private int mOverridableMinSize;
- protected Point mScreenEdgeInsets;
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm,
- @NonNull PipKeepClearAlgorithm pipKeepClearAlgorithm) {
+ @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
mPipBoundsState = pipBoundsState;
mSnapAlgorithm = pipSnapAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
reloadResources(context);
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which
// triggers a configuration change and the resources to be reloaded.
mPipBoundsState.setAspectRatio(mDefaultAspectRatio);
- mPipBoundsState.setMinEdgeSize(mDefaultMinSize);
}
/**
@@ -83,27 +74,15 @@ public class PipBoundsAlgorithm {
R.dimen.config_pictureInPictureDefaultAspectRatio);
mDefaultStackGravity = res.getInteger(
R.integer.config_defaultPictureInPictureGravity);
- mDefaultMinSize = res.getDimensionPixelSize(
- R.dimen.default_minimal_size_pip_resizable_task);
- mOverridableMinSize = res.getDimensionPixelSize(
- R.dimen.overridable_minimal_size_pip_resizable_task);
final String screenEdgeInsetsDpString = res.getString(
R.string.config_defaultPictureInPictureScreenEdgeInsets);
final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
? Size.parseSize(screenEdgeInsetsDpString)
: null;
- mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
- : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
- dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
mMinAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
mMaxAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
- mDefaultSizePercent = res.getFloat(
- R.dimen.config_pictureInPictureDefaultSizePercent);
- mMaxAspectRatioForMinSize = res.getFloat(
- R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
- mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
}
/**
@@ -180,8 +159,9 @@ public class PipBoundsAlgorithm {
if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
// If either dimension is smaller than the allowed minimum, adjust them
// according to mOverridableMinSize
- return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize),
- Math.max(windowLayout.minHeight, mOverridableMinSize));
+ return new Size(
+ Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()),
+ Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize()));
}
return null;
}
@@ -218,7 +198,7 @@ public class PipBoundsAlgorithm {
/**
* @return whether the given {@param aspectRatio} is valid.
*/
- private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
+ public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
return Float.compare(mMinAspectRatio, aspectRatio) <= 0
&& Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
}
@@ -243,28 +223,13 @@ public class PipBoundsAlgorithm {
final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
getMovementBounds(stackBounds), mPipBoundsState.getStashedState());
- final Size overrideMinSize = mPipBoundsState.getOverrideMinSize();
final Size size;
if (useCurrentMinEdgeSize || useCurrentSize) {
- // The default minimum edge size, or the override min edge size if set.
- final int defaultMinEdgeSize = overrideMinSize == null ? mDefaultMinSize
- : mPipBoundsState.getOverrideMinEdgeSize();
- final int minEdgeSize = useCurrentMinEdgeSize ? mPipBoundsState.getMinEdgeSize()
- : defaultMinEdgeSize;
- // Use the existing size but adjusted to the aspect ratio and min edge size.
- size = getSizeForAspectRatio(
- new Size(stackBounds.width(), stackBounds.height()), aspectRatio, minEdgeSize);
+ // Use the existing size but adjusted to the new aspect ratio.
+ size = mPipSizeSpecHandler.getSizeForAspectRatio(
+ new Size(stackBounds.width(), stackBounds.height()), aspectRatio);
} else {
- if (overrideMinSize != null) {
- // The override minimal size is set, use that as the default size making sure it's
- // adjusted to the aspect ratio.
- size = adjustSizeToAspectRatio(overrideMinSize, aspectRatio);
- } else {
- // Calculate the default size using the display size and default min edge size.
- final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
- size = getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
- displayLayout.width(), displayLayout.height());
- }
+ size = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
}
final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
@@ -273,18 +238,6 @@ public class PipBoundsAlgorithm {
mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
}
- /** Adjusts the given size to conform to the given aspect ratio. */
- private Size adjustSizeToAspectRatio(@NonNull Size size, float aspectRatio) {
- final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
- if (sizeAspectRatio > aspectRatio) {
- // Size is wider, fix the width and increase the height
- return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
- } else {
- // Size is taller, fix the height and adjust the width.
- return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
- }
- }
-
/**
* @return the default bounds to show the PIP, if a {@param snapFraction} and {@param size} are
* provided, then it will apply the default bounds to the provided snap fraction and size.
@@ -303,17 +256,9 @@ public class PipBoundsAlgorithm {
final Size defaultSize;
final Rect insetBounds = new Rect();
getInsetBounds(insetBounds);
- final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
- final Size overrideMinSize = mPipBoundsState.getOverrideMinSize();
- if (overrideMinSize != null) {
- // The override minimal size is set, use that as the default size making sure it's
- // adjusted to the aspect ratio.
- defaultSize = adjustSizeToAspectRatio(overrideMinSize, mDefaultAspectRatio);
- } else {
- // Calculate the default size using the display size and default min edge size.
- defaultSize = getSizeForAspectRatio(mDefaultAspectRatio,
- mDefaultMinSize, displayLayout.width(), displayLayout.height());
- }
+
+ // Calculate the default size
+ defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio);
// Now that we have the default size, apply the snap fraction if valid or position the
// bounds using the default gravity.
@@ -335,12 +280,7 @@ public class PipBoundsAlgorithm {
* Populates the bounds on the screen that the PIP can be visible in.
*/
public void getInsetBounds(Rect outRect) {
- final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
- Rect insets = mPipBoundsState.getDisplayLayout().stableInsets();
- outRect.set(insets.left + mScreenEdgeInsets.x,
- insets.top + mScreenEdgeInsets.y,
- displayLayout.width() - insets.right - mScreenEdgeInsets.x,
- displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+ outRect.set(mPipSizeSpecHandler.getInsetBounds());
}
/**
@@ -405,71 +345,11 @@ public class PipBoundsAlgorithm {
mSnapAlgorithm.applySnapFraction(stackBounds, movementBounds, snapFraction);
}
- public int getDefaultMinSize() {
- return mDefaultMinSize;
- }
-
/**
* @return the pixels for a given dp value.
*/
private int dpToPx(float dpValue, DisplayMetrics dm) {
- return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
- }
-
- /**
- * @return the size of the PiP at the given aspectRatio, ensuring that the minimum edge
- * is at least minEdgeSize.
- */
- public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
- int displayHeight) {
- final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
- final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
-
- final int width;
- final int height;
- if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
- // Beyond these points, we can just use the min size as the shorter edge
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
- } else {
- // Within these points, we ensure that the bounds fit within the radius of the limits
- // at the points
- final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
- final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
- height = (int) Math.round(Math.sqrt((radius * radius)
- / (aspectRatio * aspectRatio + 1)));
- width = Math.round(height * aspectRatio);
- }
- return new Size(width, height);
- }
-
- /**
- * @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
- * minimum edge is at least minEdgeSize.
- */
- public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
- final int smallestSize = Math.min(size.getWidth(), size.getHeight());
- final int minSize = (int) Math.max(minEdgeSize, smallestSize);
-
- final int width;
- final int height;
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size.
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
- return new Size(width, height);
+ return PipUtils.dpToPx(dpValue, dm);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 5376ae372de21d2ceb5dbb462365937422eb5c3d..92cf8cbf643ee019b2c93e8450df2afd76df7684 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -37,13 +37,16 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -83,13 +86,11 @@ public class PipBoundsState {
private int mStashedState = STASH_TYPE_NONE;
private int mStashOffset;
private @Nullable PipReentryState mPipReentryState;
+ private final LauncherState mLauncherState = new LauncherState();
+ private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
private @Nullable ComponentName mLastPipComponentName;
private int mDisplayId = Display.DEFAULT_DISPLAY;
private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
- /** The current minimum edge size of PIP. */
- private int mMinEdgeSize;
- /** The preferred minimum (and default) size specified by apps. */
- private @Nullable Size mOverrideMinSize;
private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
private boolean mIsImeShowing;
private int mImeHeight;
@@ -117,14 +118,21 @@ public class PipBoundsState {
* @see android.view.View#setPreferKeepClearRects
*/
private final Set mUnrestrictedKeepClearAreas = new ArraySet<>();
+ /**
+ * Additional to {@link #mUnrestrictedKeepClearAreas}, allow the caller to append named bounds
+ * as unrestricted keep clear area. Values in this map would be appended to
+ * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
+ */
+ private final Map mNamedUnrestrictedKeepClearAreas = new HashMap<>();
private @Nullable Runnable mOnMinimalSizeChangeCallback;
private @Nullable TriConsumer mOnShelfVisibilityChangeCallback;
private List> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
- public PipBoundsState(@NonNull Context context) {
+ public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler) {
mContext = context;
reloadResources();
+ mPipSizeSpecHandler = pipSizeSpecHandler;
}
/** Reloads the resources. */
@@ -312,10 +320,10 @@ public class PipBoundsState {
mDisplayLayout.set(displayLayout);
}
- /** Get the display layout. */
+ /** Get a copy of the display layout. */
@NonNull
public DisplayLayout getDisplayLayout() {
- return mDisplayLayout;
+ return new DisplayLayout(mDisplayLayout);
}
@VisibleForTesting
@@ -323,20 +331,10 @@ public class PipBoundsState {
mPipReentryState = null;
}
- /** Set the PIP minimum edge size. */
- public void setMinEdgeSize(int minEdgeSize) {
- mMinEdgeSize = minEdgeSize;
- }
-
- /** Returns the PIP's current minimum edge size. */
- public int getMinEdgeSize() {
- return mMinEdgeSize;
- }
-
/** Sets the preferred size of PIP as specified by the activity in PIP mode. */
public void setOverrideMinSize(@Nullable Size overrideMinSize) {
- final boolean changed = !Objects.equals(overrideMinSize, mOverrideMinSize);
- mOverrideMinSize = overrideMinSize;
+ final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize());
+ mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize);
if (changed && mOnMinimalSizeChangeCallback != null) {
mOnMinimalSizeChangeCallback.run();
}
@@ -345,13 +343,12 @@ public class PipBoundsState {
/** Returns the preferred minimal size specified by the activity in PIP. */
@Nullable
public Size getOverrideMinSize() {
- return mOverrideMinSize;
+ return mPipSizeSpecHandler.getOverrideMinSize();
}
/** Returns the minimum edge size of the override minimum size, or 0 if not set. */
public int getOverrideMinEdgeSize() {
- if (mOverrideMinSize == null) return 0;
- return Math.min(mOverrideMinSize.getWidth(), mOverrideMinSize.getHeight());
+ return mPipSizeSpecHandler.getOverrideMinEdgeSize();
}
/** Get the state of the bounds in motion. */
@@ -405,6 +402,16 @@ public class PipBoundsState {
mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
}
+ /** Add a named unrestricted keep clear area. */
+ public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) {
+ mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea);
+ }
+
+ /** Remove a named unrestricted keep clear area. */
+ public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) {
+ mNamedUnrestrictedKeepClearAreas.remove(name);
+ }
+
@NonNull
public Set getRestrictedKeepClearAreas() {
return mRestrictedKeepClearAreas;
@@ -412,7 +419,10 @@ public class PipBoundsState {
@NonNull
public Set getUnrestrictedKeepClearAreas() {
- return mUnrestrictedKeepClearAreas;
+ if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
+ final Set unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
+ unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values());
+ return unrestrictedAreas;
}
/**
@@ -488,6 +498,10 @@ public class PipBoundsState {
mOnPipExclusionBoundsChangeCallbacks.remove(onPipExclusionBoundsChangeCallback);
}
+ public LauncherState getLauncherState() {
+ return mLauncherState;
+ }
+
/** Source of truth for the current bounds of PIP that may be in motion. */
public static class MotionBoundsState {
/** The bounds used when PIP is in motion (e.g. during a drag or animation) */
@@ -540,6 +554,25 @@ public class PipBoundsState {
}
}
+ /** Data class for Launcher state. */
+ public static final class LauncherState {
+ private int mAppIconSizePx;
+
+ public void setAppIconSizePx(int appIconSizePx) {
+ mAppIconSizePx = appIconSizePx;
+ }
+
+ public int getAppIconSizePx() {
+ return mAppIconSizePx;
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + LauncherState.class.getSimpleName());
+ pw.println(innerPrefix + "getAppIconSizePx=" + getAppIconSizePx());
+ }
+ }
+
static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
@@ -581,11 +614,8 @@ public class PipBoundsState {
pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
- pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
pw.println(innerPrefix + "mStashedState=" + mStashedState);
pw.println(innerPrefix + "mStashOffset=" + mStashOffset);
- pw.println(innerPrefix + "mMinEdgeSize=" + mMinEdgeSize);
- pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
@@ -597,6 +627,7 @@ public class PipBoundsState {
} else {
mPipReentryState.dump(pw, innerPrefix);
}
+ mLauncherState.dump(pw, innerPrefix);
mMotionBoundsState.dump(pw, innerPrefix);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 7096a645ef85b40aa3fc67855ddb15f72e356688..9fa57cacb11f5e90b8a1d960d477fcd9545054a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -16,10 +16,18 @@
package com.android.wm.shell.pip;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.TaskSnapshot;
@@ -28,6 +36,9 @@ import android.window.TaskSnapshot;
* Represents the content overlay used during the entering PiP animation.
*/
public abstract class PipContentOverlay {
+ // Fixed string used in WMShellFlickerTests
+ protected static final String LAYER_NAME = "PipContentOverlay";
+
protected SurfaceControl mLeash;
/** Attaches the internal {@link #mLeash} to the given parent leash. */
@@ -41,13 +52,20 @@ public abstract class PipContentOverlay {
}
}
+ @Nullable
+ public SurfaceControl getLeash() {
+ return mLeash;
+ }
+
/**
* Animates the internal {@link #mLeash} by a given fraction.
* @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
* call apply on this transaction, it should be applied on the caller side.
+ * @param currentBounds {@link Rect} of the current animation bounds.
* @param fraction progress of the animation ranged from 0f to 1f.
*/
- public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction);
+ public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ Rect currentBounds, float fraction);
/**
* Callback when reaches the end of animation on the internal {@link #mLeash}.
@@ -60,13 +78,15 @@ public abstract class PipContentOverlay {
/** A {@link PipContentOverlay} uses solid color. */
public static final class PipColorOverlay extends PipContentOverlay {
+ private static final String TAG = PipColorOverlay.class.getSimpleName();
+
private final Context mContext;
public PipColorOverlay(Context context) {
mContext = context;
mLeash = new SurfaceControl.Builder(new SurfaceSession())
- .setCallsite("PipAnimation")
- .setName(PipColorOverlay.class.getSimpleName())
+ .setCallsite(TAG)
+ .setName(LAYER_NAME)
.setColorLayer()
.build();
}
@@ -82,7 +102,8 @@ public abstract class PipContentOverlay {
}
@Override
- public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) {
+ public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ Rect currentBounds, float fraction) {
atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
}
@@ -108,6 +129,8 @@ public abstract class PipContentOverlay {
/** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */
public static final class PipSnapshotOverlay extends PipContentOverlay {
+ private static final String TAG = PipSnapshotOverlay.class.getSimpleName();
+
private final TaskSnapshot mSnapshot;
private final Rect mSourceRectHint;
@@ -115,8 +138,8 @@ public abstract class PipContentOverlay {
mSnapshot = snapshot;
mSourceRectHint = new Rect(sourceRectHint);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
- .setCallsite("PipAnimation")
- .setName(PipSnapshotOverlay.class.getSimpleName())
+ .setCallsite(TAG)
+ .setName(LAYER_NAME)
.build();
}
@@ -137,7 +160,8 @@ public abstract class PipContentOverlay {
}
@Override
- public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) {
+ public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ Rect currentBounds, float fraction) {
// Do nothing. Keep the snapshot till animation ends.
}
@@ -146,4 +170,95 @@ public abstract class PipContentOverlay {
atomicTx.remove(mLeash);
}
}
+
+ /** A {@link PipContentOverlay} shows app icon on solid color background. */
+ public static final class PipAppIconOverlay extends PipContentOverlay {
+ private static final String TAG = PipAppIconOverlay.class.getSimpleName();
+ // The maximum size for app icon in pixel.
+ private static final int MAX_APP_ICON_SIZE_DP = 72;
+
+ private final Context mContext;
+ private final int mAppIconSizePx;
+ private final Rect mAppBounds;
+ private final Matrix mTmpTransform = new Matrix();
+ private final float[] mTmpFloat9 = new float[9];
+
+ private Bitmap mBitmap;
+
+ public PipAppIconOverlay(Context context, Rect appBounds,
+ Drawable appIcon, int appIconSizePx) {
+ mContext = context;
+ final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+ MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
+ mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
+ mAppBounds = new Rect(appBounds);
+ mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
+ Bitmap.Config.ARGB_8888);
+ prepareAppIconOverlay(appIcon);
+ mLeash = new SurfaceControl.Builder(new SurfaceSession())
+ .setCallsite(TAG)
+ .setName(LAYER_NAME)
+ .build();
+ }
+
+ @Override
+ public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
+ tx.show(mLeash);
+ tx.setLayer(mLeash, Integer.MAX_VALUE);
+ tx.setBuffer(mLeash, mBitmap.getHardwareBuffer());
+ tx.reparent(mLeash, parentLeash);
+ tx.apply();
+ }
+
+ @Override
+ public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ Rect currentBounds, float fraction) {
+ mTmpTransform.reset();
+ // Scale back the bitmap with the pivot point at center.
+ mTmpTransform.postScale(
+ (float) mAppBounds.width() / currentBounds.width(),
+ (float) mAppBounds.height() / currentBounds.height(),
+ mAppBounds.centerX(),
+ mAppBounds.centerY());
+ atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
+ .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
+ }
+
+ @Override
+ public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
+ atomicTx.remove(mLeash);
+ }
+
+ @Override
+ public void detach(SurfaceControl.Transaction tx) {
+ super.detach(tx);
+ if (mBitmap != null && !mBitmap.isRecycled()) {
+ mBitmap.recycle();
+ }
+ }
+
+ private void prepareAppIconOverlay(Drawable appIcon) {
+ final Canvas canvas = new Canvas();
+ canvas.setBitmap(mBitmap);
+ final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+ android.R.attr.colorBackground });
+ try {
+ int colorAccent = ta.getColor(0, 0);
+ canvas.drawRGB(
+ Color.red(colorAccent),
+ Color.green(colorAccent),
+ Color.blue(colorAccent));
+ } finally {
+ ta.recycle();
+ }
+ final Rect appIconBounds = new Rect(
+ mAppBounds.centerX() - mAppIconSizePx / 2,
+ mAppBounds.centerY() - mAppIconSizePx / 2,
+ mAppBounds.centerX() + mAppIconSizePx / 2,
+ mAppBounds.centerY() + mAppIconSizePx / 2);
+ appIcon.setBounds(appIconBounds);
+ appIcon.draw(canvas);
+ mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
index e3495e100c62ed53a672d9149899c8ea59a1d4a8..5045cf905ee63fc674ea1b6cb0f9e5f81bc984f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
@@ -24,7 +24,7 @@ import java.util.Set;
* Interface for interacting with keep clear algorithm used to move PiP window out of the way of
* keep clear areas.
*/
-public interface PipKeepClearAlgorithm {
+public interface PipKeepClearAlgorithmInterface {
/**
* Adjust the position of picture in picture window based on the registered keep clear areas.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 8ba2583757cda95afa19bd9ddec5c2be3ce503bc..c4b5470f461adae6a9956078db26a90ab707e06e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -62,6 +63,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.Log;
import android.view.Choreographer;
import android.view.Display;
@@ -71,6 +73,7 @@ import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransactionCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -78,11 +81,13 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -125,6 +130,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
private final PipBoundsState mPipBoundsState;
+ private final PipSizeSpecHandler mPipSizeSpecHandler;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final @NonNull PipMenuController mPipMenuController;
private final PipAnimationController mPipAnimationController;
@@ -139,13 +145,32 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
+ // the runnable to execute after WindowContainerTransactions is applied to finish resizing pip
+ private Runnable mPipFinishResizeWCTRunnable;
+
+ private final WindowContainerTransactionCallback mPipFinishResizeWCTCallback =
+ new WindowContainerTransactionCallback() {
+ @Override
+ public void onTransactionReady(int id, SurfaceControl.Transaction t) {
+ t.apply();
+
+ // execute the runnable if non-null after WCT is applied to finish resizing pip
+ if (mPipFinishResizeWCTRunnable != null) {
+ mPipFinishResizeWCTRunnable.run();
+ mPipFinishResizeWCTRunnable = null;
+ }
+ }
+ };
+
// These callbacks are called on the update thread
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
+ private boolean mIsCancelled;
@Override
public void onPipAnimationStart(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
+ mIsCancelled = false;
sendOnPipTransitionStarted(direction);
}
@@ -153,6 +178,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
+ if (mIsCancelled) {
+ sendOnPipTransitionFinished(direction);
+ return;
+ }
final int animationType = animator.getAnimationType();
final Rect destinationBounds = animator.getDestinationBounds();
if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
@@ -191,6 +220,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void onPipAnimationCancel(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
+ mIsCancelled = true;
if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
animator::clearContentOverlay, true /* withStartDelay */);
@@ -312,6 +342,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -327,6 +358,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSyncTransactionQueue = syncTransactionQueue;
mPipTransitionState = pipTransitionState;
mPipBoundsState = pipBoundsState;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
mPipTransitionController = pipTransitionController;
@@ -532,6 +564,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
return;
}
+ if (mSplitScreenOptional.isPresent()) {
+ // If pip activity will reparent to origin task case and if the origin task still under
+ // split root, just exit split screen here to ensure it could expand to fullscreen.
+ SplitScreenController split = mSplitScreenOptional.get();
+ if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
+ split.exitSplitScreen(INVALID_TASK_ID,
+ SplitScreenController.EXIT_REASON_APP_FINISHED);
+ }
+ }
mSyncTransactionQueue.queue(wct);
mSyncTransactionQueue.runInSync(t -> {
// Make sure to grab the latest source hint rect as it could have been
@@ -644,7 +685,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
- mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
// If the displayId of the task is different than what PipBoundsHandler has, then update
// it. This is possible if we entered PiP on an external display.
@@ -653,6 +693,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mOnDisplayIdChangeCallback.accept(info.displayId);
}
+ // UiEvent logging.
+ final PipUiEventLogger.PipUiEventEnum uiEventEnum;
+ if (isLaunchIntoPipTask()) {
+ uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER_CONTENT_PIP;
+ } else if (mPipTransitionState.getInSwipePipToHomeTransition()) {
+ uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_AUTO_ENTER;
+ } else {
+ uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER;
+ }
+ mPipUiEventLoggerLogger.log(uiEventEnum);
+
if (mPipTransitionState.getInSwipePipToHomeTransition()) {
if (!mWaitForFixedRotation) {
onEndOfSwipePipToHomeTransition();
@@ -1145,21 +1196,32 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final Rect newDestinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
if (newDestinationBounds.equals(currentDestinationBounds)) return;
- if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
- if (mWaitForFixedRotation) {
- // The new destination bounds are in next rotation (DisplayLayout has been rotated
- // in computeRotatedBounds). The animation runs in previous rotation so the end
- // bounds need to be transformed.
- final Rect displayBounds = mPipBoundsState.getDisplayBounds();
- final Rect rotatedEndBounds = new Rect(newDestinationBounds);
- rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
- animator.updateEndValue(rotatedEndBounds);
- } else {
- animator.updateEndValue(newDestinationBounds);
+ updateAnimatorBounds(newDestinationBounds);
+ destinationBoundsOut.set(newDestinationBounds);
+ }
+
+ /**
+ * Directly update the animator bounds.
+ */
+ public void updateAnimatorBounds(Rect bounds) {
+ final PipAnimationController.PipTransitionAnimator animator =
+ mPipAnimationController.getCurrentAnimator();
+ if (animator != null && animator.isRunning()) {
+ if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
+ if (mWaitForFixedRotation) {
+ // The new destination bounds are in next rotation (DisplayLayout has been
+ // rotated in computeRotatedBounds). The animation runs in previous rotation so
+ // the end bounds need to be transformed.
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect rotatedEndBounds = new Rect(bounds);
+ rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
+ animator.updateEndValue(rotatedEndBounds);
+ } else {
+ animator.updateEndValue(bounds);
+ }
}
+ animator.setDestinationBounds(bounds);
}
- animator.setDestinationBounds(newDestinationBounds);
- destinationBoundsOut.set(newDestinationBounds);
}
/**
@@ -1168,7 +1230,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) {
if (mDeferredTaskInfo != null || PipUtils.aspectRatioChanged(params.getAspectRatioFloat(),
mPictureInPictureParams.getAspectRatioFloat())) {
- mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat());
+ if (mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio(
+ params.getAspectRatioFloat())) {
+ mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat());
+ } else {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: New aspect ratio is not valid."
+ + " hasAspectRatio=%b"
+ + " aspectRatio=%f",
+ TAG, params.hasSetAspectRatio(), params.getAspectRatioFloat());
+ }
}
if (mDeferredTaskInfo != null
|| PipUtils.remoteActionsChanged(params.getActions(),
@@ -1207,8 +1278,23 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
/**
* Animates resizing of the pinned stack given the duration and start bounds.
* This is used when the starting bounds is not the current PiP bounds.
+ *
+ * @param pipFinishResizeWCTRunnable callback to run after window updates are complete
*/
public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
+ float startingAngle, Consumer updateBoundsCallback,
+ Runnable pipFinishResizeWCTRunnable) {
+ mPipFinishResizeWCTRunnable = pipFinishResizeWCTRunnable;
+ if (mPipFinishResizeWCTRunnable != null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "mPipFinishResizeWCTRunnable is set to be called once window updates");
+ }
+
+ scheduleAnimateResizePip(fromBounds, toBounds, duration, startingAngle,
+ updateBoundsCallback);
+ }
+
+ private void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
float startingAngle, Consumer updateBoundsCallback) {
if (mWaitForFixedRotation) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -1403,7 +1489,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@PipAnimationController.TransitionDirection int direction,
@PipAnimationController.AnimationType int type) {
final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds());
- final boolean isPipTopLeft = isPipTopLeft();
mPipBoundsState.setBounds(destinationBounds);
if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
removePipImmediately();
@@ -1449,10 +1534,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
null /* callback */, false /* withStartDelay */);
});
} else {
- applyFinishBoundsResize(wct, direction, isPipTopLeft);
+ applyFinishBoundsResize(wct, direction, false);
}
} else {
- applyFinishBoundsResize(wct, direction, isPipTopLeft);
+ applyFinishBoundsResize(wct, direction, isPipToTopLeft());
+ // Use sync transaction to apply finish transaction for enter split case.
+ if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+ mSyncTransactionQueue.runInSync(t -> {
+ t.merge(tx);
+ });
+ }
}
finishResizeForMenu(destinationBounds);
@@ -1489,7 +1580,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
wct.setBounds(mToken, taskBounds);
- wct.setBoundsChangeTransaction(mToken, tx);
+ // Pip to split should use sync transaction to sync split bounds change.
+ if (direction != TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+ wct.setBoundsChangeTransaction(mToken, tx);
+ }
}
/**
@@ -1505,7 +1599,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSplitScreenOptional.ifPresent(splitScreenController ->
splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct));
} else {
- mTaskOrganizer.applyTransaction(wct);
+ mTaskOrganizer.applySyncTransaction(wct, mPipFinishResizeWCTCallback);
}
}
@@ -1520,6 +1614,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return topLeft.contains(mPipBoundsState.getBounds());
}
+ private boolean isPipToTopLeft() {
+ if (!mSplitScreenOptional.isPresent()) {
+ return false;
+ }
+ return mSplitScreenOptional.get().getActivateSplitPosition(mTaskInfo)
+ == SPLIT_POSITION_TOP_OR_LEFT;
+ }
+
/**
* The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
* and can be overridden to restore to an alternate windowing mode.
@@ -1568,7 +1670,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// Similar to auto-enter-pip transition, we use content overlay when there is no
// source rect hint to enter PiP use bounds animation.
if (sourceHintRect == null) {
- animator.setColorContentOverlay(mContext);
+ if (SystemProperties.getBoolean(
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
+ animator.setAppIconContentOverlay(
+ mContext, currentBounds, mTaskInfo.topActivityInfo,
+ mPipBoundsState.getLauncherState().getAppIconSizePx());
+ } else {
+ animator.setColorContentOverlay(mContext);
+ }
} else {
final TaskSnapshot snapshot = PipUtils.getTaskSnapshot(
mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */);
@@ -1594,7 +1703,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private @Nullable Rect computeRotatedBounds(int rotationDelta, int direction,
Rect outDestinationBounds, Rect sourceHintRect) {
if (direction == TRANSITION_DIRECTION_TO_PIP) {
- mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), mNextRotation);
+ DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+ layoutCopy.rotateTo(mContext.getResources(), mNextRotation);
+ mPipBoundsState.setDisplayLayout(layoutCopy);
+ mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+
final Rect displayBounds = mPipBoundsState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
// Transform the destination bounds to current display coordinates.
@@ -1645,8 +1759,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final Rect topLeft = new Rect();
final Rect bottomRight = new Rect();
mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
- final boolean isPipTopLeft = isPipTopLeft();
- destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight);
+ destinationBoundsOut.set(isPipToTopLeft() ? topLeft : bottomRight);
return true;
}
@@ -1730,6 +1843,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceControlTransactionFactory = factory;
}
+ public boolean isLaunchToSplit(TaskInfo taskInfo) {
+ return mSplitScreenOptional.isPresent()
+ && mSplitScreenOptional.get().isLaunchToSplit(taskInfo);
+ }
+
/**
* Dumps internal states.
*/
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 e6c7e101d0789b4f9d72ca000038eeca535383ee..8e7b30eb60d01122d6f071807e28cf29907b5736 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
@@ -50,6 +50,7 @@ import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.SystemProperties;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -64,6 +65,8 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
@@ -82,6 +85,7 @@ public class PipTransition extends PipTransitionController {
private final Context mContext;
private final PipTransitionState mPipTransitionState;
+ private final PipSizeSpecHandler mPipSizeSpecHandler;
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional mSplitScreenOptional;
@@ -112,6 +116,7 @@ public class PipTransition extends PipTransitionController {
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipTransitionState pipTransitionState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -122,6 +127,7 @@ public class PipTransition extends PipTransitionController {
pipBoundsAlgorithm, pipAnimationController);
mContext = context;
mPipTransitionState = pipTransitionState;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -307,7 +313,12 @@ public class PipTransition extends PipTransitionController {
// initial state under the new rotation.
int rotationDelta = deltaRotation(startRotation, endRotation);
if (rotationDelta != Surface.ROTATION_0) {
- mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+ DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+ layoutCopy.rotateTo(mContext.getResources(), endRotation);
+ mPipBoundsState.setDisplayLayout(layoutCopy);
+ mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
wct.setBounds(mRequestedEnterTask, destinationBounds);
return true;
@@ -662,8 +673,8 @@ public class PipTransition extends PipTransitionController {
}
// Please file a bug to handle the unexpected transition type.
- throw new IllegalStateException("Entering PIP with unexpected transition type="
- + transitTypeToString(transitType));
+ android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type="
+ + transitTypeToString(transitType), new Throwable());
}
return false;
}
@@ -792,7 +803,21 @@ public class PipTransition extends PipTransitionController {
if (sourceHintRect == null) {
// We use content overlay when there is no source rect hint to enter PiP use bounds
// animation.
- animator.setColorContentOverlay(mContext);
+ // TODO(b/272819817): cleanup the null-check and extra logging.
+ final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
+ if (!hasTopActivityInfo) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: TaskInfo.topActivityInfo is null", TAG);
+ }
+ if (SystemProperties.getBoolean(
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)
+ && hasTopActivityInfo) {
+ animator.setAppIconContentOverlay(
+ mContext, currentBounds, taskInfo.topActivityInfo,
+ mPipBoundsState.getLauncherState().getAppIconSizePx());
+ } else {
+ animator.setColorContentOverlay(mContext);
+ }
}
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
@@ -817,7 +842,12 @@ public class PipTransition extends PipTransitionController {
/** Computes destination bounds in old rotation and updates source hint rect if available. */
private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
- mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+ DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+ layoutCopy.rotateTo(mContext.getResources(), endRotation);
+ mPipBoundsState.setDisplayLayout(layoutCopy);
+ mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+
final Rect displayBounds = mPipBoundsState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
// Transform the destination bounds to current display coordinates.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
index c6b5ce93fd35f022ad6f5b3704a538c8cfc4baa8..db6138a0891f7d1d8b127523899999e34141b655 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
@@ -93,6 +93,11 @@ public class PipTransitionState {
return hasEnteredPip(mState);
}
+ /** Returns true if activity is currently entering PiP mode. */
+ public boolean isEnteringPip() {
+ return isEnteringPip(mState);
+ }
+
public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) {
mInSwipePipToHomeTransition = inSwipePipToHomeTransition;
}
@@ -130,6 +135,11 @@ public class PipTransitionState {
return state == ENTERED_PIP;
}
+ /** Returns true if activity is currently entering PiP mode. */
+ public static boolean isEnteringPip(@TransitionState int state) {
+ return state == ENTERING_PIP;
+ }
+
public interface OnPipTransitionStateChangedListener {
void onPipTransitionStateChanged(@TransitionState int oldState,
@TransitionState int newState);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
index 513ebba59258c3892c3624e49aee1c098bf03e1a..3e5a19b69a59dae59ade11ca58c5d815296a17f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
@@ -78,6 +78,12 @@ public class PipUiEventLogger {
@UiEvent(doc = "Activity enters picture-in-picture mode")
PICTURE_IN_PICTURE_ENTER(603),
+ @UiEvent(doc = "Activity enters picture-in-picture mode with auto-enter-pip API")
+ PICTURE_IN_PICTURE_AUTO_ENTER(1313),
+
+ @UiEvent(doc = "Activity enters picture-in-picture mode from content-pip API")
+ PICTURE_IN_PICTURE_ENTER_CONTENT_PIP(1314),
+
@UiEvent(doc = "Expands from picture-in-picture to fullscreen")
PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN(604),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
index fa0061982c45e3a6ddd65f91b116129eee922514..8b98790d34990d60f13d4f84beabb1652d3b05cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.pip;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
@@ -26,8 +27,10 @@ import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Context;
import android.os.RemoteException;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
+import android.util.TypedValue;
import android.window.TaskSnapshot;
import com.android.internal.protolog.common.ProtoLog;
@@ -69,6 +72,13 @@ public class PipUtils {
return new Pair<>(null, 0);
}
+ /**
+ * @return the pixels for a given dp value.
+ */
+ public static int dpToPx(float dpValue, DisplayMetrics dm) {
+ return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
+ }
+
/**
* @return true if the aspect ratios differ
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
index 690505e03fce82d81432796d67dc448866070639..ed8dc7ded65483e072b4a6938c3a75dbfa7d981a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -26,14 +26,14 @@ import android.view.Gravity;
import com.android.wm.shell.R;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import java.util.Set;
/**
* Calculates the adjusted position that does not occlude keep clear areas.
*/
-public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm {
+public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterface {
private boolean mKeepClearAreaGravityEnabled =
SystemProperties.getBoolean(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 281ea530e9e13860c7f9919fa2d8c7f3e866a6e9..431bd7b081423e70bbacc899bcae99dda3e9d9f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -333,6 +333,9 @@ public class PhonePipMenuController implements PipMenuController {
mTmpDestinationRectF.set(destinationBounds);
mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
final SurfaceControl surfaceControl = getSurfaceControl();
+ if (surfaceControl == null) {
+ return;
+ }
final SurfaceControl.Transaction menuTx =
mSurfaceControlTransactionFactory.getTransaction();
menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform);
@@ -359,6 +362,9 @@ public class PhonePipMenuController implements PipMenuController {
}
final SurfaceControl surfaceControl = getSurfaceControl();
+ if (surfaceControl == null) {
+ return;
+ }
final SurfaceControl.Transaction menuTx =
mSurfaceControlTransactionFactory.getTransaction();
menuTx.setCrop(surfaceControl, destinationBounds);
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 01d81ff4e436d1695b527f498a45818f4ca4a17b..2bd5f1c5817c451bf75e1312f2cb4614123d41e6 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
@@ -43,11 +43,10 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
import android.util.Pair;
import android.util.Size;
import android.view.DisplayInfo;
@@ -73,6 +72,7 @@ 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.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
@@ -85,7 +85,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -117,11 +117,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
UserChangeListener {
private static final String TAG = "PipController";
+ private static final String LAUNCHER_KEEP_CLEAR_AREA_TAG = "hotseat";
+
private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
private boolean mEnablePipKeepClearAlgorithm =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+ SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
@VisibleForTesting
void setEnablePipKeepClearAlgorithm(boolean value) {
@@ -137,14 +139,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipAppOpsListener mAppOpsListener;
private PipMediaController mMediaController;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
- private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
+ private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
private TaskStackListenerImpl mTaskStackListener;
private PipParamsChangedForwarder mPipParamsChangedForwarder;
private DisplayInsetsController mDisplayInsetsController;
+ private TabletopModeController mTabletopModeController;
private Optional mOneHandedController;
private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
@@ -157,6 +161,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
this::onKeepClearAreasChangedCallback;
private void onKeepClearAreasChangedCallback() {
+ if (mIsKeyguardShowingOrAnimating) {
+ // early bail out if the change was caused by keyguard showing up
+ return;
+ }
if (!mEnablePipKeepClearAlgorithm) {
// early bail out if the keep clear areas feature is disabled
return;
@@ -182,14 +190,24 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// early bail out if the keep clear areas feature is disabled
return;
}
- // only move if already in pip, other transitions account for keep clear areas
- if (mPipTransitionState.hasEnteredPip()) {
+ if (mIsKeyguardShowingOrAnimating) {
+ // early bail out if the change was caused by keyguard showing up
+ return;
+ }
+ // only move if we're in PiP or transitioning into PiP
+ if (!mPipTransitionState.shouldBlockResizeRequest()) {
Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
mPipBoundsAlgorithm);
// only move if the bounds are actually different
- if (destBounds != mPipBoundsState.getBounds()) {
- mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
- mEnterAnimationDuration, null);
+ if (!destBounds.equals(mPipBoundsState.getBounds())) {
+ if (mPipTransitionState.hasEnteredPip()) {
+ // if already in PiP, schedule separate animation
+ mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
+ mEnterAnimationDuration, null);
+ } else if (mPipTransitionState.isEnteringPip()) {
+ // while entering PiP we just need to update animator bounds
+ mPipTaskOrganizer.updateAnimatorBounds(destBounds);
+ }
}
}
}
@@ -380,8 +398,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipAnimationController pipAnimationController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -393,6 +412,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController pipTabletopController,
Optional oneHandedController,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -403,11 +423,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener,
- pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
- taskStackListener, pipParamsChangedForwarder, displayInsetsController,
- oneHandedController, mainExecutor)
+ pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
+ pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
.mImpl;
}
@@ -419,8 +439,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipAnimationController pipAnimationController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -432,6 +453,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController tabletopModeController,
Optional oneHandedController,
ShellExecutor mainExecutor
) {
@@ -446,6 +468,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
mPipTransitionState = pipTransitionState;
@@ -463,6 +486,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
.getInteger(R.integer.config_pipEnterAnimationDuration);
mPipParamsChangedForwarder = pipParamsChangedForwarder;
mDisplayInsetsController = displayInsetsController;
+ mTabletopModeController = tabletopModeController;
shellInit.addInitCallback(this::onInit, this);
}
@@ -514,7 +538,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back
mPipBoundsState.setDisplayId(mContext.getDisplayId());
- mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
+
+ DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+ mPipSizeSpecHandler.setDisplayLayout(layout);
+ mPipBoundsState.setDisplayLayout(layout);
try {
mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener);
@@ -565,8 +592,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
return;
}
- mTouchHandler.getMotionHelper().expandLeavePip(
- clearedTask /* skipAnimation */);
+ if (mPipTaskOrganizer.isLaunchToSplit(task)) {
+ mTouchHandler.getMotionHelper().expandIntoSplit();
+ } else {
+ mTouchHandler.getMotionHelper().expandLeavePip(
+ clearedTask /* skipAnimation */);
+ }
}
});
@@ -612,13 +643,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb
DisplayLayout pendingLayout =
mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId());
if (mIsInFixedRotation
+ || mIsKeyguardShowingOrAnimating
|| pendingLayout.rotation()
!= mPipBoundsState.getDisplayLayout().rotation()) {
- // bail out if there is a pending rotation or fixed rotation change
+ // bail out if there is a pending rotation or fixed rotation change or
+ // there's a keyguard present
return;
}
int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
- onDisplayChanged(
+ onDisplayChangedUncheck(
mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
false /* saveRestoreSnapFraction */);
int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
@@ -639,6 +672,42 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
});
+ mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
+ if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return;
+ final String tag = "tabletop-mode";
+ if (!isInTabletopMode) {
+ mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
+ return;
+ }
+
+ // To prepare for the entry bounds.
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ if (mTabletopModeController.getPreferredHalfInTabletopMode()
+ == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) {
+ // Prefer top, avoid the bottom half of the display.
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+ displayBounds.left, displayBounds.centerY(),
+ displayBounds.right, displayBounds.bottom));
+ } else {
+ // Prefer bottom, avoid the top half of the display.
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+ displayBounds.left, displayBounds.top,
+ displayBounds.right, displayBounds.centerY()));
+ }
+
+ // Try to move the PiP window if we have entered PiP mode.
+ if (mPipTransitionState.hasEnteredPip()) {
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+ if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
+ // PiP bounds is too big to fit either half, bail early.
+ return;
+ }
+ mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
+ mMainExecutor.execute(mMovePipInResponseToKeepClearAreasChangeCallback);
+ }
+ });
+
mOneHandedController.ifPresent(controller -> {
controller.registerTransitionCallback(
new OneHandedTransitionCallback() {
@@ -688,6 +757,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTouchHandler.onConfigurationChanged();
mPipBoundsState.onConfigurationChanged();
+ mPipSizeSpecHandler.onConfigurationChanged();
}
@Override
@@ -704,13 +774,26 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) {
- if (mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
- return;
+ if (!mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
+ PipAnimationController.PipTransitionAnimator animator =
+ mPipAnimationController.getCurrentAnimator();
+ if (animator != null && animator.isRunning()) {
+ // cancel any running animator, as it is using stale display layout information
+ animator.cancel();
+ }
+ onDisplayChangedUncheck(layout, saveRestoreSnapFraction);
}
+ }
+
+ private void onDisplayChangedUncheck(DisplayLayout layout, boolean saveRestoreSnapFraction) {
Runnable updateDisplayLayout = () -> {
final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS
&& mPipBoundsState.getDisplayLayout().rotation() != layout.rotation();
+
+ // update the internal state of objects subscribed to display changes
+ mPipSizeSpecHandler.setDisplayLayout(layout);
mPipBoundsState.setDisplayLayout(layout);
+
final WindowContainerTransaction wct =
fromRotation ? new WindowContainerTransaction() : null;
updateMovementBounds(null /* toBounds */,
@@ -814,7 +897,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
boolean animatingDismiss) {
- if (!mPipTaskOrganizer.isInPip()) {
+ if (!mPipTransitionState.hasEnteredPip()) {
return;
}
if (visible) {
@@ -851,6 +934,23 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
}
+ private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ if (visible) {
+ Rect rect = new Rect(
+ 0, mPipBoundsState.getDisplayBounds().bottom - height,
+ mPipBoundsState.getDisplayBounds().right,
+ mPipBoundsState.getDisplayBounds().bottom);
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect);
+ } else {
+ mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG);
+ }
+ updatePipPositionForKeepClearAreas();
+ }
+
+ private void setLauncherAppIconSize(int iconSizePx) {
+ mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
+ }
+
private void setOnIsInPipStateChangedListener(Consumer callback) {
mOnIsInPipStateChangedListener = callback;
if (mOnIsInPipStateChangedListener != null) {
@@ -1019,7 +1119,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private void onDisplayRotationChangedNotInPip(Context context, int toRotation) {
// Update the display layout, note that we have to do this on every rotation even if we
// aren't in PIP since we need to update the display layout to get the right resources
- mPipBoundsState.getDisplayLayout().rotateTo(context.getResources(), toRotation);
+ DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+ layoutCopy.rotateTo(context.getResources(), toRotation);
+ mPipBoundsState.setDisplayLayout(layoutCopy);
+ mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
}
/**
@@ -1056,7 +1160,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.getStashedState());
// Update the display layout
- mPipBoundsState.getDisplayLayout().rotateTo(context.getResources(), toRotation);
+ DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+ layoutCopy.rotateTo(context.getResources(), toRotation);
+ mPipBoundsState.setDisplayLayout(layoutCopy);
+ mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
// Calculate the stack bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -1082,6 +1190,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipTaskOrganizer.dump(pw, innerPrefix);
mPipBoundsState.dump(pw, innerPrefix);
mPipInputConsumer.dump(pw, innerPrefix);
+ mPipSizeSpecHandler.dump(pw, innerPrefix);
}
/**
@@ -1190,18 +1299,26 @@ public class PipController implements PipTransitionController.PipTransitionCallb
public void stopSwipePipToHome(int taskId, ComponentName componentName,
Rect destinationBounds, SurfaceControl overlay) {
executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
- (controller) -> {
- controller.stopSwipePipToHome(taskId, componentName, destinationBounds,
- overlay);
- });
+ (controller) -> controller.stopSwipePipToHome(
+ taskId, componentName, destinationBounds, overlay));
}
@Override
public void setShelfHeight(boolean visible, int height) {
executeRemoteCallWithTaskPermission(mController, "setShelfHeight",
- (controller) -> {
- controller.setShelfHeight(visible, height);
- });
+ (controller) -> controller.setShelfHeight(visible, height));
+ }
+
+ @Override
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ executeRemoteCallWithTaskPermission(mController, "setLauncherKeepClearAreaHeight",
+ (controller) -> controller.setLauncherKeepClearAreaHeight(visible, height));
+ }
+
+ @Override
+ public void setLauncherAppIconSize(int iconSizePx) {
+ executeRemoteCallWithTaskPermission(mController, "setLauncherAppIconSize",
+ (controller) -> controller.setLauncherAppIconSize(iconSizePx));
}
@Override
@@ -1219,9 +1336,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void setPipAnimationTypeToAlpha() {
executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
- (controller) -> {
- controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA);
- });
+ (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA));
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 7619646804ad3ffc6ccd485476d7060e1f7510fd..9729a4007bac762169d20e85a4e8a1bc76a2bd6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -235,21 +235,14 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
/** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
public void createOrUpdateDismissTarget() {
- if (!mTargetViewContainer.isAttachedToWindow()) {
+ if (mTargetViewContainer.getParent() == null) {
mTargetViewContainer.cancelAnimators();
mTargetViewContainer.setVisibility(View.INVISIBLE);
mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
mHasDismissTargetSurface = false;
- try {
- mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
- } catch (IllegalStateException e) {
- // This shouldn't happen, but if the target is already added, just update its layout
- // params.
- mWindowManager.updateViewLayout(
- mTargetViewContainer, getDismissTargetLayoutParams());
- }
+ mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
} else {
mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
}
@@ -306,7 +299,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
* Removes the dismiss target and cancels any pending callbacks to show it.
*/
public void cleanUpDismissTarget() {
- if (mTargetViewContainer.isAttachedToWindow()) {
+ if (mTargetViewContainer.getParent() != null) {
mWindowManager.removeViewImmediate(mTargetViewContainer);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 979b7c7dc31f4cbf80580fe44235aef4dca0ff26..167c0321d3ad2f9a1aa6bfa494d66b4bf0066f46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -282,7 +282,8 @@ public class PipMenuView extends FrameLayout {
public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent()
- && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId);
+ && mSplitScreenControllerOptional.get().isTaskInSplitScreenForeground(
+ taskInfo.taskId);
mFocusedTaskAllowSplitScreen = isSplitScreen
|| (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
&& taskInfo.supportsMultiWindow
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 41ff0b35a0359354fe8c34487ebcd91d0c3eab13..3e83c5fcf9a0818a99986ef1e4d7b680ee489601 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
@@ -580,8 +580,16 @@ public class PipResizeGestureHandler {
final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
mLastResizeBounds, movementBounds);
mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+
+ // disable the pinch resizing until the final bounds are updated
+ final boolean prevEnablePinchResize = mEnablePinchResize;
+ mEnablePinchResize = false;
+
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
- PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback);
+ PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
+ // reset the pinch resizing to its default state
+ mEnablePinchResize = prevEnablePinchResize;
+ });
} else {
mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff5138d4bb91543f8caf043189a2f76ec30f7fd0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -0,0 +1,530 @@
+/*
+ * 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.pip.phone;
+
+import static com.android.wm.shell.pip.PipUtils.dpToPx;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.SystemProperties;
+import android.util.Size;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.io.PrintWriter;
+
+/**
+ * Acts as a source of truth for appropriate size spec for PIP.
+ */
+public class PipSizeSpecHandler {
+ private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
+
+ @NonNull private final DisplayLayout mDisplayLayout = new DisplayLayout();
+
+ @VisibleForTesting
+ final SizeSpecSource mSizeSpecSourceImpl;
+
+ /** The preferred minimum (and default minimum) size specified by apps. */
+ @Nullable private Size mOverrideMinSize;
+ private int mOverridableMinSize;
+
+ /** Used to store values obtained from resource files. */
+ private Point mScreenEdgeInsets;
+ private float mMinAspectRatioForMinSize;
+ private float mMaxAspectRatioForMinSize;
+ private int mDefaultMinSize;
+
+ @NonNull private final Context mContext;
+
+ private interface SizeSpecSource {
+ /** Returns max size allowed for the PIP window */
+ Size getMaxSize(float aspectRatio);
+
+ /** Returns default size for the PIP window */
+ Size getDefaultSize(float aspectRatio);
+
+ /** Returns min size allowed for the PIP window */
+ Size getMinSize(float aspectRatio);
+
+ /** Returns the adjusted size based on current size and target aspect ratio */
+ Size getSizeForAspectRatio(Size size, float aspectRatio);
+
+ /** Updates internal resources on configuration changes */
+ default void reloadResources() {}
+ }
+
+ /**
+ * Determines PIP window size optimized for large screens and aspect ratios close to 1:1
+ */
+ private class SizeSpecLargeScreenOptimizedImpl implements SizeSpecSource {
+ private static final float DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16;
+
+ /** Default and minimum percentages for the PIP size logic. */
+ private final float mDefaultSizePercent;
+ private final float mMinimumSizePercent;
+
+ /** Aspect ratio that the PIP size spec logic optimizes for. */
+ private float mOptimizedAspectRatio;
+
+ private SizeSpecLargeScreenOptimizedImpl() {
+ mDefaultSizePercent = Float.parseFloat(SystemProperties
+ .get("com.android.wm.shell.pip.phone.def_percentage", "0.6"));
+ mMinimumSizePercent = Float.parseFloat(SystemProperties
+ .get("com.android.wm.shell.pip.phone.min_percentage", "0.5"));
+ }
+
+ @Override
+ public void reloadResources() {
+ final Resources res = mContext.getResources();
+
+ mOptimizedAspectRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio);
+ // make sure the optimized aspect ratio is valid with a default value to fall back to
+ if (mOptimizedAspectRatio > 1) {
+ mOptimizedAspectRatio = DEFAULT_OPTIMIZED_ASPECT_RATIO;
+ }
+ }
+
+ /**
+ * Calculates the max size of PIP.
+ *
+ * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
+ * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
+ * whole screen. A linear function is used to calculate these sizes.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the max size of the PIP
+ */
+ @Override
+ public Size getMaxSize(float aspectRatio) {
+ final int totalHorizontalPadding = getInsetBounds().left
+ + (getDisplayBounds().width() - getInsetBounds().right);
+ final int totalVerticalPadding = getInsetBounds().top
+ + (getDisplayBounds().height() - getInsetBounds().bottom);
+
+ final int shorterLength = (int) (1f * Math.min(
+ getDisplayBounds().width() - totalHorizontalPadding,
+ getDisplayBounds().height() - totalVerticalPadding));
+
+ int maxWidth, maxHeight;
+
+ // use the optimized max sizing logic only within a certain aspect ratio range
+ if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
+ // this formula and its derivation is explained in b/198643358#comment16
+ maxWidth = (int) (mOptimizedAspectRatio * shorterLength
+ + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
+ + aspectRatio));
+ maxHeight = (int) (maxWidth / aspectRatio);
+ } else {
+ if (aspectRatio > 1f) {
+ maxWidth = shorterLength;
+ maxHeight = (int) (maxWidth / aspectRatio);
+ } else {
+ maxHeight = shorterLength;
+ maxWidth = (int) (maxHeight * aspectRatio);
+ }
+ }
+
+ return new Size(maxWidth, maxHeight);
+ }
+
+ /**
+ * Decreases the dimensions by a percentage relative to max size to get default size.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the default size of the PIP
+ */
+ @Override
+ public Size getDefaultSize(float aspectRatio) {
+ Size minSize = this.getMinSize(aspectRatio);
+
+ if (mOverrideMinSize != null) {
+ return minSize;
+ }
+
+ Size maxSize = this.getMaxSize(aspectRatio);
+
+ int defaultWidth = Math.max((int) (maxSize.getWidth() * mDefaultSizePercent),
+ minSize.getWidth());
+ int defaultHeight = Math.max((int) (maxSize.getHeight() * mDefaultSizePercent),
+ minSize.getHeight());
+
+ return new Size(defaultWidth, defaultHeight);
+ }
+
+ /**
+ * Decreases the dimensions by a certain percentage relative to max size to get min size.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the min size of the PIP
+ */
+ @Override
+ public Size getMinSize(float aspectRatio) {
+ // if there is an overridden min size provided, return that
+ if (mOverrideMinSize != null) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio);
+ }
+
+ Size maxSize = this.getMaxSize(aspectRatio);
+
+ int minWidth = (int) (maxSize.getWidth() * mMinimumSizePercent);
+ int minHeight = (int) (maxSize.getHeight() * mMinimumSizePercent);
+
+ // make sure the calculated min size is not smaller than the allowed default min size
+ if (aspectRatio > 1f) {
+ minHeight = (int) Math.max(minHeight, mDefaultMinSize);
+ minWidth = (int) (minHeight * aspectRatio);
+ } else {
+ minWidth = (int) Math.max(minWidth, mDefaultMinSize);
+ minHeight = (int) (minWidth / aspectRatio);
+ }
+ return new Size(minWidth, minHeight);
+ }
+
+ /**
+ * Returns the size for target aspect ratio making sure new size conforms with the rules.
+ *
+ *
Recalculates the dimensions such that the target aspect ratio is achieved, while
+ * maintaining the same maximum size to current size ratio.
+ *
+ * @param size current size
+ * @param aspectRatio target aspect ratio
+ */
+ @Override
+ public Size getSizeForAspectRatio(Size size, float aspectRatio) {
+ float currAspectRatio = (float) size.getWidth() / size.getHeight();
+
+ // getting the percentage of the max size that current size takes
+ Size currentMaxSize = getMaxSize(currAspectRatio);
+ float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
+
+ // getting the max size for the target aspect ratio
+ Size updatedMaxSize = getMaxSize(aspectRatio);
+
+ int width = Math.round(updatedMaxSize.getWidth() * currentPercent);
+ int height = Math.round(updatedMaxSize.getHeight() * currentPercent);
+
+ // adjust the dimensions if below allowed min edge size
+ if (width < getMinEdgeSize() && aspectRatio <= 1) {
+ width = getMinEdgeSize();
+ height = Math.round(width / aspectRatio);
+ } else if (height < getMinEdgeSize() && aspectRatio > 1) {
+ height = getMinEdgeSize();
+ width = Math.round(height * aspectRatio);
+ }
+
+ // reduce the dimensions of the updated size to the calculated percentage
+ return new Size(width, height);
+ }
+ }
+
+ private class SizeSpecDefaultImpl implements SizeSpecSource {
+ private float mDefaultSizePercent;
+ private float mMinimumSizePercent;
+
+ @Override
+ public void reloadResources() {
+ final Resources res = mContext.getResources();
+
+ mMaxAspectRatioForMinSize = res.getFloat(
+ R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
+ mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
+
+ mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent);
+ mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
+ }
+
+ @Override
+ public Size getMaxSize(float aspectRatio) {
+ final int shorterLength = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height());
+
+ final int totalHorizontalPadding = getInsetBounds().left
+ + (getDisplayBounds().width() - getInsetBounds().right);
+ final int totalVerticalPadding = getInsetBounds().top
+ + (getDisplayBounds().height() - getInsetBounds().bottom);
+
+ final int maxWidth, maxHeight;
+
+ if (aspectRatio > 1f) {
+ maxWidth = (int) Math.max(getDefaultSize(aspectRatio).getWidth(),
+ shorterLength - totalHorizontalPadding);
+ maxHeight = (int) (maxWidth / aspectRatio);
+ } else {
+ maxHeight = (int) Math.max(getDefaultSize(aspectRatio).getHeight(),
+ shorterLength - totalVerticalPadding);
+ maxWidth = (int) (maxHeight * aspectRatio);
+ }
+
+ return new Size(maxWidth, maxHeight);
+ }
+
+ @Override
+ public Size getDefaultSize(float aspectRatio) {
+ if (mOverrideMinSize != null) {
+ return this.getMinSize(aspectRatio);
+ }
+
+ final int smallestDisplaySize = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height());
+ final int minSize = (int) Math.max(getMinEdgeSize(),
+ smallestDisplaySize * mDefaultSizePercent);
+
+ final int width;
+ final int height;
+
+ if (aspectRatio <= mMinAspectRatioForMinSize
+ || aspectRatio > mMaxAspectRatioForMinSize) {
+ // Beyond these points, we can just use the min size as the shorter edge
+ if (aspectRatio <= 1) {
+ // Portrait, width is the minimum size
+ width = minSize;
+ height = Math.round(width / aspectRatio);
+ } else {
+ // Landscape, height is the minimum size
+ height = minSize;
+ width = Math.round(height * aspectRatio);
+ }
+ } else {
+ // Within these points, ensure that the bounds fit within the radius of the limits
+ // at the points
+ final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
+ final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
+ height = (int) Math.round(Math.sqrt((radius * radius)
+ / (aspectRatio * aspectRatio + 1)));
+ width = Math.round(height * aspectRatio);
+ }
+
+ return new Size(width, height);
+ }
+
+ @Override
+ public Size getMinSize(float aspectRatio) {
+ if (mOverrideMinSize != null) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio);
+ }
+
+ final int shorterLength = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height());
+ final int minWidth, minHeight;
+
+ if (aspectRatio > 1f) {
+ minWidth = (int) Math.min(getDefaultSize(aspectRatio).getWidth(),
+ shorterLength * mMinimumSizePercent);
+ minHeight = (int) (minWidth / aspectRatio);
+ } else {
+ minHeight = (int) Math.min(getDefaultSize(aspectRatio).getHeight(),
+ shorterLength * mMinimumSizePercent);
+ minWidth = (int) (minHeight * aspectRatio);
+ }
+
+ return new Size(minWidth, minHeight);
+ }
+
+ @Override
+ public Size getSizeForAspectRatio(Size size, float aspectRatio) {
+ final int smallestSize = Math.min(size.getWidth(), size.getHeight());
+ final int minSize = Math.max(getMinEdgeSize(), smallestSize);
+
+ final int width;
+ final int height;
+ if (aspectRatio <= 1) {
+ // Portrait, width is the minimum size.
+ width = minSize;
+ height = Math.round(width / aspectRatio);
+ } else {
+ // Landscape, height is the minimum size
+ height = minSize;
+ width = Math.round(height * aspectRatio);
+ }
+
+ return new Size(width, height);
+ }
+ }
+
+ public PipSizeSpecHandler(Context context) {
+ mContext = context;
+
+ boolean enablePipSizeLargeScreen = SystemProperties
+ .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true);
+
+ // choose between two implementations of size spec logic
+ if (enablePipSizeLargeScreen) {
+ mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
+ } else {
+ mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
+ }
+
+ reloadResources();
+ }
+
+ /** Reloads the resources */
+ public void onConfigurationChanged() {
+ reloadResources();
+ }
+
+ private void reloadResources() {
+ final Resources res = mContext.getResources();
+
+ mDefaultMinSize = res.getDimensionPixelSize(
+ R.dimen.default_minimal_size_pip_resizable_task);
+ mOverridableMinSize = res.getDimensionPixelSize(
+ R.dimen.overridable_minimal_size_pip_resizable_task);
+
+ final String screenEdgeInsetsDpString = res.getString(
+ R.string.config_defaultPictureInPictureScreenEdgeInsets);
+ final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
+ ? Size.parseSize(screenEdgeInsetsDpString)
+ : null;
+ mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
+ : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
+ dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
+
+ // update the internal resources of the size spec source's stub
+ mSizeSpecSourceImpl.reloadResources();
+ }
+
+ /** Returns the display's bounds. */
+ @NonNull
+ public Rect getDisplayBounds() {
+ return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+ }
+
+ /** Update the display layout. */
+ public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
+ mDisplayLayout.set(displayLayout);
+ }
+
+ public Point getScreenEdgeInsets() {
+ return mScreenEdgeInsets;
+ }
+
+ /**
+ * Returns the inset bounds the PIP window can be visible in.
+ */
+ public Rect getInsetBounds() {
+ Rect insetBounds = new Rect();
+ Rect insets = mDisplayLayout.stableInsets();
+ insetBounds.set(insets.left + mScreenEdgeInsets.x,
+ insets.top + mScreenEdgeInsets.y,
+ mDisplayLayout.width() - insets.right - mScreenEdgeInsets.x,
+ mDisplayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+ return insetBounds;
+ }
+
+ /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
+ public void setOverrideMinSize(@Nullable Size overrideMinSize) {
+ mOverrideMinSize = overrideMinSize;
+ }
+
+ /** Returns the preferred minimal size specified by the activity in PIP. */
+ @Nullable
+ public Size getOverrideMinSize() {
+ if (mOverrideMinSize != null
+ && (mOverrideMinSize.getWidth() < mOverridableMinSize
+ || mOverrideMinSize.getHeight() < mOverridableMinSize)) {
+ return new Size(mOverridableMinSize, mOverridableMinSize);
+ }
+
+ return mOverrideMinSize;
+ }
+
+ /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
+ public int getOverrideMinEdgeSize() {
+ if (mOverrideMinSize == null) return 0;
+ return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight());
+ }
+
+ public int getMinEdgeSize() {
+ return mOverrideMinSize == null ? mDefaultMinSize : getOverrideMinEdgeSize();
+ }
+
+ /**
+ * Returns the size for the max size spec.
+ */
+ public Size getMaxSize(float aspectRatio) {
+ return mSizeSpecSourceImpl.getMaxSize(aspectRatio);
+ }
+
+ /**
+ * Returns the size for the default size spec.
+ */
+ public Size getDefaultSize(float aspectRatio) {
+ return mSizeSpecSourceImpl.getDefaultSize(aspectRatio);
+ }
+
+ /**
+ * Returns the size for the min size spec.
+ */
+ public Size getMinSize(float aspectRatio) {
+ return mSizeSpecSourceImpl.getMinSize(aspectRatio);
+ }
+
+ /**
+ * Returns the adjusted size so that it conforms to the given aspectRatio.
+ *
+ * @param size current size
+ * @param aspectRatio target aspect ratio
+ */
+ public Size getSizeForAspectRatio(@NonNull Size size, float aspectRatio) {
+ if (size.equals(mOverrideMinSize)) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio);
+ }
+
+ return mSizeSpecSourceImpl.getSizeForAspectRatio(size, aspectRatio);
+ }
+
+ /**
+ * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+ *
+ *
Overridden min size needs to be adjusted in its own way while making sure that the target
+ * aspect ratio is maintained
+ *
+ * @param aspectRatio target aspect ratio
+ */
+ @Nullable
+ @VisibleForTesting
+ Size adjustOverrideMinSizeToAspectRatio(float aspectRatio) {
+ if (mOverrideMinSize == null) {
+ return null;
+ }
+ final Size size = getOverrideMinSize();
+ final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
+ if (sizeAspectRatio > aspectRatio) {
+ // Size is wider, fix the width and increase the height
+ return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
+ } else {
+ // Size is taller, fix the height and adjust the width.
+ return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
+ }
+ }
+
+ /** Dumps internal state. */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl.toString());
+ pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
+ pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
+ }
+}
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 8896529503899096fdc26bf20b0ce72f2d2db698..8f6cee76b68ac8495a6575af19ecf6f1f0f7badb 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
@@ -71,14 +71,20 @@ public class PipTouchHandler {
private static final String TAG = "PipTouchHandler";
private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
- private static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+ private boolean mEnablePipKeepClearAlgorithm =
+ SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
+
+ @VisibleForTesting
+ void setEnablePipKeepClearAlgorithm(boolean value) {
+ mEnablePipKeepClearAlgorithm = value;
+ }
// Allow PIP to resize to a slightly bigger state upon touch
private boolean mEnableResize;
private final Context mContext;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
- private final @NonNull PipBoundsState mPipBoundsState;
+ @NonNull private final PipBoundsState mPipBoundsState;
+ @NonNull private final PipSizeSpecHandler mPipSizeSpecHandler;
private final PipUiEventLogger mPipUiEventLogger;
private final PipDismissTargetHandler mPipDismissTargetHandler;
private final PipTaskOrganizer mPipTaskOrganizer;
@@ -99,7 +105,6 @@ public class PipTouchHandler {
// The reference inset bounds, used to determine the dismiss fraction
private final Rect mInsetBounds = new Rect();
- private int mExpandedShortestEdgeSize;
// Used to workaround an issue where the WM rotation happens before we are notified, allowing
// us to send stale bounds
@@ -120,7 +125,6 @@ public class PipTouchHandler {
private float mSavedSnapFraction = -1f;
private boolean mSendingHoverAccessibilityEvents;
private boolean mMovementWithinDismiss;
- private float mMinimumSizePercent;
// Touch state
private final PipTouchState mTouchState;
@@ -174,6 +178,7 @@ public class PipTouchHandler {
PhonePipMenuController menuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler,
PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
@@ -184,6 +189,7 @@ public class PipTouchHandler {
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
mPipTaskOrganizer = pipTaskOrganizer;
mMenuController = menuController;
mPipUiEventLogger = pipUiEventLogger;
@@ -271,10 +277,7 @@ public class PipTouchHandler {
private void reloadResources() {
final Resources res = mContext.getResources();
mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
- mExpandedShortestEdgeSize = res.getDimensionPixelSize(
- R.dimen.pip_expanded_shortest_edge_size);
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
- mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
mPipDismissTargetHandler.updateMagneticTargetSize();
}
@@ -337,8 +340,10 @@ public class PipTouchHandler {
mMotionHelper.synchronizePinnedStackBounds();
reloadResources();
- // Recreate the dismiss target for the new orientation.
- mPipDismissTargetHandler.createOrUpdateDismissTarget();
+ if (mPipTaskOrganizer.isInPip()) {
+ // Recreate the dismiss target for the new orientation.
+ mPipDismissTargetHandler.createOrUpdateDismissTarget();
+ }
}
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -405,10 +410,7 @@ public class PipTouchHandler {
// Calculate the expanded size
float aspectRatio = (float) normalBounds.width() / normalBounds.height();
- Point displaySize = new Point();
- mContext.getDisplay().getRealSize(displaySize);
- Size expandedSize = mPipBoundsAlgorithm.getSizeForAspectRatio(
- aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
+ Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
mPipBoundsState.setExpandedBounds(
new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
Rect expandedMovementBounds = new Rect();
@@ -416,7 +418,7 @@ public class PipTouchHandler {
mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
bottomOffset);
- updatePipSizeConstraints(insetBounds, normalBounds, aspectRatio);
+ updatePipSizeConstraints(normalBounds, aspectRatio);
// The extra offset does not really affect the movement bounds, but are applied based on the
// current state (ime showing, or shelf offset) when we need to actually shift
@@ -430,7 +432,7 @@ public class PipTouchHandler {
if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
// Defer the update of the current movement bounds until after the user finishes
// touching the screen
- } else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
+ } else if (mEnablePipKeepClearAlgorithm) {
// Ignore moving PiP if keep clear algorithm is enabled, since IME and shelf height
// now are accounted for in the keep clear algorithm calculations
} else {
@@ -494,14 +496,14 @@ public class PipTouchHandler {
* @param aspectRatio aspect ratio to use for the calculation of min/max size
*/
public void updateMinMaxSize(float aspectRatio) {
- updatePipSizeConstraints(mInsetBounds, mPipBoundsState.getNormalBounds(),
+ updatePipSizeConstraints(mPipBoundsState.getNormalBounds(),
aspectRatio);
}
- private void updatePipSizeConstraints(Rect insetBounds, Rect normalBounds,
+ private void updatePipSizeConstraints(Rect normalBounds,
float aspectRatio) {
if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
- updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio);
+ updatePinchResizeSizeConstraints(aspectRatio);
} else {
mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
@@ -509,26 +511,13 @@ public class PipTouchHandler {
}
}
- private void updatePinchResizeSizeConstraints(Rect insetBounds, Rect normalBounds,
- float aspectRatio) {
- final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
- mPipBoundsState.getDisplayBounds().height());
- final int totalHorizontalPadding = insetBounds.left
- + (mPipBoundsState.getDisplayBounds().width() - insetBounds.right);
- final int totalVerticalPadding = insetBounds.top
- + (mPipBoundsState.getDisplayBounds().height() - insetBounds.bottom);
+ private void updatePinchResizeSizeConstraints(float aspectRatio) {
final int minWidth, minHeight, maxWidth, maxHeight;
- if (aspectRatio > 1f) {
- minWidth = (int) Math.min(normalBounds.width(), shorterLength * mMinimumSizePercent);
- minHeight = (int) (minWidth / aspectRatio);
- maxWidth = (int) Math.max(normalBounds.width(), shorterLength - totalHorizontalPadding);
- maxHeight = (int) (maxWidth / aspectRatio);
- } else {
- minHeight = (int) Math.min(normalBounds.height(), shorterLength * mMinimumSizePercent);
- minWidth = (int) (minHeight * aspectRatio);
- maxHeight = (int) Math.max(normalBounds.height(), shorterLength - totalVerticalPadding);
- maxWidth = (int) (maxHeight * aspectRatio);
- }
+
+ minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth();
+ minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight();
+ maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth();
+ maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight();
mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
@@ -1069,11 +1058,6 @@ public class PipTouchHandler {
mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
mMotionHelper.onMovementBoundsChanged();
-
- boolean isMenuExpanded = mMenuState == MENU_STATE_FULL;
- mPipBoundsState.setMinEdgeSize(
- isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize
- : mPipBoundsAlgorithm.getDefaultMinSize());
}
private Rect getMovementBounds(Rect curBounds) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index ce34d2f9547dbee34d034ad7ee29dff71e323de2..22feb43ffd62e1e642aff9877a29865ff730cd63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -39,8 +39,9 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -63,9 +64,10 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
public TvPipBoundsAlgorithm(Context context,
@NonNull TvPipBoundsState tvPipBoundsState,
- @NonNull PipSnapAlgorithm pipSnapAlgorithm) {
+ @NonNull PipSnapAlgorithm pipSnapAlgorithm,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
super(context, tvPipBoundsState, pipSnapAlgorithm,
- new PipKeepClearAlgorithm() {});
+ new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler);
this.mTvPipBoundsState = tvPipBoundsState;
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
reloadResources(context);
@@ -370,7 +372,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
- int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y)
+ int maxHeight = displayLayout.height()
+ - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y)
- pipDecorations.top - pipDecorations.bottom;
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
@@ -393,7 +396,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
- int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x)
+ int maxWidth = displayLayout.width()
+ - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x)
- pipDecorations.left - pipDecorations.right;
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index ca22882187d8feb50c6b1c3926df7be9846e24af..4e3ee51326c13213c1cbace45cd1f32a8214f1b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -30,6 +30,7 @@ import android.view.Gravity;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -64,8 +65,9 @@ public class TvPipBoundsState extends PipBoundsState {
private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
- public TvPipBoundsState(@NonNull Context context) {
- super(context);
+ public TvPipBoundsState(@NonNull Context context,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+ super(context, pipSizeSpecHandler);
mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 4e1b0469eb96079d87bb3f20b91f69674140d2ba..6bc666f074e9e4c52c9b94bf8aae7dc2d70b55ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -50,6 +50,7 @@ import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
@@ -102,6 +103,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final ShellController mShellController;
private final TvPipBoundsState mTvPipBoundsState;
+ private final PipSizeSpecHandler mPipSizeSpecHandler;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final TvPipBoundsController mTvPipBoundsController;
private final PipAppOpsListener mAppOpsListener;
@@ -133,6 +135,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -151,6 +154,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
shellInit,
shellController,
tvPipBoundsState,
+ pipSizeSpecHandler,
tvPipBoundsAlgorithm,
tvPipBoundsController,
pipAppOpsListener,
@@ -171,6 +175,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -189,9 +194,13 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mShellController = shellController;
mDisplayController = displayController;
+ DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
+
mTvPipBoundsState = tvPipBoundsState;
+ mTvPipBoundsState.setDisplayLayout(layout);
mTvPipBoundsState.setDisplayId(context.getDisplayId());
- mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
+ mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipSizeSpecHandler.setDisplayLayout(layout);
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mTvPipBoundsController = tvPipBoundsController;
mTvPipBoundsController.setListener(this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 42fd1aab44f8902152d37970eaeaaeb50c848c4c..be9b9361b3590d6d665c8370e902708bff146d98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -36,6 +36,7 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.Objects;
@@ -50,6 +51,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -61,10 +63,11 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
ShellExecutor mainExecutor) {
- super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, boundsHandler,
- pipMenuController, pipAnimationController, surfaceTransactionHelper,
- pipTransitionController, pipParamsChangedForwarder, splitScreenOptional,
- displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
+ boundsHandler, pipMenuController, pipAnimationController,
+ surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
+ mainExecutor);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 75f9a4c33af911935dd820d74d9a3f10aedf5953..c9b3a1af6507b9abb9481f6561009f149d51b3a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -50,6 +50,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL),
WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 8490f9f156c70d7d4b09f99ed1c59b2730e53d4f..0d9faa3c6f83cf2bceca8d2c9b0e5b395dd27a49 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
@@ -308,7 +308,6 @@ public class RecentTasksController implements TaskStackListenerCallback,
rawMapping.put(taskInfo.taskId, taskInfo);
}
- boolean desktopModeActive = DesktopModeStatus.isActive(mContext);
ArrayList freeformTasks = new ArrayList<>();
// Pull out the pairs as we iterate back in the list
@@ -320,7 +319,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
continue;
}
- if (desktopModeActive && mDesktopModeTaskRepository.isPresent()
+ if (DesktopModeStatus.isProto2Enabled() && mDesktopModeTaskRepository.isPresent()
&& mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
freeformTasks.add(taskInfo);
@@ -328,7 +327,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
final int pairedTaskId = mSplitTasks.get(taskInfo.taskId);
- if (!desktopModeActive && pairedTaskId != INVALID_TASK_ID && rawMapping.contains(
+ if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(
pairedTaskId)) {
final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
rawMapping.remove(pairedTaskId);
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 56aa742b8626f39ee3cdfa4cd01d8f22922a5c1c..81e118a31b73ffb36578c9c2ab1340e0f2fe63a6 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
@@ -122,9 +122,9 @@ interface ISplitScreen {
* Start a pair of intents using legacy transition system.
*/
oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1,
- in Bundle options1, in PendingIntent pendingIntent2, in Bundle options2,
- int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
- in InstanceId instanceId) = 18;
+ in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
+ in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
/**
* Start a pair of intents in one transition.
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 38099fc51d8153bd042dfee8e4fdf2bce0ba89b4..7d5ab8428a3e7b137524290d6657e45d38df1198 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -27,7 +28,9 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
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.common.split.SplitScreenUtils.isValidToSplit;
+import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
+import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
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;
@@ -38,11 +41,9 @@ import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
+import android.app.TaskInfo;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
@@ -81,13 +82,12 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -317,10 +317,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator;
}
- public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
- return mStageCoordinator.isValidToEnterSplitScreen(taskInfo);
- }
-
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) {
@@ -331,9 +327,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mTaskOrganizer.getRunningTaskInfo(taskId);
}
+ /** Check task is under split or not by taskId. */
public boolean isTaskInSplitScreen(int taskId) {
- return isSplitScreenVisible()
- && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+ return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+ }
+
+ /** Check split is foreground and task is under split or not by taskId. */
+ public boolean isTaskInSplitScreenForeground(int taskId) {
+ return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
}
public @SplitPosition int getSplitPosition(int taskId) {
@@ -341,8 +342,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
- return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition,
- new WindowContainerTransaction());
+ return moveToStage(taskId, sideStagePosition, new WindowContainerTransaction());
}
/**
@@ -353,13 +353,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.updateSurfaces(transaction);
}
- private boolean moveToStage(int taskId, @StageType int stageType,
- @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+ private boolean moveToStage(int taskId, @SplitPosition int stagePosition,
+ WindowContainerTransaction wct) {
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
if (task == null) {
throw new IllegalArgumentException("Unknown taskId" + taskId);
}
- return mStageCoordinator.moveToStage(task, stageType, stagePosition, wct);
+ return mStageCoordinator.moveToStage(task, stagePosition, wct);
}
public boolean removeFromSideStage(int taskId) {
@@ -384,10 +384,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
- final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE;
final int stagePosition =
leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
- moveToStage(taskId, stageType, stagePosition, wct);
+ moveToStage(taskId, stagePosition, wct);
}
public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
@@ -424,6 +423,19 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.goToFullscreenFromSplit();
}
+ /** Move the specified task to fullscreen, regardless of focus state. */
+ public void moveTaskToFullscreen(int taskId) {
+ mStageCoordinator.moveTaskToFullscreen(taskId);
+ }
+
+ public boolean isLaunchToSplit(TaskInfo taskInfo) {
+ return mStageCoordinator.isLaunchToSplit(taskInfo);
+ }
+
+ public int getActivateSplitPosition(TaskInfo taskInfo) {
+ return mStageCoordinator.getActivateSplitPosition(taskInfo);
+ }
+
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -479,39 +491,54 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user) {
- IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to invoke onAnimationFinished", e);
- }
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct);
- mSyncQueue.queue(evictWct);
+ if (options == null) options = new Bundle();
+ final ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+
+ if (samePackage(packageName, getPackageName(reverseSplitPosition(position)))) {
+ if (supportMultiInstancesSplit(packageName)) {
+ activityOptions.setApplyMultipleTaskFlagForShortcut(true);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition("startShortcut");
+ return;
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ return;
}
- @Override
- public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ }
+
+ mStageCoordinator.startShortcut(packageName, shortcutId, position,
+ activityOptions.toBundle(), user);
+ }
+
+ void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ if (options1 == null) options1 = new Bundle();
+ final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+
+ final String packageName1 = shortcutInfo.getPackage();
+ final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(shortcutInfo.getPackage())) {
+ activityOptions.setApplyMultipleTaskFlagForShortcut(true);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ taskId = INVALID_TASK_ID;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
}
- };
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
- null /* wct */);
- RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
- 0 /* duration */, 0 /* statusBarTransitionDelay */);
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
- try {
- LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
- launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
- activityOptions.toBundle(), user);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "Failed to launch shortcut", e);
}
+
+ mStageCoordinator.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
+ activityOptions.toBundle(), taskId, options2, splitPosition, splitRatio, adapter,
+ instanceId);
}
/**
@@ -529,23 +556,19 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
Intent fillInIntent = null;
- if (launchSameAppAdjacently(pendingIntent, taskId)) {
- if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
+ final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
- try {
- adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
- ActivityTaskManager.getService().startActivityFromRecents(taskId, options2);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
+ taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
- return;
}
}
mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
@@ -556,8 +579,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
Intent fillInIntent = null;
- if (launchSameAppAdjacently(pendingIntent, taskId)) {
- if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
+ final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -573,35 +598,32 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
- @Nullable Bundle options1, PendingIntent pendingIntent2,
- @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
Intent fillInIntent1 = null;
Intent fillInIntent2 = null;
- if (launchSameAppAdjacently(pendingIntent1, pendingIntent2)) {
- if (supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+ final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
+ final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
fillInIntent2 = new Intent();
fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
- try {
- adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
- pendingIntent1.send();
- } catch (RemoteException | PendingIntent.CanceledException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
+ pendingIntent2 = null;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
- return;
}
}
- mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
- pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
- instanceId);
+ mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1,
+ shortcutInfo1, options1, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
+ splitPosition, splitRatio, adapter, instanceId);
}
@Override
@@ -613,13 +635,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (fillInIntent == null) fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
- if (launchSameAppAdjacently(position, intent)) {
- final ComponentName launching = intent.getIntent().getComponent();
- if (supportMultiInstancesSplit(launching)) {
+ final String packageName1 = SplitScreenUtils.getPackageName(intent);
+ final String packageName2 = getPackageName(reverseSplitPosition(position));
+ if (SplitScreenUtils.samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
// To prevent accumulating large number of instances in the background, reuse task
// in the background with priority.
final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+ .map(recentTasks -> recentTasks.findTaskInBackground(
+ intent.getIntent().getComponent()))
.orElse(null);
if (taskInfo != null) {
startTask(taskInfo.taskId, position, options);
@@ -647,63 +671,32 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
+ /** Retrieve package name of a specific split position if split screen is activated, otherwise
+ * returns the package name of the top running task. */
@Nullable
- private String getPackageName(Intent intent) {
- if (intent == null || intent.getComponent() == null) {
- return null;
- }
- return intent.getComponent().getPackageName();
- }
-
- private boolean launchSameAppAdjacently(@SplitPosition int position,
- PendingIntent pendingIntent) {
- ActivityManager.RunningTaskInfo adjacentTaskInfo = null;
+ private String getPackageName(@SplitPosition int position) {
+ ActivityManager.RunningTaskInfo taskInfo;
if (isSplitScreenVisible()) {
- adjacentTaskInfo = getTaskInfo(SplitLayout.reversePosition(position));
+ taskInfo = getTaskInfo(position);
} else {
- adjacentTaskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null);
- if (!isValidToEnterSplitScreen(adjacentTaskInfo)) {
- return false;
+ taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.getTopRunningTask())
+ .orElse(null);
+ if (!isValidToSplit(taskInfo)) {
+ return null;
}
}
- if (adjacentTaskInfo == null) {
- return false;
- }
-
- final String targetPackageName = getPackageName(pendingIntent.getIntent());
- final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent);
- return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
- }
-
- private boolean launchSameAppAdjacently(PendingIntent pendingIntent, int taskId) {
- final ActivityManager.RunningTaskInfo adjacentTaskInfo =
- mTaskOrganizer.getRunningTaskInfo(taskId);
- if (adjacentTaskInfo == null) {
- return false;
- }
- final String targetPackageName = getPackageName(pendingIntent.getIntent());
- final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent);
- return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
- }
-
- private boolean launchSameAppAdjacently(PendingIntent pendingIntent1,
- PendingIntent pendingIntent2) {
- final String targetPackageName = getPackageName(pendingIntent1.getIntent());
- final String adjacentPackageName = getPackageName(pendingIntent2.getIntent());
- return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
+ return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null;
}
@VisibleForTesting
- /** Returns {@code true} if the component supports multi-instances split. */
- boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
- if (launching == null) return false;
-
- final String packageName = launching.getPackageName();
- for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
- if (mAppsSupportMultiInstances[i].equals(packageName)) {
- return true;
+ boolean supportMultiInstancesSplit(String packageName) {
+ if (packageName != null) {
+ for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
+ if (mAppsSupportMultiInstances[i].equals(packageName)) {
+ return true;
+ }
}
}
@@ -1022,7 +1015,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startShortcutAndTaskWithLegacyTransition", (controller) ->
- controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition(
+ controller.startShortcutAndTaskWithLegacyTransition(
shortcutInfo, options1, taskId, options2, splitPosition,
splitRatio, adapter, instanceId));
}
@@ -1060,13 +1053,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
- @Nullable Bundle options1, PendingIntent pendingIntent2, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
(controller) ->
- controller.startIntentsWithLegacyTransition(
- pendingIntent1, options1, pendingIntent2, options2, splitPosition,
+ controller.startIntentsWithLegacyTransition(pendingIntent1, shortcutInfo1,
+ options1, pendingIntent2, shortcutInfo2, options2, splitPosition,
splitRatio, adapter, 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 8ddc3c04d991ba41af89282b7a35755c8ac1febb..77939c7c69645c86f197f02b02d26c7a254ed2b4 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
@@ -36,12 +36,11 @@ import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -75,9 +74,12 @@ import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -87,6 +89,7 @@ import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import android.view.Choreographer;
@@ -122,6 +125,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
@@ -200,12 +204,43 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// and exit, since exit itself can trigger a number of changes that update the stages.
private boolean mShouldUpdateRecents;
private boolean mExitSplitScreenOnHide;
- private boolean mIsSplitEntering;
+ private boolean mIsDividerRemoteAnimating;
private boolean mIsDropEntering;
private boolean mIsExiting;
+ private boolean mIsRootTranslucent;
private DefaultMixedHandler mMixedHandler;
private final Toast mSplitUnsupportedToast;
+ private SplitRequest mSplitRequest;
+
+ class SplitRequest {
+ @SplitPosition
+ int mActivatePosition;
+ int mActivateTaskId;
+ int mActivateTaskId2;
+ Intent mStartIntent;
+ Intent mStartIntent2;
+
+ SplitRequest(int taskId, Intent startIntent, int position) {
+ mActivateTaskId = taskId;
+ mStartIntent = startIntent;
+ mActivatePosition = position;
+ }
+ SplitRequest(Intent startIntent, int position) {
+ mStartIntent = startIntent;
+ mActivatePosition = position;
+ }
+ SplitRequest(Intent startIntent, Intent startIntent2, int position) {
+ mStartIntent = startIntent;
+ mStartIntent2 = startIntent2;
+ mActivatePosition = position;
+ }
+ SplitRequest(int taskId1, int taskId2, int position) {
+ mActivateTaskId = taskId1;
+ mActivateTaskId2 = taskId2;
+ mActivatePosition = position;
+ }
+ }
private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
new SplitWindowManager.ParentContainerCallbacks() {
@@ -364,49 +399,43 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return STAGE_TYPE_UNDEFINED;
}
- boolean moveToStage(ActivityManager.RunningTaskInfo task, @StageType int stageType,
- @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+ boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
+ WindowContainerTransaction wct) {
StageTaskListener targetStage;
int sideStagePosition;
- if (stageType == STAGE_TYPE_MAIN) {
- targetStage = mMainStage;
- sideStagePosition = SplitLayout.reversePosition(stagePosition);
- } else if (stageType == STAGE_TYPE_SIDE) {
+ if (isSplitScreenVisible()) {
+ // If the split screen is foreground, retrieves target stage based on position.
+ targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
+ sideStagePosition = mSideStagePosition;
+ } else {
targetStage = mSideStage;
sideStagePosition = stagePosition;
- } else {
- if (isSplitScreenVisible()) {
- // If the split screen is activated, retrieves target stage based on position.
- targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
- sideStagePosition = mSideStagePosition;
- } else {
- // Exit split if it running background.
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
-
- targetStage = mSideStage;
- sideStagePosition = stagePosition;
- }
}
- setSideStagePosition(sideStagePosition, wct);
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- targetStage.evictAllChildren(evictWct);
- targetStage.addTask(task, wct);
-
- if (ENABLE_SHELL_TRANSITIONS) {
- prepareEnterSplitScreen(wct);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
- null, this, null /* consumedCallback */, (finishWct, finishT) -> {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
- } /* finishedCallback */);
+ if (!isSplitActive()) {
+ mSplitLayout.init();
+ prepareEnterSplitScreen(wct, task, stagePosition);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ });
} else {
- if (!evictWct.isEmpty()) {
- wct.merge(evictWct, true /* transfer */);
+ setSideStagePosition(sideStagePosition, wct);
+ targetStage.addTask(task, wct);
+ targetStage.evictAllChildren(wct);
+ if (!isSplitScreenVisible()) {
+ final StageTaskListener anotherStage = targetStage == mMainStage
+ ? mSideStage : mMainStage;
+ anotherStage.reparentTopTask(wct);
+ anotherStage.evictAllChildren(wct);
+ wct.reorder(mRootTaskInfo.token, true);
}
- mTaskOrganizer.applyTransaction(wct);
+ setRootForceTranslucent(false, wct);
+ mSyncQueue.queue(wct);
}
+
+ // Due to drag already pip task entering split by this method so need to reset flag here.
+ mIsDropEntering = false;
return true;
}
@@ -428,6 +457,81 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return mLogger;
}
+ void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
+ Bundle options, UserHandle user) {
+ final boolean isEnteringSplit = !isSplitActive();
+
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ boolean openingToSide = false;
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING
+ && mSideStage.containsTask(apps[i].taskId)) {
+ openingToSide = true;
+ break;
+ }
+ }
+ } else if (mSideStage.getChildCount() != 0) {
+ // There are chances the entering app transition got canceled by performing
+ // rotation transition. Checks if there is any child task existed in split
+ // screen before fallback to cancel entering flow.
+ openingToSide = true;
+ }
+
+ if (isEnteringSplit && !openingToSide) {
+ mMainExecutor.execute(() -> exitSplitScreen(
+ mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+ EXIT_REASON_UNKNOWN));
+ }
+
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing legacy transition: ", e);
+ }
+ }
+
+ if (!isEnteringSplit && apps != null) {
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+ }
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ if (isEnteringSplit) {
+ mMainExecutor.execute(() -> exitSplitScreen(
+ mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+ EXIT_REASON_UNKNOWN));
+ }
+ }
+ };
+ options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
+ null /* wct */);
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
+ 0 /* duration */, 0 /* statusBarTransitionDelay */);
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the current
+ // top activity since it's going to be put into another side of the split. This prevents the
+ // current top activity from going into pip mode due to user leaving event.
+ activityOptions.setApplyNoUserActionFlagForShortcut(true);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ try {
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+ activityOptions.toBundle(), user);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to launch shortcut", e);
+ }
+ }
+
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
@@ -477,9 +581,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
break;
}
}
+ } else if (mSideStage.getChildCount() != 0) {
+ // There are chances the entering app transition got canceled by performing
+ // rotation transition. Checks if there is any child task existed in split
+ // screen before fallback to cancel entering flow.
+ openingToSide = true;
}
- if (isEnteringSplit && !openingToSide) {
+ if (isEnteringSplit && !openingToSide && apps != null) {
mMainExecutor.execute(() -> exitSplitScreen(
mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
EXIT_REASON_UNKNOWN));
@@ -503,7 +612,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
- if (!isEnteringSplit && openingToSide) {
+ if (!isEnteringSplit && apps != null) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictNonOpeningChildTasks(position, apps, evictWct);
mSyncQueue.queue(evictWct);
@@ -519,7 +628,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
updateWindowBounds(mSplitLayout, wct);
}
-
+ mSplitRequest = new SplitRequest(intent.getIntent(), position);
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
@@ -585,7 +694,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDivideRatio(splitRatio);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
// Make sure the launch options will put tasks in the corresponding split roots
mainOptions = mainOptions != null ? mainOptions : new Bundle();
@@ -605,25 +714,54 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
+ if (taskId2 == INVALID_TASK_ID) {
+ // Launching a solo task.
+ // Exit split first if this task under split roots.
+ if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+ }
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
+ options1 = activityOptions.toBundle();
+ addActivityOptions(options1, null /* launchTarget */);
+ wct.startTask(taskId1, options1);
+ mSyncQueue.queue(wct);
+ return;
+ }
+
addActivityOptions(options1, mSideStage);
wct.startTask(taskId1, options1);
-
+ mSplitRequest = new SplitRequest(taskId1, taskId2, splitPosition);
startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
instanceId);
}
/** Starts a pair of intents using legacy transition. */
void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1,
- @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ @Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
- addActivityOptions(options1, mSideStage);
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ if (pendingIntent2 == null) {
+ // Launching a solo intent or shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent1, fillInIntent1, shortcutInfo1,
+ options1, adapter, wct);
+ return;
+ }
- startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
- splitRatio, adapter, instanceId);
+ addActivityOptions(options1, mSideStage);
+ if (shortcutInfo1 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+ } else {
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ mSplitRequest = new SplitRequest(pendingIntent1.getIntent(),
+ pendingIntent2 != null ? pendingIntent2.getIntent() : null, splitPosition);
+ }
+ startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
+ splitPosition, splitRatio, adapter, instanceId);
}
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@@ -632,9 +770,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
+ if (taskId == INVALID_TASK_ID) {
+ // Launching a solo intent as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent, fillInIntent, null, options1,
+ adapter, wct);
+ return;
+ }
+
addActivityOptions(options1, mSideStage);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
-
+ mSplitRequest = new SplitRequest(taskId, pendingIntent.getIntent(), splitPosition);
startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
@@ -646,27 +791,76 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
+ if (taskId == INVALID_TASK_ID) {
+ // Launching a solo shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(null, null, shortcutInfo, options1, adapter, wct);
+ return;
+ }
+
addActivityOptions(options1, mSideStage);
wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
-
startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
+ private void launchAsFullscreenWithRemoteAnimation(@Nullable PendingIntent pendingIntent,
+ @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo,
+ @Nullable Bundle options, RemoteAnimationAdapter adapter,
+ WindowContainerTransaction wct) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ t.apply();
+
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ addActivityOptions(options, null /* launchTarget */);
+ if (shortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options);
+ } else if (pendingIntent != null) {
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options);
+ } else {
+ Slog.e(TAG, "Pending intent and shortcut are null is invalid case.");
+ }
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
private void startWithLegacyTransition(WindowContainerTransaction wct,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
- @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
- mainOptions, sidePosition, splitRatio, adapter, instanceId);
+ mainShortcutInfo, mainOptions, sidePosition, splitRatio, adapter, instanceId);
}
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
@Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
- null /* mainFillInIntent */, mainOptions, sidePosition, splitRatio, adapter,
- instanceId);
+ null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition,
+ splitRatio, adapter, instanceId);
}
/**
@@ -676,8 +870,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
*/
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
- @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (!isSplitScreenVisible()) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
}
@@ -694,8 +889,46 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Set false to avoid record new bounds with old task still on top;
mShouldUpdateRecents = false;
- mIsSplitEntering = true;
+ mIsDividerRemoteAnimating = true;
+ if (mSplitRequest == null) {
+ mSplitRequest = new SplitRequest(mainTaskId,
+ mainPendingIntent != null ? mainPendingIntent.getIntent() : null,
+ sidePosition);
+ }
+ setSideStagePosition(sidePosition, wct);
+ if (!mMainStage.isActive()) {
+ mMainStage.activate(wct, false /* reparent */);
+ }
+ if (options == null) options = new Bundle();
+ addActivityOptions(options, mMainStage);
+
+ updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ setRootForceTranslucent(false, wct);
+
+ // TODO(b/268008375): Merge APIs to start a split pair into one.
+ if (mainTaskId != INVALID_TASK_ID) {
+ options = wrapAsSplitRemoteAnimation(adapter, options);
+ wct.startTask(mainTaskId, options);
+ mSyncQueue.queue(wct);
+ } else {
+ if (mainShortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
+ } else {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
+ }
+ mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
+ }
+
+ mSyncQueue.runInSync(t -> {
+ setDividerVisibility(true, t);
+ });
+
+ setEnterInstanceId(instanceId);
+ }
+
+ private Bundle wrapAsSplitRemoteAnimation(RemoteAnimationAdapter adapter, Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
if (isSplitScreenVisible()) {
mMainStage.evictAllChildren(evictWct);
@@ -713,7 +946,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() throws RemoteException {
- onRemoteAnimationFinishedOrCancelled(false /* cancel */, evictWct);
+ onRemoteAnimationFinishedOrCancelled(evictWct);
finishedCallback.onAnimationFinished();
}
};
@@ -729,7 +962,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
- onRemoteAnimationFinishedOrCancelled(true /* cancel */, evictWct);
+ onRemoteAnimationFinishedOrCancelled(evictWct);
try {
adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
} catch (RemoteException e) {
@@ -739,37 +972,57 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
};
RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ return activityOptions.toBundle();
+ }
+
+ private LegacyTransitions.ILegacyTransition wrapAsSplitRemoteAnimation(
+ RemoteAnimationAdapter adapter) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
- if (mainOptions == null) {
- mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
- } else {
- ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
- mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
- mainOptions = mainActivityOptions.toBundle();
- }
-
- setSideStagePosition(sidePosition, wct);
- if (!mMainStage.isActive()) {
- mMainStage.activate(wct, false /* reparent */);
- }
-
- if (mainOptions == null) mainOptions = new Bundle();
- addActivityOptions(mainOptions, mMainStage);
- updateWindowBounds(mSplitLayout, wct);
- if (mainTaskId == INVALID_TASK_ID) {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
- } else {
- wct.startTask(mainTaskId, mainOptions);
- }
- wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ // Wrap the divider bar into non-apps target to animate together.
+ nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+ getDividerBarLegacyTarget());
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- setDividerVisibility(true, t);
- });
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ // Reset the surface position of the opening app to prevent offset.
+ t.setPosition(apps[i].leash, 0, 0);
+ }
+ }
+ t.apply();
+
+ IRemoteAnimationFinishedCallback wrapCallback =
+ new IRemoteAnimationFinishedCallback.Stub() {
+ @Override
+ public void onAnimationFinished() throws RemoteException {
+ onRemoteAnimationFinished(apps);
+ finishedCallback.onAnimationFinished();
+ }
+ };
+ Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, wrapCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
- setEnterInstanceId(instanceId);
+ return transition;
}
private void setEnterInstanceId(InstanceId instanceId) {
@@ -778,14 +1031,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- private void onRemoteAnimationFinishedOrCancelled(boolean cancel,
- WindowContainerTransaction evictWct) {
- mIsSplitEntering = false;
+ private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) {
+ mIsDividerRemoteAnimating = false;
mShouldUpdateRecents = true;
+ mSplitRequest = null;
// If any stage has no child after animation finished, it means that split will display
// nothing, such status will happen if task and intent is same app but not support
// multi-instance, we should exit split and expand that app as full screen.
- if (!cancel && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
@@ -798,6 +1051,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) {
+ mIsDividerRemoteAnimating = false;
+ mShouldUpdateRecents = true;
+ mSplitRequest = null;
+ // If any stage has no child after finished animation, that side of the split will display
+ // nothing. This might happen if starting the same app on the both sides while not
+ // supporting multi-instance. Exit the split screen and expand that app to full screen.
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
+ ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
+ return;
+ }
+
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct);
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+
+
/**
* Collects all the current child tasks of a specific split and prepares transaction to evict
* them to display.
@@ -859,7 +1133,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
case STAGE_TYPE_MAIN: {
if (position != SPLIT_POSITION_UNDEFINED) {
// Set the side stage opposite of what we want to the main stage.
- setSideStagePosition(SplitLayout.reversePosition(position), wct);
+ setSideStagePosition(reverseSplitPosition(position), wct);
} else {
position = getMainStagePosition();
}
@@ -883,7 +1157,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@SplitPosition
int getMainStagePosition() {
- return SplitLayout.reversePosition(mSideStagePosition);
+ return reverseSplitPosition(mSideStagePosition);
}
int getTaskId(@SplitPosition int splitPosition) {
@@ -910,7 +1184,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,
insets -> {
WindowContainerTransaction wct = new WindowContainerTransaction();
- setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), wct);
+ setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(st -> {
updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
@@ -1045,13 +1319,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
});
mShouldUpdateRecents = false;
- mIsSplitEntering = false;
+ mIsDividerRemoteAnimating = false;
mSplitLayout.getInvisibleBounds(mTempRect1);
if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ setRootForceTranslucent(true, wct);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
@@ -1083,6 +1358,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
+ setRootForceTranslucent(true, finishedWCT);
finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(finishedWCT);
mSyncQueue.runInSync(at -> {
@@ -1194,7 +1470,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
}
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
@@ -1228,8 +1504,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return SPLIT_POSITION_UNDEFINED;
}
- private void addActivityOptions(Bundle opts, StageTaskListener stage) {
- opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+ private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) {
+ if (launchTarget != null) {
+ opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, launchTarget.mRootTaskInfo.token);
+ }
// Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
// will be canceled.
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
@@ -1374,7 +1652,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
&& !ENABLE_SHELL_TRANSITIONS) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config.
- mIsSplitEntering = false;
+ mIsDividerRemoteAnimating = false;
mSplitLayout.update(null /* t */);
onLayoutSizeChanged(mSplitLayout);
}
@@ -1396,6 +1674,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRootTaskInfo = null;
mRootTaskLeash = null;
+ mIsRootTranslucent = false;
}
@@ -1414,7 +1693,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
mSplitLayout.getInvisibleBounds(mTempRect1);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(wct);
@@ -1424,9 +1703,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
+ // Handle entering split screen while there is a split pair running in the background.
if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
- && !mIsSplitEntering) {
- // Handle entring split case here if split already running background.
+ && mSplitRequest == null) {
if (mIsDropEntering) {
mSplitLayout.resetDividerPosition();
} else {
@@ -1438,7 +1717,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.evictOtherChildren(wct, taskId);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -1462,7 +1741,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout);
}
+ private void setRootForceTranslucent(boolean translucent, WindowContainerTransaction wct) {
+ if (mIsRootTranslucent == translucent) return;
+
+ mIsRootTranslucent = translucent;
+ wct.setForceTranslucent(mRootTaskInfo.token, translucent);
+ }
+
private void onStageVisibilityChanged(StageListenerImpl stageListener) {
+ // If split didn't active, just ignore this callback because we should already did these
+ // on #applyExitSplitScreen.
+ if (!isSplitActive()) {
+ return;
+ }
+
final boolean sideStageVisible = mSideStageListener.mVisible;
final boolean mainStageVisible = mMainStageListener.mVisible;
@@ -1471,24 +1763,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
+ // Check if it needs to dismiss split screen when both stage invisible.
+ if (!mainStageVisible && mExitSplitScreenOnHide) {
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
+ return;
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (!mainStageVisible) {
+ // Split entering background.
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
true /* setReparentLeafTaskIfRelaunch */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
- // Both stages are not visible, check if it needs to dismiss split screen.
- if (mExitSplitScreenOnHide) {
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
- }
+ setRootForceTranslucent(true, wct);
} else {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* setReparentLeafTaskIfRelaunch */);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
}
+
mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- setDividerVisibility(mainStageVisible, t);
- });
+ setDividerVisibility(mainStageVisible, null);
}
private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
@@ -1511,7 +1805,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDividerVisible = visible;
sendSplitVisibilityChanged();
- if (mIsSplitEntering) {
+ if (mIsDividerRemoteAnimating) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to it's remote animating.");
return;
@@ -1531,7 +1825,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
" Skip animating divider bar due to divider leash not ready.");
return;
}
- if (mIsSplitEntering) {
+ if (mIsDividerRemoteAnimating) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to it's remote animating.");
return;
@@ -1570,6 +1864,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onAnimationEnd(Animator animation) {
+ if (dividerLeash != null && dividerLeash.isValid()) {
+ transaction.setAlpha(dividerLeash, 1);
+ transaction.apply();
+ }
mTransactionPool.release(transaction);
mDividerFadeInAnimator = null;
}
@@ -1595,14 +1893,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.flingDividerToDismiss(
mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
EXIT_REASON_APP_FINISHED);
- } else if (!isSplitScreenVisible() && !mIsSplitEntering) {
+ } else if (!isSplitScreenVisible() && mSplitRequest == null) {
+ // Dismiss split screen in the background once any sides of the split become empty.
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
mSplitLayout.init();
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (mLogger.isEnterRequestedByDrag()) {
+ if (mIsDropEntering) {
prepareEnterSplitScreen(wct);
} else {
// TODO (b/238697912) : Add the validation to prevent entering non-recovered status
@@ -1611,7 +1910,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
}
mSyncQueue.queue(wct);
@@ -1627,6 +1926,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
+ mSplitRequest = null;
updateRecentTasksSplitPair();
if (!mLogger.hasStartedSession()) {
@@ -1641,12 +1941,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
- return taskInfo.supportsMultiWindow
- && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
- && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
- }
-
@Override
public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
final boolean mainStageToTop =
@@ -2185,6 +2479,47 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
}
+ /** Move the specified task to fullscreen, regardless of focus state. */
+ public void moveTaskToFullscreen(int taskId) {
+ boolean leftOrTop;
+ if (mMainStage.containsTask(taskId)) {
+ leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ } else if (mSideStage.containsTask(taskId)) {
+ leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ } else {
+ return;
+ }
+ mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+
+ }
+
+ boolean isLaunchToSplit(TaskInfo taskInfo) {
+ return getActivateSplitPosition(taskInfo) != SPLIT_POSITION_UNDEFINED;
+ }
+
+ int getActivateSplitPosition(TaskInfo taskInfo) {
+ if (mSplitRequest == null || taskInfo == null) {
+ return SPLIT_POSITION_UNDEFINED;
+ }
+ if (mSplitRequest.mActivateTaskId != 0
+ && mSplitRequest.mActivateTaskId2 == taskInfo.taskId) {
+ return mSplitRequest.mActivatePosition;
+ }
+ if (mSplitRequest.mActivateTaskId == taskInfo.taskId) {
+ return mSplitRequest.mActivatePosition;
+ }
+ final String packageName1 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent);
+ final String basePackageName = SplitScreenUtils.getPackageName(taskInfo.baseIntent);
+ if (packageName1 != null && packageName1.equals(basePackageName)) {
+ return mSplitRequest.mActivatePosition;
+ }
+ final String packageName2 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent2);
+ if (packageName2 != null && packageName2.equals(basePackageName)) {
+ return mSplitRequest.mActivatePosition;
+ }
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
/** Synchronize split-screen state with transition and make appropriate preparations. */
public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index a841b7f96d3c5fa6765f18dba19aafafee2f6d1e..ead0bcd15c73b864fb2b3884a2852e585f05ed25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -220,12 +220,20 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mCallbacks.onNoLongerSupportMultiWindow();
return;
}
- mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ if (taskInfo.topActivity == null && mChildrenTaskInfo.contains(taskInfo.taskId)
+ && mChildrenTaskInfo.get(taskInfo.taskId).topActivity != null) {
+ // If top activity become null, it means the task is about to vanish, we use this
+ // signal to remove it from children list earlier for smooth dismiss transition.
+ mChildrenTaskInfo.remove(taskInfo.taskId);
+ mChildrenLeashes.remove(taskInfo.taskId);
+ } else {
+ mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ }
mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
taskInfo.isVisible);
- if (!ENABLE_SHELL_TRANSITIONS) {
- updateChildTaskSurface(
- taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+ if (!ENABLE_SHELL_TRANSITIONS && mChildrenLeashes.contains(taskInfo.taskId)) {
+ updateChildTaskSurface(taskInfo, mChildrenLeashes.get(taskInfo.taskId),
+ false /* firstAppeared */);
}
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
@@ -259,9 +267,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return;
}
sendStatusChanged();
- } else {
- throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
- + "\n mRootTaskInfo: " + mRootTaskInfo);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
index 86ca292399cbdd151987fae3215059c681e5060a..fe0a3fb7b9dc3c2104fb30fe412b901a9dccfaab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
@@ -79,7 +79,7 @@ public class UnfoldBackgroundController {
}
private float[] getBackgroundColor(Context context) {
- int colorInt = context.getResources().getColor(R.color.taskbar_background);
+ int colorInt = context.getResources().getColor(R.color.unfold_background);
return new float[]{
(float) red(colorInt) / 255.0F,
(float) green(colorInt) / 255.0F,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index eab82f00e9627950dd1751d3b8b5bdec15e27e2d..e0f3fcd932c212187cf6638a7eddc683235b7970 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -26,8 +26,10 @@ import android.animation.TypeEvaluator;
import android.annotation.NonNull;
import android.app.TaskInfo;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.Rect;
+import android.os.Trace;
import android.util.SparseArray;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -36,6 +38,8 @@ import android.view.SurfaceControl.Transaction;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
@@ -51,7 +55,7 @@ import com.android.wm.shell.unfold.UnfoldBackgroundController;
* instances of FullscreenUnfoldTaskAnimator.
*/
public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
- DisplayInsetsController.OnInsetsChangedListener {
+ DisplayInsetsController.OnInsetsChangedListener, ConfigurationChangeListener {
private static final float[] FLOAT_9 = new float[9];
private static final TypeEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
@@ -63,17 +67,21 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
private final SparseArray mAnimationContextByTaskId = new SparseArray<>();
private final int mExpandedTaskBarHeight;
- private final float mWindowCornerRadiusPx;
private final DisplayInsetsController mDisplayInsetsController;
private final UnfoldBackgroundController mBackgroundController;
+ private final Context mContext;
+ private final ShellController mShellController;
private InsetsSource mTaskbarInsetsSource;
+ private float mWindowCornerRadiusPx;
public FullscreenUnfoldTaskAnimator(Context context,
@NonNull UnfoldBackgroundController backgroundController,
- DisplayInsetsController displayInsetsController) {
+ ShellController shellController, DisplayInsetsController displayInsetsController) {
+ mContext = context;
mDisplayInsetsController = displayInsetsController;
mBackgroundController = backgroundController;
+ mShellController = shellController;
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.taskbar_frame_height);
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
@@ -81,6 +89,14 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
public void init() {
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+ mShellController.addConfigurationChangeListener(this);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ Trace.beginSection("FullscreenUnfoldTaskAnimator#onConfigurationChanged");
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
+ Trace.endSection();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index 6e10ebe94c5d38ea4e27d5b698bf69f9a00b2eff..addd0a6012c444aac9897349dd4ec9bad76fd078 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -28,8 +28,10 @@ import android.animation.RectEvaluator;
import android.animation.TypeEvaluator;
import android.app.TaskInfo;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.os.Trace;
import android.util.SparseArray;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -42,6 +44,8 @@ import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
@@ -62,16 +66,18 @@ import dagger.Lazy;
* They use independent instances of SplitTaskUnfoldAnimator.
*/
public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
- DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener {
+ DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener,
+ ConfigurationChangeListener {
private static final TypeEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
+ private final Context mContext;
private final Executor mExecutor;
private final DisplayInsetsController mDisplayInsetsController;
private final SparseArray mAnimationContextByTaskId = new SparseArray<>();
private final int mExpandedTaskBarHeight;
- private final float mWindowCornerRadiusPx;
+ private final ShellController mShellController;
private final Lazy> mSplitScreenController;
private final UnfoldBackgroundController mUnfoldBackgroundController;
@@ -79,6 +85,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
private final Rect mSideStageBounds = new Rect();
private final Rect mRootStageBounds = new Rect();
+ private float mWindowCornerRadiusPx;
private InsetsSource mTaskbarInsetsSource;
@SplitPosition
@@ -88,10 +95,12 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
public SplitTaskUnfoldAnimator(Context context, Executor executor,
Lazy> splitScreenController,
- UnfoldBackgroundController unfoldBackgroundController,
+ ShellController shellController, UnfoldBackgroundController unfoldBackgroundController,
DisplayInsetsController displayInsetsController) {
mDisplayInsetsController = displayInsetsController;
mExecutor = executor;
+ mContext = context;
+ mShellController = shellController;
mUnfoldBackgroundController = unfoldBackgroundController;
mSplitScreenController = splitScreenController;
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
@@ -103,6 +112,14 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
@Override
public void init() {
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+ mShellController.addConfigurationChangeListener(this);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ Trace.beginSection("SplitTaskUnfoldAnimator#onConfigurationChanged");
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
+ Trace.endSection();
}
/**
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
new file mode 100644
index 0000000000000000000000000000000000000000..6b7ca421f5ed49c880cbb986a23d35afe680d9c8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.os.Handler;
+import android.util.SparseArray;
+import android.view.Choreographer;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.transition.Transitions;
+
+/**
+ * View model for the window decoration with a caption and shadows. Works with
+ * {@link CaptionWindowDecoration}.
+ */
+public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final Context mContext;
+ private final Handler mMainHandler;
+ private final Choreographer mMainChoreographer;
+ private final DisplayController mDisplayController;
+ private final SyncTransactionQueue mSyncQueue;
+ private TaskOperations mTaskOperations;
+
+ private final SparseArray mWindowDecorByTaskId = new SparseArray<>();
+
+ public CaptionWindowDecorViewModel(
+ Context context,
+ Handler mainHandler,
+ Choreographer mainChoreographer,
+ ShellTaskOrganizer taskOrganizer,
+ DisplayController displayController,
+ SyncTransactionQueue syncQueue) {
+ mContext = context;
+ mMainHandler = mainHandler;
+ mMainChoreographer = mainChoreographer;
+ mTaskOrganizer = taskOrganizer;
+ mDisplayController = displayController;
+ mSyncQueue = syncQueue;
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
+ }
+ }
+
+ @Override
+ public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
+ mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
+ }
+
+ @Override
+ public boolean onTaskOpening(
+ RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ if (!shouldShowWindowDecor(taskInfo)) return false;
+ createWindowDecoration(taskInfo, taskSurface, startT, finishT);
+ return true;
+ }
+
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
+ if (decoration == null) return;
+
+ decoration.relayout(taskInfo);
+ setupCaptionColor(taskInfo, decoration);
+ }
+
+ @Override
+ public void onTaskChanging(
+ RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
+ if (!shouldShowWindowDecor(taskInfo)) {
+ if (decoration != null) {
+ destroyWindowDecoration(taskInfo);
+ }
+ return;
+ }
+
+ if (decoration == null) {
+ createWindowDecoration(taskInfo, taskSurface, startT, finishT);
+ } else {
+ decoration.relayout(taskInfo, startT, finishT);
+ }
+ }
+
+ @Override
+ public void onTaskClosing(
+ RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (decoration == null) return;
+
+ decoration.relayout(taskInfo, startT, finishT);
+ }
+
+ @Override
+ public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
+ final CaptionWindowDecoration decoration =
+ mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+ if (decoration == null) return;
+
+ decoration.close();
+ }
+
+ private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
+ final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
+ decoration.setCaptionColor(statusBarColor);
+ }
+
+ private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+ return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ || (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+ && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode()
+ == WINDOWING_MODE_FREEFORM);
+ }
+
+ private void createWindowDecoration(
+ RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ final CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (oldDecoration != null) {
+ // close the old decoration if it exists to avoid two window decorations being added
+ oldDecoration.close();
+ }
+ final CaptionWindowDecoration windowDecoration =
+ new CaptionWindowDecoration(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ taskSurface,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+ mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+
+ final TaskPositioner taskPositioner =
+ new TaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController);
+ final CaptionTouchEventListener touchEventListener =
+ new CaptionTouchEventListener(taskInfo, taskPositioner);
+ windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
+ windowDecoration.setDragPositioningCallback(taskPositioner);
+ windowDecoration.setDragDetector(touchEventListener.mDragDetector);
+ windowDecoration.relayout(taskInfo, startT, finishT);
+ setupCaptionColor(taskInfo, windowDecoration);
+ }
+
+ private class CaptionTouchEventListener implements
+ View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
+
+ private final int mTaskId;
+ private final WindowContainerToken mTaskToken;
+ private final DragPositioningCallback mDragPositioningCallback;
+ private final DragDetector mDragDetector;
+
+ private int mDragPointerId = -1;
+ private boolean mIsDragging;
+
+ private CaptionTouchEventListener(
+ RunningTaskInfo taskInfo,
+ DragPositioningCallback dragPositioningCallback) {
+ mTaskId = taskInfo.taskId;
+ mTaskToken = taskInfo.token;
+ mDragPositioningCallback = dragPositioningCallback;
+ mDragDetector = new DragDetector(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ final int id = v.getId();
+ if (id == R.id.close_window) {
+ mTaskOperations.closeTask(mTaskToken);
+ } else if (id == R.id.back_button) {
+ mTaskOperations.injectBackKey();
+ } else if (id == R.id.minimize_window) {
+ mTaskOperations.minimizeTask(mTaskToken);
+ } else if (id == R.id.maximize_window) {
+ RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ mTaskOperations.maximizeTask(taskInfo);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent e) {
+ if (v.getId() != R.id.caption) {
+ return false;
+ }
+ if (e.getAction() == MotionEvent.ACTION_DOWN) {
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ if (!taskInfo.isFocused) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(mTaskToken, true /* onTop */);
+ mSyncQueue.queue(wct);
+ }
+ }
+ return mDragDetector.onMotionEvent(e);
+ }
+
+ /**
+ * @param e {@link MotionEvent} to process
+ * @return {@code true} if a drag is happening; or {@code false} if it is not
+ */
+ @Override
+ public boolean handleMotionEvent(MotionEvent e) {
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ return false;
+ }
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ mDragPointerId = e.getPointerId(0);
+ mDragPositioningCallback.onDragPositioningStart(
+ 0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
+ mIsDragging = false;
+ return false;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ mDragPositioningCallback.onDragPositioningMove(
+ e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ mIsDragging = true;
+ return true;
+ }
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ mDragPositioningCallback.onDragPositioningEnd(
+ e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ final boolean wasDragging = mIsDragging;
+ mIsDragging = false;
+ return wasDragging;
+ }
+ }
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
new file mode 100644
index 0000000000000000000000000000000000000000..060dc4e05b46257fc2cb8a2b431200bbb7d4496d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+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;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
+ * {@link CaptionWindowDecorViewModel}. The caption bar contains a back button, minimize button,
+ * maximize button and close button.
+ */
+public class CaptionWindowDecoration extends WindowDecoration {
+ private final Handler mHandler;
+ private final Choreographer mChoreographer;
+ private final SyncTransactionQueue mSyncQueue;
+
+ private View.OnClickListener mOnCaptionButtonClickListener;
+ private View.OnTouchListener mOnCaptionTouchListener;
+ private DragPositioningCallback mDragPositioningCallback;
+ private DragResizeInputListener mDragResizeListener;
+ private DragDetector mDragDetector;
+
+ private RelayoutParams mRelayoutParams = new RelayoutParams();
+ private final RelayoutResult mResult =
+ new RelayoutResult<>();
+
+ CaptionWindowDecoration(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Handler handler,
+ Choreographer choreographer,
+ SyncTransactionQueue syncQueue) {
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface);
+
+ mHandler = handler;
+ mChoreographer = choreographer;
+ mSyncQueue = syncQueue;
+ }
+
+ void setCaptionListeners(
+ View.OnClickListener onCaptionButtonClickListener,
+ View.OnTouchListener onCaptionTouchListener) {
+ mOnCaptionButtonClickListener = onCaptionButtonClickListener;
+ mOnCaptionTouchListener = onCaptionTouchListener;
+ }
+
+ void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
+ mDragPositioningCallback = dragPositioningCallback;
+ }
+
+ void setDragDetector(DragDetector dragDetector) {
+ mDragDetector = dragDetector;
+ mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
+ }
+
+ @Override
+ void relayout(RunningTaskInfo taskInfo) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ relayout(taskInfo, t, t);
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ }
+
+ void relayout(RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
+ final int shadowRadiusID = taskInfo.isFocused
+ ? R.dimen.freeform_decor_shadow_focused_thickness
+ : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ final boolean isFreeform =
+ taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+ final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
+
+ final WindowDecorLinearLayout oldRootView = mResult.mRootView;
+ final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ final int outsetLeftId = R.dimen.freeform_resize_handle;
+ final int outsetTopId = R.dimen.freeform_resize_handle;
+ final int outsetRightId = R.dimen.freeform_resize_handle;
+ final int outsetBottomId = R.dimen.freeform_resize_handle;
+
+ mRelayoutParams.reset();
+ mRelayoutParams.mRunningTaskInfo = taskInfo;
+ mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
+ mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+ mRelayoutParams.mShadowRadiusId = shadowRadiusID;
+ if (isDragResizeable) {
+ mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
+ }
+
+ relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
+ // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
+
+ mTaskOrganizer.applyTransaction(wct);
+
+ if (mResult.mRootView == null) {
+ // This means something blocks the window decor from showing, e.g. the task is hidden.
+ // Nothing is set up in this case including the decoration surface.
+ return;
+ }
+ if (oldRootView != mResult.mRootView) {
+ setupRootView();
+ }
+
+ if (!isDragResizeable) {
+ closeDragResizeListener();
+ return;
+ }
+
+ if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
+ closeDragResizeListener();
+ mDragResizeListener = new DragResizeInputListener(
+ mContext,
+ mHandler,
+ mChoreographer,
+ mDisplay.getDisplayId(),
+ mDecorationContainerSurface,
+ mDragPositioningCallback);
+ }
+
+ final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
+ .getScaledTouchSlop();
+ mDragDetector.setTouchSlop(touchSlop);
+
+ final int resize_handle = mResult.mRootView.getResources()
+ .getDimensionPixelSize(R.dimen.freeform_resize_handle);
+ final int resize_corner = mResult.mRootView.getResources()
+ .getDimensionPixelSize(R.dimen.freeform_resize_corner);
+ mDragResizeListener.setGeometry(
+ mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
+ }
+
+ /**
+ * Sets up listeners when a new root view is created.
+ */
+ private void setupRootView() {
+ final View caption = mResult.mRootView.findViewById(R.id.caption);
+ caption.setOnTouchListener(mOnCaptionTouchListener);
+ final View close = caption.findViewById(R.id.close_window);
+ close.setOnClickListener(mOnCaptionButtonClickListener);
+ final View back = caption.findViewById(R.id.back_button);
+ back.setOnClickListener(mOnCaptionButtonClickListener);
+ final View minimize = caption.findViewById(R.id.minimize_window);
+ minimize.setOnClickListener(mOnCaptionButtonClickListener);
+ final View maximize = caption.findViewById(R.id.maximize_window);
+ maximize.setOnClickListener(mOnCaptionButtonClickListener);
+ }
+
+ void setCaptionColor(int captionColor) {
+ if (mResult.mRootView == null) {
+ return;
+ }
+
+ final View caption = mResult.mRootView.findViewById(R.id.caption);
+ final GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
+ captionDrawable.setColor(captionColor);
+
+ final int buttonTintColorRes =
+ Color.valueOf(captionColor).luminance() < 0.5
+ ? R.color.decor_button_light_color
+ : R.color.decor_button_dark_color;
+ final ColorStateList buttonTintColor =
+ caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
+
+ final View back = caption.findViewById(R.id.back_button);
+ final VectorDrawable backBackground = (VectorDrawable) back.getBackground();
+ backBackground.setTintList(buttonTintColor);
+
+ final View minimize = caption.findViewById(R.id.minimize_window);
+ final VectorDrawable minimizeBackground = (VectorDrawable) minimize.getBackground();
+ minimizeBackground.setTintList(buttonTintColor);
+
+ final View maximize = caption.findViewById(R.id.maximize_window);
+ final VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
+ maximizeBackground.setTintList(buttonTintColor);
+
+ final View close = caption.findViewById(R.id.close_window);
+ final VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
+ closeBackground.setTintList(buttonTintColor);
+ }
+
+ private void closeDragResizeListener() {
+ if (mDragResizeListener == null) {
+ return;
+ }
+ mDragResizeListener.close();
+ mDragResizeListener = null;
+ }
+
+ @Override
+ public void close() {
+ closeDragResizeListener();
+ super.close();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 00aab673336924ac75a07ac0bde2a870a25783ea..dee5f8fa74d87eea4453882111c70ce1b067b21b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -20,29 +20,27 @@ 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 com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
+import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
-import android.os.SystemClock;
-import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.InputChannel;
-import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
@@ -55,7 +53,7 @@ import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.Optional;
@@ -74,9 +72,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
- private FreeformTaskTransitionStarter mTransitionStarter;
- private Optional mDesktopModeController;
- private Optional mDesktopTasksController;
+ private final Optional mDesktopModeController;
+ private final Optional mDesktopTasksController;
private boolean mTransitionDragActive;
private SparseArray mEventReceiversByDisplay = new SparseArray<>();
@@ -84,7 +81,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final SparseArray mWindowDecorByTaskId =
new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
- private InputMonitorFactory mInputMonitorFactory;
+ private final InputMonitorFactory mInputMonitorFactory;
+ private TaskOperations mTaskOperations;
+
+ private Optional mSplitScreenController;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -94,7 +94,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
DisplayController displayController,
SyncTransactionQueue syncQueue,
Optional desktopModeController,
- Optional desktopTasksController) {
+ Optional desktopTasksController,
+ Optional splitScreenController) {
this(
context,
mainHandler,
@@ -104,6 +105,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
syncQueue,
desktopModeController,
desktopTasksController,
+ splitScreenController,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory());
}
@@ -118,6 +120,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Optional desktopModeController,
Optional desktopTasksController,
+ Optional splitScreenController,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory) {
mContext = context;
@@ -126,6 +129,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
+ mSplitScreenController = splitScreenController;
mSyncQueue = syncQueue;
mDesktopModeController = desktopModeController;
mDesktopTasksController = desktopTasksController;
@@ -136,7 +140,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Override
public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
- mTransitionStarter = transitionStarter;
+ mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
}
@Override
@@ -162,6 +166,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
decoration.relayout(taskInfo);
+ setupCaptionColor(taskInfo, decoration);
}
@Override
@@ -204,46 +209,49 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) return;
decoration.close();
- int displayId = taskInfo.displayId;
+ final int displayId = taskInfo.displayId;
if (mEventReceiversByDisplay.contains(displayId)) {
removeTaskFromEventReceiver(displayId);
}
}
- private class CaptionTouchEventListener implements
- View.OnClickListener, View.OnTouchListener {
+ private class DesktopModeTouchEventListener implements
+ View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
private final int mTaskId;
private final WindowContainerToken mTaskToken;
- private final DragResizeCallback mDragResizeCallback;
+ private final DragPositioningCallback mDragPositioningCallback;
private final DragDetector mDragDetector;
+ private boolean mIsDragging;
private int mDragPointerId = -1;
- private CaptionTouchEventListener(
+ private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo,
- DragResizeCallback dragResizeCallback,
- DragDetector dragDetector) {
+ DragPositioningCallback dragPositioningCallback) {
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
- mDragResizeCallback = dragResizeCallback;
- mDragDetector = dragDetector;
+ mDragPositioningCallback = dragPositioningCallback;
+ mDragDetector = new DragDetector(this);
}
@Override
public void onClick(View v) {
- DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
- if (id == R.id.close_window) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(mTaskToken);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitionStarter.startRemoveTransition(wct);
- } else {
- mSyncQueue.queue(wct);
+ if (id == R.id.close_window || id == R.id.close_button) {
+ mTaskOperations.closeTask(mTaskToken);
+ if (mSplitScreenController.isPresent()
+ && mSplitScreenController.get().isSplitScreenVisible()) {
+ int remainingTaskPosition = mTaskId == mSplitScreenController.get()
+ .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get()
+ .getTaskInfo(remainingTaskPosition);
+ mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId);
}
} else if (id == R.id.back_button) {
- injectBackKey();
+ mTaskOperations.injectBackKey();
} else if (id == R.id.caption_handle) {
decoration.createHandleMenu();
} else if (id == R.id.desktop_button) {
@@ -255,106 +263,72 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
decoration.setButtonVisibility(false);
- }
- }
-
- 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");
+ } else if (id == R.id.collapse_menu_button) {
+ decoration.closeHandleMenu();
}
}
@Override
public boolean onTouch(View v, MotionEvent e) {
- boolean isDrag = false;
- int id = v.getId();
+ final int id = v.getId();
if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) {
return false;
}
- if (id == R.id.caption_handle) {
- isDrag = mDragDetector.detectDragEvent(e);
- handleEventForMove(e);
- }
- if (e.getAction() != MotionEvent.ACTION_DOWN) {
- return isDrag;
- }
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (taskInfo.isFocused) {
- return isDrag;
- }
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(mTaskToken, true /* onTop */);
- mSyncQueue.queue(wct);
- return true;
+ return mDragDetector.onMotionEvent(e);
}
/**
* @param e {@link MotionEvent} to process
- * @return {@code true} if a drag is happening; or {@code false} if it is not
+ * @return {@code true} if the motion event is handled.
*/
- private void handleEventForMove(MotionEvent e) {
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ @Override
+ public boolean handleMotionEvent(MotionEvent e) {
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- return;
+ return false;
}
if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
- && mDesktopModeController.get().getDisplayAreaWindowingMode(
- taskInfo.displayId)
+ && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
== WINDOWING_MODE_FULLSCREEN) {
- return;
+ return false;
}
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
- mDragResizeCallback.onDragResizeStart(
+ mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
- break;
+ mIsDragging = false;
+ return false;
}
case MotionEvent.ACTION_MOVE: {
- int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDragResizeCallback.onDragResizeMove(
+ final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
+ mIsDragging = true;
+ return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
- .stableInsets().top;
- mDragResizeCallback.onDragResizeEnd(
+ final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ final int statusBarHeight = mDisplayController
+ .getDisplayLayout(taskInfo.displayId).stableInsets().top;
+ mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
- if (DesktopModeStatus.isProto2Enabled()) {
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- // Switch a single task to fullscreen
- mDesktopTasksController.ifPresent(
- c -> c.moveToFullscreen(taskInfo));
- }
- } else if (DesktopModeStatus.isProto1Enabled()) {
- if (DesktopModeStatus.isActive(mContext)) {
- // Turn off desktop mode
- mDesktopModeController.ifPresent(
- c -> c.setDesktopModeActive(false));
- }
+ if (DesktopModeStatus.isProto2Enabled()
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ // Switch a single task to fullscreen
+ mDesktopTasksController.ifPresent(
+ c -> c.moveToFullscreen(taskInfo));
}
}
- break;
+ final boolean wasDragging = mIsDragging;
+ mIsDragging = false;
+ return wasDragging;
}
}
+ return true;
}
}
@@ -408,7 +382,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
*/
private void incrementEventReceiverTasks(int displayId) {
if (mEventReceiversByDisplay.contains(displayId)) {
- EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
eventReceiver.incrementTaskNumber();
} else {
createInputChannel(displayId);
@@ -418,7 +392,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
// If all tasks on this display are gone, we don't need to monitor its input.
private void removeTaskFromEventReceiver(int displayId) {
if (!mEventReceiversByDisplay.contains(displayId)) return;
- EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
if (eventReceiver == null) return;
eventReceiver.decrementTaskNumber();
if (eventReceiver.getTasksOnDisplay() == 0) {
@@ -432,18 +406,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
* @param ev the {@link MotionEvent} received by {@link EventReceiver}
*/
private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
+ final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
if (DesktopModeStatus.isProto2Enabled()) {
- DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
- if (focusedDecor == null
- || focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
- handleCaptionThroughStatusBar(ev);
- }
- } else if (DesktopModeStatus.isProto1Enabled()) {
- if (!DesktopModeStatus.isActive(mContext)) {
- handleCaptionThroughStatusBar(ev);
+ if (relevantDecor == null
+ || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ handleCaptionThroughStatusBar(ev, relevantDecor);
}
}
- handleEventOutsideFocusedCaption(ev);
+ handleEventOutsideFocusedCaption(ev, relevantDecor);
// Prevent status bar from reacting to a caption drag.
if (DesktopModeStatus.isProto2Enabled()) {
if (mTransitionDragActive) {
@@ -457,16 +427,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
// If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
- private void handleEventOutsideFocusedCaption(MotionEvent ev) {
- int action = ev.getActionMasked();
+ private void handleEventOutsideFocusedCaption(MotionEvent ev,
+ DesktopModeWindowDecoration relevantDecor) {
+ final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
- if (focusedDecor == null) {
+ if (relevantDecor == null) {
return;
}
if (!mTransitionDragActive) {
- focusedDecor.closeHandleMenuIfNeeded(ev);
+ relevantDecor.closeHandleMenuIfNeeded(ev);
}
}
}
@@ -476,42 +446,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
* Perform caption actions if not able to through normal means.
* Turn on desktop mode if handle is dragged below status bar.
*/
- private void handleCaptionThroughStatusBar(MotionEvent ev) {
+ private void handleCaptionThroughStatusBar(MotionEvent ev,
+ DesktopModeWindowDecoration relevantDecor) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
// Begin drag through status bar if applicable.
- DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
- if (focusedDecor != null) {
+ if (relevantDecor != null) {
boolean dragFromStatusBarAllowed = false;
if (DesktopModeStatus.isProto2Enabled()) {
// In proto2 any full screen task can be dragged to freeform
- dragFromStatusBarAllowed = focusedDecor.mTaskInfo.getWindowingMode()
+ dragFromStatusBarAllowed = relevantDecor.mTaskInfo.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN;
- } else if (DesktopModeStatus.isProto1Enabled()) {
- // In proto1 task can be dragged to freeform when not in desktop mode
- dragFromStatusBarAllowed = !DesktopModeStatus.isActive(mContext);
}
- if (dragFromStatusBarAllowed && focusedDecor.checkTouchEventInHandle(ev)) {
+ if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) {
mTransitionDragActive = true;
}
}
break;
}
case MotionEvent.ACTION_UP: {
- DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
- if (focusedDecor == null) {
+ if (relevantDecor == null) {
mTransitionDragActive = false;
return;
}
if (mTransitionDragActive) {
mTransitionDragActive = false;
- int statusBarHeight = mDisplayController
- .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
+ final int statusBarHeight = mDisplayController
+ .getDisplayLayout(relevantDecor.mTaskInfo.displayId).stableInsets().top;
if (ev.getY() > statusBarHeight) {
if (DesktopModeStatus.isProto2Enabled()) {
mDesktopTasksController.ifPresent(
- c -> c.moveToDesktop(focusedDecor.mTaskInfo));
+ c -> c.moveToDesktop(relevantDecor.mTaskInfo));
} else if (DesktopModeStatus.isProto1Enabled()) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
@@ -519,7 +485,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return;
}
}
- focusedDecor.checkClickEvent(ev);
+ relevantDecor.checkClickEvent(ev);
break;
}
case MotionEvent.ACTION_CANCEL: {
@@ -528,12 +494,44 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
+ @Nullable
+ private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
+ if (mSplitScreenController.isPresent()
+ && mSplitScreenController.get().isSplitScreenVisible()) {
+ // We can't look at focused task here as only one task will have focus.
+ return getSplitScreenDecor(ev);
+ } else {
+ return getFocusedDecor();
+ }
+ }
+
+ @Nullable
+ private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) {
+ ActivityManager.RunningTaskInfo topOrLeftTask =
+ mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ ActivityManager.RunningTaskInfo bottomOrRightTask =
+ mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ if (topOrLeftTask != null && topOrLeftTask.getConfiguration()
+ .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
+ return mWindowDecorByTaskId.get(topOrLeftTask.taskId);
+ } else if (bottomOrRightTask != null && bottomOrRightTask.getConfiguration()
+ .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
+ Rect bottomOrRightBounds = bottomOrRightTask.getConfiguration().windowConfiguration
+ .getBounds();
+ ev.offsetLocation(-bottomOrRightBounds.left, -bottomOrRightBounds.top);
+ return mWindowDecorByTaskId.get(bottomOrRightTask.taskId);
+ } else {
+ return null;
+ }
+
+ }
+
@Nullable
private DesktopModeWindowDecoration getFocusedDecor() {
- int size = mWindowDecorByTaskId.size();
+ final int size = mWindowDecorByTaskId.size();
DesktopModeWindowDecoration focusedDecor = null;
for (int i = 0; i < size; i++) {
- DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
if (decor != null && decor.isFocused()) {
focusedDecor = decor;
break;
@@ -543,24 +541,31 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
private void createInputChannel(int displayId) {
- InputManager inputManager = InputManager.getInstance();
- InputMonitor inputMonitor =
+ final InputManager inputManager = InputManager.getInstance();
+ final InputMonitor inputMonitor =
mInputMonitorFactory.create(inputManager, mContext);
- EventReceiver eventReceiver = new EventReceiver(inputMonitor,
+ final EventReceiver eventReceiver = new EventReceiver(inputMonitor,
inputMonitor.getInputChannel(), Looper.myLooper());
mEventReceiversByDisplay.put(displayId, eventReceiver);
}
private void disposeInputChannel(int displayId) {
- EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
+ final EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
if (eventReceiver != null) {
eventReceiver.dispose();
}
}
+ private void setupCaptionColor(RunningTaskInfo taskInfo,
+ DesktopModeWindowDecoration decoration) {
+ if (taskInfo == null || taskInfo.taskDescription == null) return;
+ final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
+ decoration.setCaptionColor(statusBarColor);
+ }
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
- return DesktopModeStatus.isAnyEnabled()
+ return DesktopModeStatus.isProto2Enabled()
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
@@ -571,7 +576,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- DesktopModeWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ final DesktopModeWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (oldDecoration != null) {
// close the old decoration if it exists to avoid two window decorations being added
oldDecoration.close();
@@ -588,14 +593,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- TaskPositioner taskPositioner =
- new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
- CaptionTouchEventListener touchEventListener =
- new CaptionTouchEventListener(
- taskInfo, taskPositioner, windowDecoration.getDragDetector());
+ final TaskPositioner taskPositioner =
+ new TaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
+ mDragStartListener);
+ final DesktopModeTouchEventListener touchEventListener =
+ new DesktopModeTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setDragResizeCallback(taskPositioner);
+ windowDecoration.setDragPositioningCallback(taskPositioner);
+ windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT);
+ setupCaptionColor(taskInfo, windowDecoration);
incrementEventReceiverTasks(taskInfo.displayId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 9c2beb9c4b2b957d023d2a378b04561f3998deb8..3c0ef965f4f56ffcb05769dc7d120e84a3a6619c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -20,19 +20,26 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import android.app.ActivityManager;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
-import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
+import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
+import android.widget.ImageView;
+import android.widget.TextView;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.R;
@@ -40,6 +47,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -49,25 +57,28 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
public class DesktopModeWindowDecoration extends WindowDecoration {
+ private static final String TAG = "DesktopModeWindowDecoration";
private final Handler mHandler;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
- private DragResizeCallback mDragResizeCallback;
-
+ private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
+ private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
+ private final int mCaptionMenuHeightId = R.dimen.freeform_decor_caption_menu_height;
private final WindowDecoration.RelayoutResult mResult =
new WindowDecoration.RelayoutResult<>();
private boolean mDesktopActive;
-
- private DragDetector mDragDetector;
-
private AdditionalWindow mHandleMenu;
+ private final int mHandleMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
+ private final int mHandleMenuShadowRadiusId = R.dimen.caption_menu_shadow_radius;
+ private final int mHandleMenuCornerRadiusId = R.dimen.caption_menu_corner_radius;
+ private PointF mHandleMenuPosition = new PointF();
DesktopModeWindowDecoration(
Context context,
@@ -84,7 +95,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration {
transaction.merge(t);
t.close();
@@ -335,10 +406,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration= x
+ && v.getTop() <= y && v.getBottom() >= y;
+ }
+
@Override
public void close() {
closeDragResizeListener();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index 0abe8ab2e30b1278d77096882479cb6025e7f8e4..65b5a7a17afe85ab78a36c53521b30d4a707996b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
@@ -25,63 +26,91 @@ import android.graphics.PointF;
import android.view.MotionEvent;
/**
- * A detector for touch inputs that differentiates between drag and click inputs.
+ * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
+ * of {@link MotionEvent} and generates a new flow of motion events with slop in consideration to
+ * the event handler. In particular, it always passes down, up and cancel events. It'll pass move
+ * events only when there is at least one move event that's beyond the slop threshold. For the
+ * purpose of convenience it also passes all events of other actions.
+ *
* All touch events must be passed through this class to track a drag event.
*/
-public class DragDetector {
+class DragDetector {
+ private final MotionEventHandler mEventHandler;
+
+ private final PointF mInputDownPoint = new PointF();
private int mTouchSlop;
- private PointF mInputDownPoint;
private boolean mIsDragEvent;
private int mDragPointerId;
- public DragDetector(int touchSlop) {
- mTouchSlop = touchSlop;
- mInputDownPoint = new PointF();
- mIsDragEvent = false;
- mDragPointerId = -1;
+
+ private boolean mResultOfDownAction;
+
+ DragDetector(MotionEventHandler eventHandler) {
+ resetState();
+ mEventHandler = eventHandler;
}
/**
- * Determine if {@link MotionEvent} is part of a drag event.
- * @return {@code true} if this is a drag event, {@code false} if not
- */
- public boolean detectDragEvent(MotionEvent ev) {
- switch (ev.getAction()) {
+ * The receiver of the {@link MotionEvent} flow.
+ *
+ * @return the result returned by {@link #mEventHandler}, or the result when
+ * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
+ */
+ boolean onMotionEvent(MotionEvent ev) {
+ final boolean isTouchScreen =
+ (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+ if (!isTouchScreen) {
+ // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
+ // to take the slop threshold into consideration.
+ return mEventHandler.handleMotionEvent(ev);
+ }
+ switch (ev.getActionMasked()) {
case ACTION_DOWN: {
mDragPointerId = ev.getPointerId(0);
float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0);
mInputDownPoint.set(rawX, rawY);
- return false;
+ mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
+ return mResultOfDownAction;
}
case ACTION_MOVE: {
if (!mIsDragEvent) {
int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
- if (Math.hypot(dx, dy) > mTouchSlop) {
- mIsDragEvent = true;
- }
+ // Touches generate noisy moves, so only once the move is past the touch
+ // slop threshold should it be considered a drag.
+ mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop;
+ }
+ // The event handler should only be notified about 'move' events if a drag has been
+ // detected.
+ if (mIsDragEvent) {
+ return mEventHandler.handleMotionEvent(ev);
+ } else {
+ return mResultOfDownAction;
}
- return mIsDragEvent;
- }
- case ACTION_UP: {
- boolean result = mIsDragEvent;
- mIsDragEvent = false;
- mInputDownPoint.set(0, 0);
- mDragPointerId = -1;
- return result;
}
+ case ACTION_UP:
case ACTION_CANCEL: {
- mIsDragEvent = false;
- mInputDownPoint.set(0, 0);
- mDragPointerId = -1;
- return false;
+ resetState();
+ return mEventHandler.handleMotionEvent(ev);
}
+ default:
+ return mEventHandler.handleMotionEvent(ev);
}
- return mIsDragEvent;
}
- public void setTouchSlop(int touchSlop) {
+ void setTouchSlop(int touchSlop) {
mTouchSlop = touchSlop;
}
+
+ private void resetState() {
+ mIsDragEvent = false;
+ mInputDownPoint.set(0, 0);
+ mDragPointerId = -1;
+ mResultOfDownAction = false;
+ }
+
+ interface MotionEventHandler {
+ boolean handleMotionEvent(MotionEvent ev);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
similarity index 76%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index ee160a15df198b24a3ef9d60ca807946051417c8..0191c609a8b2eedc5800461227b4ae49f9626a0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -19,28 +19,28 @@ package com.android.wm.shell.windowdecor;
/**
* Callback called when receiving drag-resize or drag-move related input events.
*/
-public interface DragResizeCallback {
+public interface DragPositioningCallback {
/**
- * Called when a drag resize starts.
+ * Called when a drag-resize or drag-move starts.
*
* @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use
* {@code 0} to indicate it's a move
- * @param x x coordinate in window decoration coordinate system where the drag resize starts
- * @param y y coordinate in window decoration coordinate system where the drag resize starts
+ * @param x x coordinate in window decoration coordinate system where the drag starts
+ * @param y y coordinate in window decoration coordinate system where the drag starts
*/
- void onDragResizeStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
+ void onDragPositioningStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
/**
- * Called when the pointer moves during a drag resize.
+ * Called when the pointer moves during a drag-resize or drag-move.
* @param x x coordinate in window decoration coordinate system of the new pointer location
* @param y y coordinate in window decoration coordinate system of the new pointer location
*/
- void onDragResizeMove(float x, float y);
+ void onDragPositioningMove(float x, float y);
/**
- * Called when a drag resize stops.
+ * Called when a drag-resize or drag-move stops.
* @param x x coordinate in window decoration coordinate system where the drag resize stops
* @param y y coordinate in window decoration coordinate system where the drag resize stops
*/
- void onDragResizeEnd(float x, float y);
+ void onDragPositioningEnd(float x, float y);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index d3f1332f622473028c767cb0845a27d257ee51ad..81c4176b0f3986cf00ad3eb56da79bdb7cd2e9ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -48,7 +48,6 @@ import com.android.internal.view.BaseIWindow;
* Task edges are for resizing with a mouse.
* Task corners are for resizing with touch input.
*/
-// TODO(b/251270585): investigate how to pass taps in corners to the tasks
class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener";
@@ -63,7 +62,7 @@ class DragResizeInputListener implements AutoCloseable {
private final SurfaceControl mDecorationSurface;
private final InputChannel mInputChannel;
private final TaskResizeInputEventReceiver mInputEventReceiver;
- private final com.android.wm.shell.windowdecor.DragResizeCallback mCallback;
+ private final DragPositioningCallback mCallback;
private int mWidth;
private int mHeight;
@@ -84,7 +83,7 @@ class DragResizeInputListener implements AutoCloseable {
Choreographer choreographer,
int displayId,
SurfaceControl decorationSurface,
- DragResizeCallback callback) {
+ DragPositioningCallback callback) {
mInputManager = context.getSystemService(InputManager.class);
mHandler = handler;
mChoreographer = choreographer;
@@ -115,7 +114,8 @@ class DragResizeInputListener implements AutoCloseable {
mInputEventReceiver = new TaskResizeInputEventReceiver(
mInputChannel, mHandler, mChoreographer);
mCallback = callback;
- mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
+ mDragDetector = new DragDetector(mInputEventReceiver);
+ mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
}
/**
@@ -215,6 +215,7 @@ class DragResizeInputListener implements AutoCloseable {
@Override
public void close() {
+ mInputEventReceiver.dispose();
mInputChannel.dispose();
try {
mWindowSession.remove(mFakeWindow);
@@ -223,12 +224,12 @@ class DragResizeInputListener implements AutoCloseable {
}
}
- private class TaskResizeInputEventReceiver extends InputEventReceiver {
+ private class TaskResizeInputEventReceiver extends InputEventReceiver
+ implements DragDetector.MotionEventHandler {
private final Choreographer mChoreographer;
private final Runnable mConsumeBatchEventRunnable;
private boolean mConsumeBatchEventScheduled;
private boolean mShouldHandleEvents;
- private boolean mDragging;
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -270,15 +271,15 @@ class DragResizeInputListener implements AutoCloseable {
if (!(inputEvent instanceof MotionEvent)) {
return false;
}
+ return mDragDetector.onMotionEvent((MotionEvent) inputEvent);
+ }
- MotionEvent e = (MotionEvent) inputEvent;
+ @Override
+ public boolean handleMotionEvent(MotionEvent e) {
boolean result = false;
// Check if this is a touch event vs mouse event.
// Touch events are tracked in four corners. Other events are tracked in resize edges.
boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
- if (isTouch) {
- mDragging = mDragDetector.detectDragEvent(e);
- }
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
float x = e.getX(0);
@@ -293,7 +294,7 @@ class DragResizeInputListener implements AutoCloseable {
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
int ctrlType = calculateCtrlType(isTouch, x, y);
- mCallback.onDragResizeStart(ctrlType, rawX, rawY);
+ mCallback.onDragPositioningStart(ctrlType, rawX, rawY);
result = true;
}
break;
@@ -305,24 +306,17 @@ class DragResizeInputListener implements AutoCloseable {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
- if (!isTouch) {
- // For all other types allow immediate dragging.
- mDragging = true;
- }
- if (mDragging) {
- mCallback.onDragResizeMove(rawX, rawY);
- result = true;
- }
+ mCallback.onDragPositioningMove(rawX, rawY);
+ result = true;
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- if (mShouldHandleEvents && mDragging) {
+ if (mShouldHandleEvents) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
- mCallback.onDragResizeEnd(
+ mCallback.onDragPositioningEnd(
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
}
- mDragging = false;
mShouldHandleEvents = false;
mDragPointerId = -1;
result = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
new file mode 100644
index 0000000000000000000000000000000000000000..aea3404643040418ced95bf8b00d8b165beb74c1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+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.RunningTaskInfo;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.transition.Transitions;
+
+/**
+ * Utility class to handle task operations performed on a window decoration.
+ */
+class TaskOperations {
+ private static final String TAG = "TaskOperations";
+
+ private final FreeformTaskTransitionStarter mTransitionStarter;
+ private final Context mContext;
+ private final SyncTransactionQueue mSyncQueue;
+
+ TaskOperations(FreeformTaskTransitionStarter transitionStarter, Context context,
+ SyncTransactionQueue syncQueue) {
+ mTransitionStarter = transitionStarter;
+ mContext = context;
+ mSyncQueue = syncQueue;
+ }
+
+ 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");
+ }
+ }
+
+ void closeTask(WindowContainerToken taskToken) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.removeTask(taskToken);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitionStarter.startRemoveTransition(wct);
+ } else {
+ mSyncQueue.queue(wct);
+ }
+ }
+
+ void minimizeTask(WindowContainerToken taskToken) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(taskToken, false);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitionStarter.startMinimizedModeTransition(wct);
+ } else {
+ mSyncQueue.queue(wct);
+ }
+ }
+
+ void maximizeTask(RunningTaskInfo taskInfo) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
+ ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM;
+ int displayWindowingMode =
+ taskInfo.configuration.windowConfiguration.getDisplayWindowingMode();
+ wct.setWindowingMode(taskInfo.token,
+ targetWindowingMode == displayWindowingMode
+ ? WINDOWING_MODE_UNDEFINED : targetWindowingMode);
+ if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ wct.setBounds(taskInfo.token, null);
+ }
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitionStarter.startWindowingModeTransition(targetWindowingMode, wct);
+ } else {
+ mSyncQueue.queue(wct);
+ }
+ }
+}
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 a49a300995e6be200157dfc2857f1ec642c7683f..0bce3acecb3c8c0693bf896fcd06618812517afb 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
@@ -19,11 +19,13 @@ package com.android.wm.shell.windowdecor;
import android.annotation.IntDef;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.util.DisplayMetrics;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
-class TaskPositioner implements DragResizeCallback {
+class TaskPositioner implements DragPositioningCallback {
@IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
@interface CtrlType {}
@@ -35,92 +37,154 @@ class TaskPositioner implements DragResizeCallback {
static final int CTRL_TYPE_BOTTOM = 8;
private final ShellTaskOrganizer mTaskOrganizer;
+ private final DisplayController mDisplayController;
private final WindowDecoration mWindowDecoration;
+ private final Rect mTempBounds = new Rect();
private final Rect mTaskBoundsAtDragStart = new Rect();
- private final PointF mResizeStartPoint = new PointF();
- private final Rect mResizeTaskBounds = new Rect();
- // Whether the |dragResizing| hint should be sent with the next bounds change WCT.
- // Used to optimized fluid resizing of freeform tasks.
- private boolean mPendingDragResizeHint = false;
+ private final PointF mRepositionStartPoint = new PointF();
+ private final Rect mRepositionTaskBounds = new Rect();
+ private boolean mHasMoved = false;
private int mCtrlType;
private DragStartListener mDragStartListener;
TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DragStartListener dragStartListener) {
+ DisplayController displayController) {
+ this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {});
+ }
+
+ TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ DisplayController displayController, DragStartListener dragStartListener) {
mTaskOrganizer = taskOrganizer;
mWindowDecoration = windowDecoration;
+ mDisplayController = displayController;
mDragStartListener = dragStartListener;
}
@Override
- public void onDragResizeStart(int ctrlType, float x, float y) {
- if (ctrlType != CTRL_TYPE_UNDEFINED) {
- // The task is being resized, send the |dragResizing| hint to core with the first
- // bounds-change wct.
- mPendingDragResizeHint = true;
- }
+ public void onDragPositioningStart(int ctrlType, float x, float y) {
+ mHasMoved = false;
mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
mTaskBoundsAtDragStart.set(
mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
- mResizeStartPoint.set(x, y);
+ mRepositionStartPoint.set(x, y);
}
@Override
- public void onDragResizeMove(float x, float y) {
+ public void onDragPositioningMove(float x, float y) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (changeBounds(wct, x, y)) {
- if (mPendingDragResizeHint) {
+ // The task is being resized, send the |dragResizing| hint to core with the first
+ // bounds-change wct.
+ if (!mHasMoved && mCtrlType != CTRL_TYPE_UNDEFINED) {
// This is the first bounds change since drag resize operation started.
wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
- mPendingDragResizeHint = false;
}
mTaskOrganizer.applyTransaction(wct);
+ mHasMoved = true;
}
}
@Override
- public void onDragResizeEnd(float x, float y) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
- changeBounds(wct, x, y);
- mTaskOrganizer.applyTransaction(wct);
+ public void onDragPositioningEnd(float x, float y) {
+ // |mHasMoved| being false means there is no real change to the task bounds in WM core, so
+ // we don't need a WCT to finish it.
+ if (mHasMoved) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
+ changeBounds(wct, x, y);
+ mTaskOrganizer.applyTransaction(wct);
+ }
- mCtrlType = 0;
+ mCtrlType = CTRL_TYPE_UNDEFINED;
mTaskBoundsAtDragStart.setEmpty();
- mResizeStartPoint.set(0, 0);
- mPendingDragResizeHint = false;
+ mRepositionStartPoint.set(0, 0);
+ mHasMoved = false;
}
private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
- float deltaX = x - mResizeStartPoint.x;
- mResizeTaskBounds.set(mTaskBoundsAtDragStart);
+ // |mRepositionTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not
+ // true, we can compare it against |mTaskBoundsAtDragStart|.
+ final int oldLeft = mHasMoved ? mRepositionTaskBounds.left : mTaskBoundsAtDragStart.left;
+ final int oldTop = mHasMoved ? mRepositionTaskBounds.top : mTaskBoundsAtDragStart.top;
+ final int oldRight = mHasMoved ? mRepositionTaskBounds.right : mTaskBoundsAtDragStart.right;
+ final int oldBottom =
+ mHasMoved ? mRepositionTaskBounds.bottom : mTaskBoundsAtDragStart.bottom;
+
+ final float deltaX = x - mRepositionStartPoint.x;
+ final float deltaY = y - mRepositionStartPoint.y;
+ mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+
+ final Rect stableBounds = mTempBounds;
+ // Make sure the new resizing destination in any direction falls within the stable bounds.
+ // If not, set the bounds back to the old location that was valid to avoid conflicts with
+ // some regions such as the gesture area.
+ mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(stableBounds);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
- mResizeTaskBounds.left += deltaX;
+ final int candidateLeft = mRepositionTaskBounds.left + (int) deltaX;
+ mRepositionTaskBounds.left = (candidateLeft > stableBounds.left)
+ ? candidateLeft : oldLeft;
}
if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
- mResizeTaskBounds.right += deltaX;
+ final int candidateRight = mRepositionTaskBounds.right + (int) deltaX;
+ mRepositionTaskBounds.right = (candidateRight < stableBounds.right)
+ ? candidateRight : oldRight;
}
- float deltaY = y - mResizeStartPoint.y;
if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
- mResizeTaskBounds.top += deltaY;
+ final int candidateTop = mRepositionTaskBounds.top + (int) deltaY;
+ mRepositionTaskBounds.top = (candidateTop > stableBounds.top)
+ ? candidateTop : oldTop;
}
if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
- mResizeTaskBounds.bottom += deltaY;
+ final int candidateBottom = mRepositionTaskBounds.bottom + (int) deltaY;
+ mRepositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom)
+ ? candidateBottom : oldBottom;
}
- if (mCtrlType == 0) {
- mResizeTaskBounds.offset((int) deltaX, (int) deltaY);
+ if (mCtrlType == CTRL_TYPE_UNDEFINED) {
+ mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
}
- if (!mResizeTaskBounds.isEmpty()) {
- wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
- return true;
+ // If width or height are negative or less than the minimum width or height, revert the
+ // respective bounds to use previous bound dimensions.
+ if (mRepositionTaskBounds.width() < getMinWidth()) {
+ mRepositionTaskBounds.right = oldRight;
+ mRepositionTaskBounds.left = oldLeft;
+ }
+ if (mRepositionTaskBounds.height() < getMinHeight()) {
+ mRepositionTaskBounds.top = oldTop;
+ mRepositionTaskBounds.bottom = oldBottom;
+ }
+ // If there are no changes to the bounds after checking new bounds against minimum width
+ // and height, do not set bounds and return false
+ if (oldLeft == mRepositionTaskBounds.left && oldTop == mRepositionTaskBounds.top
+ && oldRight == mRepositionTaskBounds.right
+ && oldBottom == mRepositionTaskBounds.bottom) {
+ return false;
}
- return false;
+
+ wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ return true;
+ }
+
+ private float getMinWidth() {
+ return mWindowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinSize()
+ : mWindowDecoration.mTaskInfo.minWidth;
+ }
+
+ private float getMinHeight() {
+ return mWindowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinSize()
+ : mWindowDecoration.mTaskInfo.minHeight;
+ }
+
+ private float getDefaultMinSize() {
+ float density = mDisplayController.getDisplayLayout(mWindowDecoration.mTaskInfo.displayId)
+ .densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ return mWindowDecoration.mTaskInfo.defaultMinSize * density;
}
interface DragStartListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 907977c661f8ba5ffa8c66a395739e4d72b28075..3734487e8f4b752a0bd6b01e5f2ef298a5a1aeff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -29,7 +29,8 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
*/
public interface WindowDecorViewModel {
/**
- * Sets the transition starter that starts freeform task transitions.
+ * Sets the transition starter that starts freeform task transitions. Only called when
+ * {@link com.android.wm.shell.transition.Transitions#ENABLE_SHELL_TRANSITIONS} is {@code true}.
*
* @param transitionStarter the transition starter that starts freeform task transitions
*/
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 7f85988d137723f2353bfb5d2bcddb990aadee23..f8e6ecc4499a564412b31b1d5fd7de9b846f166b 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
@@ -131,7 +131,17 @@ public abstract class WindowDecoration
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
- mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration());
+ mDecorWindowContext = mContext.createConfigurationContext(
+ getConfigurationWithOverrides(mTaskInfo));
+ }
+
+ /**
+ * Get {@link Configuration} from supplied {@link RunningTaskInfo}.
+ *
+ * Allows values to be overridden before returning the configuration.
+ */
+ protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) {
+ return taskInfo.getConfiguration();
}
/**
@@ -165,7 +175,7 @@ public abstract class WindowDecoration
outResult.mRootView = rootView;
rootView = null; // Clear it just in case we use it accidentally
- final Configuration taskConfig = mTaskInfo.getConfiguration();
+ final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
if (oldTaskConfig.densityDpi != taskConfig.densityDpi
|| mDisplay == null
|| mDisplay.getDisplayId() != mTaskInfo.displayId) {
@@ -252,9 +262,7 @@ public abstract class WindowDecoration
}
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
- final int captionWidth = params.mCaptionWidthId == Resources.ID_NULL
- ? taskBounds.width()
- : loadDimensionPixelSize(resources, params.mCaptionWidthId);
+ final int captionWidth = taskBounds.width();
startT.setPosition(
mCaptionContainerSurface,
@@ -393,10 +401,12 @@ public abstract class WindowDecoration
* @param yPos y position of new window
* @param width width of new window
* @param height height of new window
- * @return
+ * @param shadowRadius radius of the shadow of the new window
+ * @param cornerRadius radius of the corners of the new window
+ * @return the {@link AdditionalWindow} that was added.
*/
- AdditionalWindow addWindow(int layoutId, String namePrefix,
- SurfaceControl.Transaction t, int xPos, int yPos, int width, int height) {
+ AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
+ int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -405,9 +415,10 @@ public abstract class WindowDecoration
.build();
View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
- t.setPosition(
- windowSurfaceControl, xPos, yPos)
+ t.setPosition(windowSurfaceControl, xPos, yPos)
.setWindowCrop(windowSurfaceControl, width, height)
+ .setShadowRadius(windowSurfaceControl, shadowRadius)
+ .setCornerRadius(windowSurfaceControl, cornerRadius)
.show(windowSurfaceControl);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height,
diff --git a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
index fac04614d9451977ab2b43ed005d17b19bfc5ac2..47a116be1b66d35670ee54dc3daab30629140da7 100644
--- a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
@@ -20,6 +20,7 @@
package="com.android.wm.shell.tests">
+
diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
index 8949a75d1a154d54ff17d25ddefa763836f4c8fd..aa1b24189274dc43babaa35526a1fb807ae2a50b 100644
--- a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
@@ -17,11 +17,13 @@
32dp
- 216dp
+ 216dp10dp20dp30dp40dp5dp10dp
+ 4dp
+ 20dp
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index ff1d2990a82a478048cc487afbd7001234a56d31..d5bb901259bdfad0fc5a5ca29f70a362de87fde5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -28,9 +28,11 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -82,13 +84,14 @@ public class TaskViewTest extends ShellTestCase {
@Mock
SyncTransactionQueue mSyncQueue;
@Mock
- TaskViewTransitions mTaskViewTransitions;
+ Transitions mTransitions;
SurfaceSession mSession;
SurfaceControl mLeash;
Context mContext;
TaskView mTaskView;
+ TaskViewTransitions mTaskViewTransitions;
@Before
public void setUp() {
@@ -118,6 +121,10 @@ public class TaskViewTest extends ShellTestCase {
return null;
}).when(mSyncQueue).runInSync(any());
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ doReturn(true).when(mTransitions).isRegistered();
+ }
+ mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
mTaskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
mTaskView.setListener(mExecutor, mViewListener);
}
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 2e328b0736dddc3278ce06c531d49453210b4ace..2754496a6f3f5afbb6133ee5c373a6b4eb7b39b0 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
@@ -53,6 +53,7 @@ import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackNaviAnimationController;
import android.window.IOnBackInvokedCallback;
@@ -246,10 +247,11 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- ArgumentCaptor backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ ArgumentCaptor backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
- verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackMotionEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
@@ -271,17 +273,18 @@ public class BackAnimationControllerTest extends ShellTestCase {
RemoteAnimationTarget animationTarget = createAnimationTarget();
IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
- ArgumentCaptor backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ ArgumentCaptor backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback, false);
triggerBackGesture();
- verify(appCallback, never()).onBackStarted(any(BackEvent.class));
+ verify(appCallback, never()).onBackStarted(any(BackMotionEvent.class));
verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(appCallback, times(1)).onBackInvoked();
- verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackMotionEvent.class));
verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(mIOnBackInvokedCallback, never()).onBackInvoked();
}
@@ -314,7 +317,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
}
@Test
@@ -333,7 +336,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
}
@@ -349,7 +352,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index 3aefc3f03a8aa550dad14ecc249b18343a82642a..ba9c159bad284e0196337f22edb8ebdec460ff1e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import static org.junit.Assert.assertEquals;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import org.junit.Before;
import org.junit.Test;
@@ -38,7 +39,7 @@ public class TouchTrackerTest {
@Test
public void generatesProgress_onStart() {
mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
- BackEvent event = mTouchTracker.createStartEvent(null);
+ BackMotionEvent event = mTouchTracker.createStartEvent(null);
assertEquals(event.getProgress(), 0f, 0f);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index e6711aca19c105fab4a85cd86a09afa74064a060..8b025cd7c246fa58af103e57ccbca2f189e0e9f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles;
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -32,6 +34,7 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.PendingIntent;
+import android.content.Intent;
import android.content.LocusId;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -94,6 +97,7 @@ public class BubbleDataTest extends ShellTestCase {
private Bubble mBubbleInterruptive;
private Bubble mBubbleDismissed;
private Bubble mBubbleLocusId;
+ private Bubble mAppBubble;
private BubbleData mBubbleData;
private TestableBubblePositioner mPositioner;
@@ -178,6 +182,11 @@ public class BubbleDataTest extends ShellTestCase {
mBubbleMetadataFlagListener,
mPendingIntentCanceledListener,
mMainExecutor);
+
+ Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
+ appBubbleIntent.setPackage(mContext.getPackageName());
+ mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mMainExecutor);
+
mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner,
@@ -1089,6 +1098,18 @@ public class BubbleDataTest extends ShellTestCase {
assertOverflowChangedTo(ImmutableList.of());
}
+ @Test
+ public void test_removeAppBubble_skipsOverflow() {
+ mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
+ false /* showInShade */);
+ assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isEqualTo(mAppBubble);
+
+ mBubbleData.dismissBubbleWithKey(KEY_APP_BUBBLE, Bubbles.DISMISS_USER_GESTURE);
+
+ assertThat(mBubbleData.getOverflowBubbleWithKey(KEY_APP_BUBBLE)).isNull();
+ assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull();
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
index 1636c5f731333f12fb9ffa6ff6491928354d1f9d..0a31338a7c81fd4ccb9cf2ed0d6286b18ca3ac94 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -21,7 +21,6 @@ import android.testing.AndroidTestingRunner
import android.util.SparseArray
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.bubbles.storage.BubbleXmlHelperTest.Companion.sparseArraysEqual
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertTrue
@@ -36,7 +35,8 @@ class BubblePersistentRepositoryTest : ShellTestCase() {
// user, package, shortcut, notification key, height, res-height, title, taskId, locusId
private val user0Bubbles = listOf(
- BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, null),
+ BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, null,
+ true),
BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2,
null),
BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null,
@@ -44,7 +44,8 @@ class BubblePersistentRepositoryTest : ShellTestCase() {
)
private val user1Bubbles = listOf(
- BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, null),
+ BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, null,
+ true),
BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4,
null),
BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null,
@@ -76,6 +77,6 @@ class BubblePersistentRepositoryTest : ShellTestCase() {
assertEquals(actual.size(), 0)
repository.persistsToDisk(bubbles)
- assertTrue(sparseArraysEqual(bubbles, repository.readFromDisk()))
+ assertTrue(bubbles.contentEquals(repository.readFromDisk()))
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
index 4ab9f87dbbf6e0888ff8dff55e9068b59ffc9bb3..3bfbcd26a5777b8ee40e442daa4c3d2702f40080 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
@@ -34,7 +34,8 @@ import java.io.ByteArrayOutputStream
class BubbleXmlHelperTest : ShellTestCase() {
private val user0Bubbles = listOf(
- BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1),
+ BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1,
+ isDismissable = true),
BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2,
null),
BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null,
@@ -42,7 +43,8 @@ class BubbleXmlHelperTest : ShellTestCase() {
)
private val user1Bubbles = listOf(
- BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3),
+ BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3,
+ isDismissable = true),
BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4,
null),
BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null,
@@ -51,28 +53,6 @@ class BubbleXmlHelperTest : ShellTestCase() {
private val bubbles = SparseArray>()
- // Checks that the contents of the two sparse arrays are the same.
- companion object {
- fun sparseArraysEqual(
- one: SparseArray>?,
- two: SparseArray>?
- ): Boolean {
- if (one == null && two == null) return true
- if ((one == null) != (two == null)) return false
- if (one!!.size() != two!!.size()) return false
- for (i in 0 until one.size()) {
- val k1 = one.keyAt(i)
- val v1 = one.valueAt(i)
- val k2 = two.keyAt(i)
- val v2 = two.valueAt(i)
- if (k1 != k2 && v1 != v2) {
- return false
- }
- }
- return true
- }
- }
-
@Before
fun setup() {
bubbles.put(0, user0Bubbles)
@@ -83,14 +63,14 @@ class BubbleXmlHelperTest : ShellTestCase() {
fun testWriteXml() {
val expectedEntries = """
-
-
-
+
+
+
-
-
-
+
+
+
""".trimIndent()
ByteArrayOutputStream().use {
@@ -107,19 +87,19 @@ class BubbleXmlHelperTest : ShellTestCase() {
-
-
-
+
+
+
-
-
-
+
+
+
""".trimIndent()
val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
- assertTrue("failed parsing bubbles from xml\n$src", sparseArraysEqual(bubbles, actual))
+ assertTrue("failed parsing bubbles from xml\n$src", bubbles.contentEquals(actual))
}
// V0 -> V1 happened prior to release / during dogfood so nothing is saved
@@ -161,8 +141,7 @@ class BubbleXmlHelperTest : ShellTestCase() {
""".trimIndent()
val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
- assertTrue("failed parsing bubbles from xml\n$src",
- sparseArraysEqual(expectedBubbles, actual))
+ assertTrue("failed parsing bubbles from xml\n$src", expectedBubbles.contentEquals(actual))
}
/**
@@ -187,7 +166,7 @@ class BubbleXmlHelperTest : ShellTestCase() {
""".trimIndent()
val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
assertTrue("failed parsing bubbles from xml\n$src",
- sparseArraysEqual(expectedBubbles, actual))
+ expectedBubbles.contentEquals(actual))
}
@Test
@@ -210,6 +189,6 @@ class BubbleXmlHelperTest : ShellTestCase() {
)
val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
assertTrue("failed parsing bubbles from xml\n$src",
- sparseArraysEqual(expectedBubbles, actual))
+ expectedBubbles.contentEquals(actual))
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8ee300e411cb65e0077a045670de1333a51c7ed
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DevicePostureControllerTest {
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private ShellInit mShellInit;
+
+ @Mock
+ private ShellExecutor mMainExecutor;
+
+ @Captor
+ private ArgumentCaptor mDevicePostureCaptor;
+
+ @Mock
+ private DevicePostureController.OnDevicePostureChangedListener mOnDevicePostureChangedListener;
+
+ private DevicePostureController mDevicePostureController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mDevicePostureController = new DevicePostureController(mContext, mShellInit, mMainExecutor);
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mDevicePostureController));
+ }
+
+ @Test
+ public void registerOnDevicePostureChangedListener_callbackCurrentPosture() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(
+ mOnDevicePostureChangedListener);
+ verify(mOnDevicePostureChangedListener, times(1))
+ .onDevicePostureChanged(anyInt());
+ }
+
+ @Test
+ public void onDevicePostureChanged_differentPosture_callbackListener() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(
+ mOnDevicePostureChangedListener);
+ verify(mOnDevicePostureChangedListener).onDevicePostureChanged(
+ mDevicePostureCaptor.capture());
+ clearInvocations(mOnDevicePostureChangedListener);
+
+ int differentDevicePosture = mDevicePostureCaptor.getValue() + 1;
+ mDevicePostureController.onDevicePostureChanged(differentDevicePosture);
+
+ verify(mOnDevicePostureChangedListener, times(1))
+ .onDevicePostureChanged(differentDevicePosture);
+ }
+
+ @Test
+ public void onDevicePostureChanged_samePosture_doesNotCallbackListener() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(
+ mOnDevicePostureChangedListener);
+ verify(mOnDevicePostureChangedListener).onDevicePostureChanged(
+ mDevicePostureCaptor.capture());
+ clearInvocations(mOnDevicePostureChangedListener);
+
+ int sameDevicePosture = mDevicePostureCaptor.getValue();
+ mDevicePostureController.onDevicePostureChanged(sameDevicePosture);
+
+ verifyZeroInteractions(mOnDevicePostureChangedListener);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..96d202ce3a851061d9ca3c49339fe957a41450fd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_OPENED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link TabletopModeController}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class TabletopModeControllerTest extends ShellTestCase {
+ // It's considered tabletop mode if the display rotation angle matches what's in this array.
+ // It's defined as com.android.internal.R.array.config_deviceTabletopRotations on real devices.
+ private static final int[] TABLETOP_MODE_ROTATIONS = new int[] {
+ 90 /* Surface.ROTATION_90 */,
+ 270 /* Surface.ROTATION_270 */
+ };
+
+ private TestShellExecutor mMainExecutor;
+
+ private Configuration mConfiguration;
+
+ private TabletopModeController mPipTabletopController;
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private ShellInit mShellInit;
+
+ @Mock
+ private Resources mResources;
+
+ @Mock
+ private DevicePostureController mDevicePostureController;
+
+ @Mock
+ private DisplayController mDisplayController;
+
+ @Mock
+ private TabletopModeController.OnTabletopModeChangedListener mOnTabletopModeChangedListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mResources.getIntArray(com.android.internal.R.array.config_deviceTabletopRotations))
+ .thenReturn(TABLETOP_MODE_ROTATIONS);
+ when(mContext.getResources()).thenReturn(mResources);
+ mMainExecutor = new TestShellExecutor();
+ mConfiguration = new Configuration();
+ mPipTabletopController = new TabletopModeController(mContext, mShellInit,
+ mDevicePostureController, mDisplayController, mMainExecutor);
+ mPipTabletopController.onInit();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipTabletopController));
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopMode_callbackFalse() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(false);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_inTabletopMode_callbackTrue() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(true);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopModeTwice_callbackOnce() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+ clearInvocations(mOnTabletopModeChangedListener);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ verifyZeroInteractions(mOnTabletopModeChangedListener);
+ }
+
+ // Test cases starting from folded state (DEVICE_POSTURE_CLOSED)
+ @Test
+ public void foldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ // Test cases starting from unfolded state (DEVICE_POSTURE_OPENED)
+ @Test
+ public void unfoldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 2fc0914acbd4087780d52acdb0db627ec85963a1..4cf9e6acaec60e495bd1b6200119d6f2244ddaf7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -54,7 +54,6 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -94,7 +93,10 @@ public class CompatUIControllerTest extends ShellTestCase {
private @Mock Lazy mMockTransitionsLazy;
private @Mock CompatUIWindowManager mMockCompatLayout;
private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
+ private @Mock RestartDialogWindowManager mMockRestartDialogLayout;
private @Mock DockStateReader mDockStateReader;
+ private @Mock CompatUIConfiguration mCompatUIConfiguration;
+ private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler;
@Captor
ArgumentCaptor mOnInsetsChangedListenerCaptor;
@@ -112,10 +114,17 @@ public class CompatUIControllerTest extends ShellTestCase {
doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
+
+ doReturn(DISPLAY_ID).when(mMockRestartDialogLayout).getDisplayId();
+ doReturn(TASK_ID).when(mMockRestartDialogLayout).getTaskId();
+ doReturn(true).when(mMockRestartDialogLayout).createLayout(anyBoolean());
+ doReturn(true).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
+
mShellInit = spy(new ShellInit(mMockExecutor));
mController = new CompatUIController(mContext, mShellInit, mMockShellController,
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
- mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader) {
+ mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
+ mCompatUIConfiguration, mCompatUIShellCommandHandler) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -127,6 +136,12 @@ public class CompatUIControllerTest extends ShellTestCase {
TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
return mMockLetterboxEduLayout;
}
+
+ @Override
+ RestartDialogWindowManager createRestartDialogWindowManager(Context context,
+ TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+ return mMockRestartDialogLayout;
+ }
};
mShellInit.init();
spyOn(mController);
@@ -159,6 +174,8 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
// Verify that the compat controls and letterbox education are updated with new size compat
// info.
@@ -167,10 +184,12 @@ public class CompatUIControllerTest extends ShellTestCase {
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- true);
- verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- true);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
+ verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
// Verify that compat controls and letterbox education are removed with null task listener.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
@@ -180,12 +199,14 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockCompatLayout).release();
verify(mMockLetterboxEduLayout).release();
+ verify(mMockRestartDialogLayout).release();
}
@Test
public void testOnCompatInfoChanged_createLayoutReturnsFalse() {
doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
+ doReturn(false).when(mMockRestartDialogLayout).createLayout(anyBoolean());
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -194,6 +215,8 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
// Verify that the layout is created again.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
@@ -201,15 +224,19 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mMockRestartDialogLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
}
@Test
public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() {
doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
+ doReturn(false).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -218,24 +245,33 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
- clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
+ mController);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- true);
- verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- true);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
+ verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
// Verify that the layout is created again.
- clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
+ mController);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mMockRestartDialogLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
}
@@ -259,6 +295,7 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockCompatLayout, never()).release();
verify(mMockLetterboxEduLayout, never()).release();
+ verify(mMockRestartDialogLayout, never()).release();
verify(mMockDisplayInsetsController, never()).removeInsetsChangedListener(eq(DISPLAY_ID),
any());
@@ -267,6 +304,7 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockDisplayInsetsController).removeInsetsChangedListener(eq(DISPLAY_ID), any());
verify(mMockCompatLayout).release();
verify(mMockLetterboxEduLayout).release();
+ verify(mMockRestartDialogLayout).release();
}
@Test
@@ -278,11 +316,13 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockCompatLayout, never()).updateDisplayLayout(any());
verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(any());
+ verify(mMockRestartDialogLayout, never()).updateDisplayLayout(any());
mController.onDisplayConfigurationChanged(DISPLAY_ID, new Configuration());
verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout);
verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockRestartDialogLayout).updateDisplayLayout(mMockDisplayLayout);
}
@Test
@@ -301,12 +341,14 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout);
verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockRestartDialogLayout).updateDisplayLayout(mMockDisplayLayout);
// No update if the insets state is the same.
- clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout);
mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState));
verify(mMockCompatLayout, never()).updateDisplayLayout(mMockDisplayLayout);
verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockRestartDialogLayout, never()).updateDisplayLayout(mMockDisplayLayout);
}
@Test
@@ -319,22 +361,26 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
+ verify(mMockRestartDialogLayout).updateVisibility(false);
// Verify button remains hidden while IME is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- false);
- verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- false);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
+ verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
// Verify button is shown after IME is hidden.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
+ verify(mMockRestartDialogLayout).updateVisibility(true);
}
@Test
@@ -347,22 +393,26 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
+ verify(mMockRestartDialogLayout).updateVisibility(false);
// Verify button remains hidden while keyguard is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- false);
- verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- false);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
+ verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
// Verify button is shown after keyguard becomes not showing.
mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
+ verify(mMockRestartDialogLayout).updateVisibility(true);
}
@Test
@@ -375,20 +425,23 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockCompatLayout, times(2)).updateVisibility(false);
verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
+ verify(mMockRestartDialogLayout, times(2)).updateVisibility(false);
- clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout);
// Verify button remains hidden after keyguard becomes not showing since IME is showing.
mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
+ verify(mMockRestartDialogLayout).updateVisibility(false);
// Verify button is shown after IME is not showing.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
+ verify(mMockRestartDialogLayout).updateVisibility(true);
}
@Test
@@ -401,20 +454,53 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockCompatLayout, times(2)).updateVisibility(false);
verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
+ verify(mMockRestartDialogLayout, times(2)).updateVisibility(false);
- clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout);
// Verify button remains hidden after IME is hidden since keyguard is showing.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
+ verify(mMockRestartDialogLayout).updateVisibility(false);
// Verify button is shown after keyguard becomes not showing.
mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
+ verify(mMockRestartDialogLayout).updateVisibility(true);
+ }
+
+ @Test
+ public void testRestartLayoutRecreatedIfNeeded() {
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ doReturn(true).when(mMockRestartDialogLayout)
+ .needsToBeRecreated(any(TaskInfo.class),
+ any(ShellTaskOrganizer.TaskListener.class));
+
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+ verify(mMockRestartDialogLayout, times(2))
+ .createLayout(anyBoolean());
+ }
+
+ @Test
+ public void testRestartLayoutNotRecreatedIfNotNeeded() {
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ doReturn(false).when(mMockRestartDialogLayout)
+ .needsToBeRecreated(any(TaskInfo.class),
+ any(ShellTaskOrganizer.TaskListener.class));
+
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+ verify(mMockRestartDialogLayout, times(1))
+ .createLayout(anyBoolean());
}
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 7d3e718313e62d6eaeb1fce2be8cb0c0f182d804..5f294d53b662722fcd8681099ee3c5498992a8a7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -31,6 +31,7 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
import android.testing.AndroidTestingRunner;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.SurfaceControlViewHost;
import android.widget.ImageButton;
@@ -45,12 +46,17 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import junit.framework.Assert;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Consumer;
+
/**
* Tests for {@link CompatUILayout}.
*
@@ -65,20 +71,22 @@ public class CompatUILayoutTest extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private SurfaceControlViewHost mViewHost;
+ @Mock private CompatUIConfiguration mCompatUIConfiguration;
private CompatUIWindowManager mWindowManager;
private CompatUILayout mLayout;
+ private TaskInfo mTaskInfo;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- mWindowManager = new CompatUIWindowManager(mContext,
- createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
- mSyncTransactionQueue, mCallback, mTaskListener,
- new DisplayLayout(), new CompatUIHintsState());
+ mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+ mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mCompatUIConfiguration, mOnRestartButtonClicked);
mLayout = (CompatUILayout)
LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
@@ -95,8 +103,15 @@ public class CompatUILayoutTest extends ShellTestCase {
final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
button.performClick();
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor> restartCaptor =
+ ArgumentCaptor.forClass(Pair.class);
+
verify(mWindowManager).onRestartButtonClicked();
- verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
+ verify(mOnRestartButtonClicked).accept(restartCaptor.capture());
+ final Pair result = restartCaptor.getValue();
+ Assert.assertEquals(mTaskInfo, result.first);
+ Assert.assertEquals(mTaskListener, result.second);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index e79b803b430411a28194583a3025bda1cef1e605..0c5edc3f59decad9bcef9cf437240881738ac36f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -28,6 +28,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -38,6 +39,7 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
+import android.util.Pair;
import android.view.DisplayInfo;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -53,12 +55,17 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import junit.framework.Assert;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Consumer;
+
/**
* Tests for {@link CompatUIWindowManager}.
*
@@ -73,20 +80,22 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private CompatUILayout mLayout;
@Mock private SurfaceControlViewHost mViewHost;
+ @Mock private CompatUIConfiguration mCompatUIConfiguration;
private CompatUIWindowManager mWindowManager;
+ private TaskInfo mTaskInfo;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- mWindowManager = new CompatUIWindowManager(mContext,
- createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
- mSyncTransactionQueue, mCallback, mTaskListener,
- new DisplayLayout(), new CompatUIHintsState());
+ mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+ mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mCompatUIConfiguration, mOnRestartButtonClicked);
spyOn(mWindowManager);
doReturn(mLayout).when(mWindowManager).inflateLayout();
@@ -351,14 +360,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
mWindowManager.updateVisibility(/* canShow= */ false);
verify(mWindowManager, never()).createLayout(anyBoolean());
- verify(mLayout).setVisibility(View.GONE);
+ verify(mLayout, atLeastOnce()).setVisibility(View.GONE);
// Show button.
doReturn(View.GONE).when(mLayout).getVisibility();
mWindowManager.updateVisibility(/* canShow= */ true);
verify(mWindowManager, never()).createLayout(anyBoolean());
- verify(mLayout).setVisibility(View.VISIBLE);
+ verify(mLayout, atLeastOnce()).setVisibility(View.VISIBLE);
}
@Test
@@ -404,7 +413,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
public void testOnRestartButtonClicked() {
mWindowManager.onRestartButtonClicked();
- verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor> restartCaptor =
+ ArgumentCaptor.forClass(Pair.class);
+
+ verify(mOnRestartButtonClicked).accept(restartCaptor.capture());
+ final Pair result = restartCaptor.getValue();
+ Assert.assertEquals(mTaskInfo, result.first);
+ Assert.assertEquals(mTaskListener, result.second);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
similarity index 91%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
index a58620dfc6dcf799cdc99f9216611c12d843cab4..172c263ab0f6f7ff8b4484c51eb718edb1142b27 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -58,9 +58,8 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
- mLayout = (LetterboxEduDialogLayout)
- LayoutInflater.from(mContext).inflate(R.layout.letterbox_education_dialog_layout,
- null);
+ mLayout = (LetterboxEduDialogLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.letterbox_education_dialog_layout, null);
mDismissButton = mLayout.findViewById(R.id.letterbox_education_dialog_dismiss_button);
mDialogContainer = mLayout.findViewById(R.id.letterbox_education_dialog_container);
mLayout.setDismissOnClickListener(mDismissCallback);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
similarity index 84%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index 14190f18929c2a9f848aac9f4784e9d36b1d4ed6..12ceb0a9a9ba9bf462d78f8c745ff3cc605ef715 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -31,14 +31,12 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.TaskInfo;
-import android.content.Context;
-import android.content.SharedPreferences;
import android.graphics.Insets;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
+import android.util.Pair;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.SurfaceControlViewHost;
@@ -53,10 +51,10 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.DialogAnimationController;
import com.android.wm.shell.transition.Transitions;
import org.junit.After;
@@ -68,6 +66,10 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
/**
* Tests for {@link LetterboxEduWindowManager}.
*
@@ -81,8 +83,10 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
private static final int USER_ID_1 = 1;
private static final int USER_ID_2 = 2;
- private static final String PREF_KEY_1 = String.valueOf(USER_ID_1);
- private static final String PREF_KEY_2 = String.valueOf(USER_ID_2);
+ private static final String TEST_COMPAT_UI_SHARED_PREFERENCES = "test_compat_ui_configuration";
+
+ private static final String TEST_HAS_SEEN_LETTERBOX_SHARED_PREFERENCES =
+ "test_has_seen_letterbox";
private static final int TASK_ID = 1;
@@ -104,46 +108,46 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private SurfaceControlViewHost mViewHost;
@Mock private Transitions mTransitions;
- @Mock private Runnable mOnDismissCallback;
+ @Mock private Consumer> mOnDismissCallback;
@Mock private DockStateReader mDockStateReader;
- private SharedPreferences mSharedPreferences;
- @Nullable
- private Boolean mInitialPrefValue1 = null;
- @Nullable
- private Boolean mInitialPrefValue2 = null;
+ private CompatUIConfiguration mCompatUIConfiguration;
+ private TestShellExecutor mExecutor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- mSharedPreferences = mContext.getSharedPreferences(
- LetterboxEduWindowManager.HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME,
- Context.MODE_PRIVATE);
- if (mSharedPreferences.contains(PREF_KEY_1)) {
- mInitialPrefValue1 = mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false);
- mSharedPreferences.edit().remove(PREF_KEY_1).apply();
- }
- if (mSharedPreferences.contains(PREF_KEY_2)) {
- mInitialPrefValue2 = mSharedPreferences.getBoolean(PREF_KEY_2, /* default= */ false);
- mSharedPreferences.edit().remove(PREF_KEY_2).apply();
- }
+ mExecutor = new TestShellExecutor();
+ mCompatUIConfiguration = new CompatUIConfiguration(mContext, mExecutor) {
+
+ final Set mHasSeenSet = new HashSet<>();
+
+ @Override
+ boolean getHasSeenLetterboxEducation(int userId) {
+ return mHasSeenSet.contains(userId);
+ }
+
+ @Override
+ void setSeenLetterboxEducation(int userId) {
+ mHasSeenSet.add(userId);
+ }
+
+ @Override
+ protected String getCompatUISharedPreferenceName() {
+ return TEST_COMPAT_UI_SHARED_PREFERENCES;
+ }
+
+ @Override
+ protected String getHasSeenLetterboxEducationSharedPreferencedName() {
+ return TEST_HAS_SEEN_LETTERBOX_SHARED_PREFERENCES;
+ }
+ };
}
@After
public void tearDown() {
- SharedPreferences.Editor editor = mSharedPreferences.edit();
- if (mInitialPrefValue1 == null) {
- editor.remove(PREF_KEY_1);
- } else {
- editor.putBoolean(PREF_KEY_1, mInitialPrefValue1);
- }
- if (mInitialPrefValue2 == null) {
- editor.remove(PREF_KEY_2);
- } else {
- editor.putBoolean(PREF_KEY_2, mInitialPrefValue2);
- }
- editor.apply();
+ mContext.deleteSharedPreferences(TEST_COMPAT_UI_SHARED_PREFERENCES);
+ mContext.deleteSharedPreferences(TEST_HAS_SEEN_LETTERBOX_SHARED_PREFERENCES);
}
@Test
@@ -167,8 +171,8 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Test
public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
- LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
- true, USER_ID_1, /* isTaskbarEduShowing= */ true);
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
+ USER_ID_1, /* isTaskbarEduShowing= */ true);
assertFalse(windowManager.createLayout(/* canShow= */ true));
@@ -181,7 +185,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
assertTrue(windowManager.createLayout(/* canShow= */ false));
- assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertFalse(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
assertNull(windowManager.mLayout);
}
@@ -202,7 +206,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
spyOn(dialogTitle);
// The education shouldn't be marked as seen until enter animation is done.
- assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertFalse(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
// Clicking the layout does nothing until enter animation is done.
layout.performClick();
verify(mAnimationController, never()).startExitAnimation(any(), any());
@@ -211,7 +215,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
verifyAndFinishEnterAnimation(layout);
- assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertFalse(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
verify(dialogTitle).sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
// Exit animation should start following a click on the layout.
layout.performClick();
@@ -219,13 +223,16 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
// Window manager isn't released until exit animation is done.
verify(windowManager, never()).release();
+ // After dismissed the user has seen the dialog
+ assertTrue(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
+
// Verify multiple clicks are ignored.
layout.performClick();
verifyAndFinishExitAnimation(layout);
verify(windowManager).release();
- verify(mOnDismissCallback).run();
+ verify(mOnDismissCallback).accept(any());
}
@Test
@@ -237,7 +244,10 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
assertNotNull(windowManager.mLayout);
verifyAndFinishEnterAnimation(windowManager.mLayout);
- assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+
+ // We dismiss
+ windowManager.mLayout.findViewById(R.id.letterbox_education_dialog_dismiss_button)
+ .performClick();
windowManager.release();
windowManager = createWindowManager(/* eligible= */ true,
@@ -255,7 +265,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
assertNotNull(windowManager.mLayout);
verifyAndFinishEnterAnimation(windowManager.mLayout);
- assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertTrue(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
}
@Test
@@ -272,7 +282,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
mRunOnIdleCaptor.getValue().run();
verify(mAnimationController, never()).startEnterAnimation(any(), any());
- assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+ assertFalse(mCompatUIConfiguration.getHasSeenLetterboxEducation(USER_ID_1));
}
@Test
@@ -298,7 +308,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
mTaskListener, /* canShow= */ true));
verify(windowManager).release();
- verify(mOnDismissCallback, never()).run();
+ verify(mOnDismissCallback, never()).accept(any());
verify(mAnimationController, never()).startExitAnimation(any(), any());
assertNull(windowManager.mLayout);
}
@@ -396,23 +406,22 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
private LetterboxEduWindowManager createWindowManager(boolean eligible, boolean isDocked) {
- return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */
- false, isDocked);
+ return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */ false, isDocked);
}
- private LetterboxEduWindowManager createWindowManager(boolean eligible,
- int userId, boolean isTaskbarEduShowing) {
+ private LetterboxEduWindowManager createWindowManager(boolean eligible, int userId,
+ boolean isTaskbarEduShowing) {
return createWindowManager(eligible, userId, isTaskbarEduShowing, /* isDocked */false);
}
- private LetterboxEduWindowManager createWindowManager(boolean eligible,
- int userId, boolean isTaskbarEduShowing, boolean isDocked) {
+ private LetterboxEduWindowManager createWindowManager(boolean eligible, int userId,
+ boolean isTaskbarEduShowing, boolean isDocked) {
doReturn(isDocked).when(mDockStateReader).isDocked();
- LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
+ LetterboxEduWindowManager
+ windowManager = new LetterboxEduWindowManager(mContext,
createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
- createDisplayLayout(), mTransitions, mOnDismissCallback,
- mAnimationController, mDockStateReader);
-
+ createDisplayLayout(), mTransitions, mOnDismissCallback, mAnimationController,
+ mDockStateReader, mCompatUIConfiguration);
spyOn(windowManager);
doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f71b83179b1c89196b0195a0a3ffa86a4e174a9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.TaskInfo;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link LetterboxEduDialogLayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ReachabilityEduLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class ReachabilityEduLayoutTest extends ShellTestCase {
+
+ private ReachabilityEduLayout mLayout;
+ private View mMoveUpButton;
+ private View mMoveDownButton;
+ private View mMoveLeftButton;
+ private View mMoveRightButton;
+
+ @Mock
+ private CompatUIConfiguration mCompatUIConfiguration;
+
+ @Mock
+ private TaskInfo mTaskInfo;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLayout = (ReachabilityEduLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.reachability_ui_layout, null);
+ mMoveLeftButton = mLayout.findViewById(R.id.reachability_move_left_button);
+ mMoveRightButton = mLayout.findViewById(R.id.reachability_move_right_button);
+ mMoveUpButton = mLayout.findViewById(R.id.reachability_move_up_button);
+ mMoveDownButton = mLayout.findViewById(R.id.reachability_move_down_button);
+ }
+
+ @Test
+ public void testOnFinishInflate() {
+ assertNotNull(mMoveUpButton);
+ assertNotNull(mMoveDownButton);
+ assertNotNull(mMoveLeftButton);
+ assertNotNull(mMoveRightButton);
+ }
+
+ @Test
+ public void handleVisibility_educationNotEnabled_buttonsAreHidden() {
+ mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
+ false, /* letterboxVerticalPosition */
+ -1, /* letterboxHorizontalPosition */ -1, /* availableWidth */
+ 0, /* availableHeight */ 0, mCompatUIConfiguration, mTaskInfo);
+ assertEquals(View.INVISIBLE, mMoveUpButton.getVisibility());
+ assertEquals(View.INVISIBLE, mMoveDownButton.getVisibility());
+ assertEquals(View.INVISIBLE, mMoveLeftButton.getVisibility());
+ assertEquals(View.INVISIBLE, mMoveRightButton.getVisibility());
+ }
+
+ @Test
+ public void handleVisibility_horizontalEducationEnableduiConfigurationIsUpdated() {
+ mLayout.handleVisibility(/* horizontalEnabled */ true, /* verticalEnabled */
+ false, /* letterboxVerticalPosition */ -1, /* letterboxHorizontalPosition */
+ 1, /* availableWidth */ 500, /* availableHeight */ 0, mCompatUIConfiguration,
+ mTaskInfo);
+
+ verify(mCompatUIConfiguration).setUserHasSeenHorizontalReachabilityEducation(mTaskInfo);
+ verify(mCompatUIConfiguration, never()).setUserHasSeenVerticalReachabilityEducation(
+ mTaskInfo);
+ }
+
+ @Test
+ public void handleVisibility_verticalEducationEnabled_uiConfigurationIsUpdated() {
+ mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
+ true, /* letterboxVerticalPosition */ 0, /* letterboxHorizontalPosition */
+ -1, /* availableWidth */ 0, /* availableHeight */ 500, mCompatUIConfiguration,
+ mTaskInfo);
+
+ verify(mCompatUIConfiguration, never())
+ .setUserHasSeenHorizontalReachabilityEducation(mTaskInfo);
+ verify(mCompatUIConfiguration).setUserHasSeenVerticalReachabilityEducation(mTaskInfo);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5bcc72e73cb904c4b6eb790b3c86980b4418abf0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link ReachabilityEduWindowManager}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ReachabilityEduWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class ReachabilityEduWindowManagerTest extends ShellTestCase {
+
+ private static final int USER_ID = 1;
+ private static final int TASK_ID = 1;
+
+ @Mock
+ private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock
+ private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock
+ private CompatUIController.CompatUICallback mCallback;
+ @Mock
+ private CompatUIConfiguration mCompatUIConfiguration;
+ @Mock
+ private DisplayLayout mDisplayLayout;
+
+ private TestShellExecutor mExecutor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mExecutor = new TestShellExecutor();
+ }
+
+ @Test
+ public void testCreateLayout_notEligible_doesNotCreateLayout() {
+ final ReachabilityEduWindowManager windowManager = createReachabilityEduWindowManager(
+ createTaskInfo(/* userId= */ USER_ID, /*isLetterboxDoubleTapEnabled */ false));
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+ assertNull(windowManager.mLayout);
+ }
+
+ private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
+ return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue,
+ mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor);
+ }
+
+ private static TaskInfo createTaskInfo(int userId, boolean isLetterboxDoubleTapEnabled) {
+ return createTaskInfo(userId, /* isLetterboxDoubleTapEnabled */ isLetterboxDoubleTapEnabled,
+ /* topActivityLetterboxVerticalPosition */ -1,
+ /* topActivityLetterboxHorizontalPosition */ -1,
+ /* topActivityLetterboxWidth */ -1,
+ /* topActivityLetterboxHeight */ -1);
+ }
+
+ private static TaskInfo createTaskInfo(int userId, boolean isLetterboxDoubleTapEnabled,
+ int topActivityLetterboxVerticalPosition, int topActivityLetterboxHorizontalPosition,
+ int topActivityLetterboxWidth, int topActivityLetterboxHeight) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.userId = userId;
+ taskInfo.taskId = TASK_ID;
+ taskInfo.isLetterboxDoubleTapEnabled = isLetterboxDoubleTapEnabled;
+ taskInfo.topActivityLetterboxVerticalPosition = topActivityLetterboxVerticalPosition;
+ taskInfo.topActivityLetterboxHorizontalPosition = topActivityLetterboxHorizontalPosition;
+ taskInfo.topActivityLetterboxWidth = topActivityLetterboxWidth;
+ taskInfo.topActivityLetterboxHeight = topActivityLetterboxHeight;
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e2dcdb0e91b271bd35c322a7ed0557bb4f9517cc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link RestartDialogLayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:RestartDialogLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class RestartDialogLayoutTest extends ShellTestCase {
+
+ @Mock private Runnable mDismissCallback;
+ @Mock private Consumer mRestartCallback;
+
+ private RestartDialogLayout mLayout;
+ private View mDismissButton;
+ private View mRestartButton;
+ private View mDialogContainer;
+ private CheckBox mDontRepeatCheckBox;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mLayout = (RestartDialogLayout)
+ LayoutInflater.from(mContext).inflate(R.layout.letterbox_restart_dialog_layout,
+ null);
+ mDismissButton = mLayout.findViewById(R.id.letterbox_restart_dialog_dismiss_button);
+ mRestartButton = mLayout.findViewById(R.id.letterbox_restart_dialog_restart_button);
+ mDialogContainer = mLayout.findViewById(R.id.letterbox_restart_dialog_container);
+ mDontRepeatCheckBox = mLayout.findViewById(R.id.letterbox_restart_dialog_checkbox);
+ mLayout.setDismissOnClickListener(mDismissCallback);
+ mLayout.setRestartOnClickListener(mRestartCallback);
+ }
+
+ @Test
+ public void testOnFinishInflate() {
+ assertEquals(mLayout.getDialogContainerView(),
+ mLayout.findViewById(R.id.letterbox_restart_dialog_container));
+ assertEquals(mLayout.getDialogTitle(),
+ mLayout.findViewById(R.id.letterbox_restart_dialog_title));
+ assertEquals(mLayout.getBackgroundDimDrawable(), mLayout.getBackground());
+ assertEquals(mLayout.getBackground().getAlpha(), 0);
+ }
+
+ @Test
+ public void testOnDismissButtonClicked() {
+ assertTrue(mDismissButton.performClick());
+
+ verify(mDismissCallback).run();
+ }
+
+ @Test
+ public void testOnRestartButtonClickedWithoutCheckbox() {
+ mDontRepeatCheckBox.setChecked(false);
+ assertTrue(mRestartButton.performClick());
+
+ verify(mRestartCallback).accept(false);
+ }
+
+ @Test
+ public void testOnRestartButtonClickedWithCheckbox() {
+ mDontRepeatCheckBox.setChecked(true);
+ assertTrue(mRestartButton.performClick());
+
+ verify(mRestartCallback).accept(true);
+ }
+
+ @Test
+ public void testOnBackgroundClickedDoesntDismiss() {
+ assertFalse(mLayout.performClick());
+
+ verify(mDismissCallback, never()).run();
+ }
+
+ @Test
+ public void testOnDialogContainerClicked() {
+ assertTrue(mDialogContainer.performClick());
+
+ verify(mDismissCallback, never()).run();
+ verify(mRestartCallback, never()).accept(anyBoolean());
+ }
+
+ @Test
+ public void testSetDismissOnClickListenerNull() {
+ mLayout.setDismissOnClickListener(null);
+
+ assertFalse(mDismissButton.performClick());
+ assertFalse(mLayout.performClick());
+ assertTrue(mDialogContainer.performClick());
+
+ verify(mDismissCallback, never()).run();
+ }
+
+ @Test
+ public void testSetRestartOnClickListenerNull() {
+ mLayout.setRestartOnClickListener(null);
+
+ assertFalse(mRestartButton.performClick());
+ assertFalse(mLayout.performClick());
+ assertTrue(mDialogContainer.performClick());
+
+ verify(mRestartCallback, never()).accept(anyBoolean());
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 08af3d3eecfe3b6f5d91bacf50243d51946b6d97..43f8f7b074bfc87ef81bbbf6809c6c163f1642c4 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
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -279,7 +280,7 @@ public class DesktopModeControllerTest extends ShellTestCase {
}
@Test
- public void testShowDesktopApps_appsAlreadyVisible_doesNothing() {
+ public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() {
final RunningTaskInfo task1 = createFreeformTask();
mDesktopModeTaskRepository.addActiveTask(task1.taskId);
mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
@@ -294,8 +295,17 @@ public class DesktopModeControllerTest extends ShellTestCase {
mController.showDesktopApps();
final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // No reordering needed.
- assertThat(wct.getHierarchyOps()).isEmpty();
+ // Check wct has reorder calls
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ // Task 1 appeared first, must be first reorder to top.
+ HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
+
+ // Task 2 appeared last, must be last reorder to top.
+ HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
}
@Test
@@ -324,6 +334,41 @@ public class DesktopModeControllerTest extends ShellTestCase {
assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
}
+ @Test
+ public void testGetVisibleTaskCount_noTasks_returnsZero() {
+ assertThat(mController.getVisibleTaskCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
+ RunningTaskInfo task1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+
+ RunningTaskInfo task2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+
+ assertThat(mController.getVisibleTaskCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
+ RunningTaskInfo task1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+
+ RunningTaskInfo task2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, false /* visible */);
+
+ assertThat(mController.getVisibleTaskCount()).isEqualTo(1);
+ }
+
@Test
public void testHandleTransitionRequest_desktopModeNotActive_returnsNull() {
when(DesktopModeStatus.isActive(any())).thenReturn(false);
@@ -402,7 +447,7 @@ public class DesktopModeControllerTest extends ShellTestCase {
final ArgumentCaptor arg = ArgumentCaptor.forClass(
WindowContainerTransaction.class);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any());
+ verify(mTransitions).startTransition(eq(TRANSIT_NONE), arg.capture(), any());
} else {
verify(mShellTaskOrganizer).applyTransaction(arg.capture());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 1e43a5983821a67096b519e5d2e71c9d244ceed7..45cb3a062cc5526eaec91ae618fcfbcac7e77615 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -140,6 +140,36 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
}
+ @Test
+ fun getVisibleTaskCount() {
+ // No tasks, count is 0
+ assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+
+ // New task increments count to 1
+ repo.updateVisibleFreeformTasks(taskId = 1, visible = true)
+ assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+
+ // Visibility update to same task does not increase count
+ repo.updateVisibleFreeformTasks(taskId = 1, visible = true)
+ assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+
+ // Second task visible increments count
+ repo.updateVisibleFreeformTasks(taskId = 2, visible = true)
+ assertThat(repo.getVisibleTaskCount()).isEqualTo(2)
+
+ // Hiding a task decrements count
+ repo.updateVisibleFreeformTasks(taskId = 1, visible = false)
+ assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+
+ // Hiding all tasks leaves count at 0
+ repo.updateVisibleFreeformTasks(taskId = 2, visible = false)
+ assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+
+ // Hiding a not existing task, count remains at 0
+ repo.updateVisibleFreeformTasks(taskId = 999, visible = false)
+ assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+ }
+
@Test
fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
repo.addOrMoveFreeformTaskToTop(5)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 9a92879bde1f9cbb88f8ee521d433b4d5928f74d..95e78a8b7bcc115c448b919ba9fcd84a95156a8d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -26,6 +26,8 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.os.Binder
import android.testing.AndroidTestingRunner
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionRequestInfo
@@ -55,6 +57,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito
@@ -141,7 +144,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.showDesktopApps()
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_NONE)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -150,8 +153,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun showDesktopApps_appsAlreadyVisible_doesNothing() {
- setUpHomeTask()
+ fun showDesktopApps_appsAlreadyVisible_bringsToFront() {
+ val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskVisible(task1)
@@ -159,7 +162,12 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.showDesktopApps()
- verifyWCTNotExecuted()
+ val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
}
@Test
@@ -172,7 +180,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.showDesktopApps()
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_NONE)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -186,16 +194,37 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.showDesktopApps()
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_NONE)
assertThat(wct.hierarchyOps).hasSize(1)
wct.assertReorderAt(index = 0, homeTask)
}
+ @Test
+ fun getVisibleTaskCount_noTasks_returnsZero() {
+ assertThat(controller.getVisibleTaskCount()).isEqualTo(0)
+ }
+
+ @Test
+ fun getVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
+ setUpHomeTask()
+ setUpFreeformTask().also(::markTaskVisible)
+ setUpFreeformTask().also(::markTaskVisible)
+ assertThat(controller.getVisibleTaskCount()).isEqualTo(2)
+ }
+
+ @Test
+ fun getVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
+ setUpHomeTask()
+ setUpFreeformTask().also(::markTaskVisible)
+ setUpFreeformTask().also(::markTaskHidden)
+ assertThat(controller.getVisibleTaskCount()).isEqualTo(1)
+ }
+
@Test
fun moveToDesktop() {
val task = setUpFullscreenTask()
controller.moveToDesktop(task)
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
}
@@ -206,11 +235,28 @@ class DesktopTasksControllerTest : ShellTestCase() {
verifyWCTNotExecuted()
}
+ @Test
+ fun moveToDesktop_otherFreeformTasksBroughtToFront() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val fullscreenTask = setUpFullscreenTask()
+ markTaskHidden(freeformTask)
+
+ controller.moveToDesktop(fullscreenTask)
+
+ with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ assertThat(hierarchyOps).hasSize(3)
+ assertReorderSequence(homeTask, freeformTask, fullscreenTask)
+ assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+ }
+
@Test
fun moveToFullscreen() {
val task = setUpFreeformTask()
controller.moveToFullscreen(task)
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
@@ -372,10 +418,12 @@ class DesktopTasksControllerTest : ShellTestCase() {
desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = false)
}
- private fun getLatestWct(): WindowContainerTransaction {
+ private fun getLatestWct(
+ @WindowManager.TransitionType expectTransition: Int = TRANSIT_OPEN
+ ): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
- verify(transitions).startTransition(anyInt(), arg.capture(), isNull())
+ verify(transitions).startTransition(eq(expectTransition), arg.capture(), isNull())
} else {
verify(shellTaskOrganizer).applyTransaction(arg.capture())
}
@@ -406,3 +454,9 @@ private fun WindowContainerTransaction.assertReorderAt(index: Int, task: Running
assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
assertThat(op.container).isEqualTo(task.token.asBinder())
}
+
+private fun WindowContainerTransaction.assertReorderSequence(vararg tasks: RunningTaskInfo) {
+ for (i in tasks.indices) {
+ assertReorderAt(i, tasks[i])
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index b6dbcf204364b170ca6dce7414c003fc568461be..523cb6629d9a3e6045aaa289ed5943e2d941f729 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -48,7 +48,6 @@ import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -82,7 +81,7 @@ public class DragAndDropControllerTest extends ShellTestCase {
@Mock
private ShellExecutor mMainExecutor;
@Mock
- private SplitScreenController mSplitScreenController;
+ private WindowManager mWindowManager;
private DragAndDropController mController;
@@ -99,11 +98,6 @@ public class DragAndDropControllerTest extends ShellTestCase {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
- @Test
- public void instantiateController_registerConfigChangeListener() {
- verify(mShellController, times(1)).addConfigurationChangeListener(any());
- }
-
@Test
public void testIgnoreNonDefaultDisplays() {
final int nonDefaultDisplayId = 12345;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index ecfb427dbcedcc60f969f7f111df8669130daf09..58e91cb50c7a4c1caf66c32bec0cae10b1a31695 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -77,6 +78,7 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
@Mock private ShellInit mShellInit;
@Mock private ShellCommandHandler mShellCommandHandler;
@Mock private DisplayInsetsController mDisplayInsetsController;
+ @Mock private Resources mResources;
KidsModeTaskOrganizer mOrganizer;
@@ -89,10 +91,12 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
} catch (RemoteException e) {
}
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
- mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mShellInit, mShellCommandHandler,
- mTaskOrganizerController, mSyncTransactionQueue, mDisplayController,
- mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver,
- mTestExecutor, mHandler));
+ doReturn(mResources).when(mContext).getResources();
+ final KidsModeTaskOrganizer kidsModeTaskOrganizer = new KidsModeTaskOrganizer(mContext,
+ mShellInit, mShellCommandHandler, mTaskOrganizerController, mSyncTransactionQueue,
+ mDisplayController, mDisplayInsetsController, Optional.empty(), Optional.empty(),
+ mObserver, mTestExecutor, mHandler);
+ mOrganizer = spy(kidsModeTaskOrganizer);
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
}
@@ -112,6 +116,8 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
verify(mOrganizer, times(1)).registerOrganizer();
verify(mOrganizer, times(1)).createRootTask(
eq(DEFAULT_DISPLAY), eq(WINDOWING_MODE_FULLSCREEN), eq(mOrganizer.mCookie));
+ verify(mOrganizer, times(1))
+ .setOrientationRequestPolicy(eq(true), any(), any());
final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
@@ -132,10 +138,11 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
doReturn(false).when(mObserver).isEnabled();
mOrganizer.updateKidsModeState();
-
verify(mOrganizer, times(1)).disable();
verify(mOrganizer, times(1)).unregisterOrganizer();
verify(mOrganizer, times(1)).deleteRootTask(rootTask.token);
+ verify(mOrganizer, times(1))
+ .setOrientationRequestPolicy(eq(false), any(), any());
assertThat(mOrganizer.mLaunchRootLeash).isNull();
assertThat(mOrganizer.mLaunchRootTask).isNull();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 262e4290ef44bec1c51950711fa89c65a6cc9ea6..ec264a643785f43785bfa0f1c6610b7cb9a10b0b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import org.junit.Before;
import org.junit.Test;
@@ -57,17 +58,22 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private DisplayInfo mDefaultDisplayInfo;
private PipBoundsState mPipBoundsState;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
@Before
public void setUp() throws Exception {
initializeMockResources();
- mPipBoundsState = new PipBoundsState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
- new PipSnapAlgorithm(), new PipKeepClearAlgorithm() {});
+ new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
+ mPipSizeSpecHandler);
- mPipBoundsState.setDisplayLayout(
- new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true));
+ DisplayLayout layout =
+ new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
+ mPipBoundsState.setDisplayLayout(layout);
+ mPipSizeSpecHandler.setDisplayLayout(layout);
}
private void initializeMockResources() {
@@ -120,9 +126,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
@Test
public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
- final Size defaultSize = mPipBoundsAlgorithm.getSizeForAspectRatio(DEFAULT_ASPECT_RATIO,
- DEFAULT_MIN_EDGE_SIZE, mDefaultDisplayInfo.logicalWidth,
- mDefaultDisplayInfo.logicalHeight);
+ final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO);
mPipBoundsState.setOverrideMinSize(null);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
@@ -296,9 +300,9 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
(MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
};
final Size[] minimalSizes = new Size[] {
- new Size((int) (100 * aspectRatios[0]), 100),
- new Size((int) (100 * aspectRatios[1]), 100),
- new Size((int) (100 * aspectRatios[2]), 100)
+ new Size((int) (200 * aspectRatios[0]), 200),
+ new Size((int) (200 * aspectRatios[1]), 200),
+ new Size((int) (200 * aspectRatios[2]), 200)
};
for (int i = 0; i < aspectRatios.length; i++) {
final float aspectRatio = aspectRatios[i];
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 8e30f65cee7823ab1a9a817f728c130cca0d84b5..341a451eeb4365a5b7153998e888aff368fb5edb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import org.junit.Before;
import org.junit.Test;
@@ -57,7 +58,7 @@ public class PipBoundsStateTest extends ShellTestCase {
@Before
public void setUp() {
- mPipBoundsState = new PipBoundsState(mContext);
+ mPipBoundsState = new PipBoundsState(mContext, new PipSizeSpecHandler(mContext));
mTestComponentName1 = new ComponentName(mContext, "component1");
mTestComponentName2 = new ComponentName(mContext, "component2");
}
@@ -161,10 +162,10 @@ public class PipBoundsStateTest extends ShellTestCase {
@Test
public void testSetOverrideMinSize_notChanged_callbackNotInvoked() {
final Runnable callback = mock(Runnable.class);
- mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+ mPipBoundsState.setOverrideMinSize(new Size(100, 150));
mPipBoundsState.setOnMinimalSizeChangeCallback(callback);
- mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+ mPipBoundsState.setOverrideMinSize(new Size(100, 150));
verify(callback, never()).run();
}
@@ -174,11 +175,11 @@ public class PipBoundsStateTest extends ShellTestCase {
mPipBoundsState.setOverrideMinSize(null);
assertEquals(0, mPipBoundsState.getOverrideMinEdgeSize());
- mPipBoundsState.setOverrideMinSize(new Size(5, 10));
- assertEquals(5, mPipBoundsState.getOverrideMinEdgeSize());
+ mPipBoundsState.setOverrideMinSize(new Size(100, 110));
+ assertEquals(100, mPipBoundsState.getOverrideMinEdgeSize());
- mPipBoundsState.setOverrideMinSize(new Size(15, 10));
- assertEquals(10, mPipBoundsState.getOverrideMinEdgeSize());
+ mPipBoundsState.setOverrideMinSize(new Size(150, 200));
+ assertEquals(150, mPipBoundsState.getOverrideMinEdgeSize());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 90880772b25d2b260362259906a61de159b803d9..e907cd3ca0ad37d0947595580aa82b4f8484773a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -53,6 +53,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import org.junit.Before;
@@ -86,6 +87,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
private PipBoundsState mPipBoundsState;
private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
private ComponentName mComponent1;
private ComponentName mComponent2;
@@ -95,13 +97,15 @@ public class PipTaskOrganizerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
- mPipBoundsState = new PipBoundsState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
- new PipSnapAlgorithm(), new PipKeepClearAlgorithm() {});
+ new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
+ mPipSizeSpecHandler);
mMainExecutor = new TestShellExecutor();
- mPipTaskOrganizer = new PipTaskOrganizer(mContext,
- mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
+ mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
+ mPipTransitionState, mPipBoundsState, mPipSizeSpecHandler,
mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
mMockPipParamsChangedForwarder, mMockOptionalSplitScreen, mMockDisplayController,
@@ -253,8 +257,10 @@ public class PipTaskOrganizerTest extends ShellTestCase {
private void preparePipTaskOrg() {
final DisplayInfo info = new DisplayInfo();
- mPipBoundsState.setDisplayLayout(new DisplayLayout(info,
- mContext.getResources(), true, true));
+ DisplayLayout layout = new DisplayLayout(info,
+ mContext.getResources(), true, true);
+ mPipBoundsState.setDisplayLayout(layout);
+ mPipSizeSpecHandler.setDisplayLayout(layout);
mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
mPipTaskOrganizer.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
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 35c09a121a1caedb21c63fd60d9d0557e1a94499..a41a30e73baab942fbd56c11af1793cace874f41 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
@@ -53,6 +53,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.ShellExecutor;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
@@ -65,7 +66,6 @@ import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -108,11 +108,13 @@ public class PipControllerTest extends ShellTestCase {
@Mock private PipMotionHelper mMockPipMotionHelper;
@Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
@Mock private PipBoundsState mMockPipBoundsState;
+ @Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
@Mock private TaskStackListenerImpl mMockTaskStackListener;
@Mock private ShellExecutor mMockExecutor;
@Mock private Optional mMockOneHandedController;
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
@Mock private DisplayInsetsController mMockDisplayInsetsController;
+ @Mock private TabletopModeController mMockTabletopModeController;
@Mock private DisplayLayout mMockDisplayLayout1;
@Mock private DisplayLayout mMockDisplayLayout2;
@@ -130,11 +132,12 @@ public class PipControllerTest extends ShellTestCase {
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
- mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
- mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mMockPipParamsChangedForwarder,
- mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor);
+ mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
+ mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
+ mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
+ mMockWindowManagerShellWrapper, mMockTaskStackListener,
+ mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
+ mMockTabletopModeController, mMockOneHandedController, mMockExecutor);
mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -220,11 +223,12 @@ public class PipControllerTest extends ShellTestCase {
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
- mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
- mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mMockPipParamsChangedForwarder,
- mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor));
+ mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
+ mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
+ mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
+ mMockWindowManagerShellWrapper, mMockTaskStackListener,
+ mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
+ mMockTabletopModeController, mMockOneHandedController, mMockExecutor));
}
@Test
@@ -310,6 +314,7 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void onKeepClearAreasChanged_featureDisabled_pipBoundsStateDoesntChange() {
+ mPipController.setEnablePipKeepClearAlgorithm(false);
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
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 3bd2ae76ebfd53072421675cfdb9314b47d994c5..5f356c93d0a40a7f77f3d79ba526f6b61cef7b15 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
@@ -37,7 +37,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -85,15 +85,18 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
private PipBoundsState mPipBoundsState;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipBoundsState = new PipBoundsState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
- final PipKeepClearAlgorithm pipKeepClearAlgorithm =
- new PipKeepClearAlgorithm() {};
+ final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
+ new PipKeepClearAlgorithmInterface() {};
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
- mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm);
+ mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler);
final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
@@ -152,7 +155,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
mPipResizeGestureHandler.onPinchResize(upEvent);
verify(mPipTaskOrganizer, times(1))
- .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any());
+ .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any(), any());
assertTrue("The new size should be bigger than the original PiP size.",
mPipResizeGestureHandler.getLastResizeBounds().width()
@@ -191,7 +194,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
mPipResizeGestureHandler.onPinchResize(upEvent);
verify(mPipTaskOrganizer, times(1))
- .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any());
+ .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any(), any());
assertTrue("The new size should be smaller than the original PiP size.",
mPipResizeGestureHandler.getLastResizeBounds().width()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d9ff7d1f108902de0f8ecad91293f77fc22c002d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.phone;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.testing.AndroidTestingRunner;
+import android.util.Size;
+import android.view.DisplayInfo;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.exceptions.misusing.InvalidUseOfMatchersException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Unit test against {@link PipSizeSpecHandler} with feature flag on.
+ */
+@RunWith(AndroidTestingRunner.class)
+public class PipSizeSpecHandlerTest extends ShellTestCase {
+ /** A sample overridden min edge size. */
+ private static final int OVERRIDE_MIN_EDGE_SIZE = 40;
+ /** A sample default min edge size */
+ private static final int DEFAULT_MIN_EDGE_SIZE = 40;
+ /** Display edge size */
+ private static final int DISPLAY_EDGE_SIZE = 1000;
+ /** Default sizing percentage */
+ private static final float DEFAULT_PERCENT = 0.6f;
+ /** Minimum sizing percentage */
+ private static final float MIN_PERCENT = 0.5f;
+ /** Aspect ratio that the new PIP size spec logic optimizes for. */
+ private static final float OPTIMIZED_ASPECT_RATIO = 9f / 16;
+
+ /** A map of aspect ratios to be tested to expected sizes */
+ private static Map sExpectedMaxSizes;
+ private static Map sExpectedDefaultSizes;
+ private static Map sExpectedMinSizes;
+ /** A static mockito session object to mock {@link SystemProperties} */
+ private static StaticMockitoSession sStaticMockitoSession;
+
+ @Mock private Context mContext;
+ @Mock private Resources mResources;
+
+ private PipSizeSpecHandler mPipSizeSpecHandler;
+
+ /**
+ * Sets up static Mockito session for SystemProperties and mocks necessary static methods.
+ */
+ private static void setUpStaticSystemPropertiesSession() {
+ sStaticMockitoSession = mockitoSession()
+ .mockStatic(SystemProperties.class).startMocking();
+ // make sure the feature flag is on
+ when(SystemProperties.getBoolean(anyString(), anyBoolean())).thenReturn(true);
+ when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
+ String property = invocation.getArgument(0);
+ if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
+ return Float.toString(DEFAULT_PERCENT);
+ } else if (property.equals("com.android.wm.shell.pip.phone.min_percentage")) {
+ return Float.toString(MIN_PERCENT);
+ }
+
+ // throw an exception if illegal arguments are used for these tests
+ throw new InvalidUseOfMatchersException(
+ String.format("Argument %s does not match", property)
+ );
+ });
+ }
+
+ /**
+ * Initializes the map with the aspect ratios to be tested and corresponding expected max sizes.
+ */
+ private static void initExpectedSizes() {
+ sExpectedMaxSizes = new HashMap<>();
+ sExpectedDefaultSizes = new HashMap<>();
+ sExpectedMinSizes = new HashMap<>();
+
+ sExpectedMaxSizes.put(16f / 9, new Size(1000, 562));
+ sExpectedDefaultSizes.put(16f / 9, new Size(600, 337));
+ sExpectedMinSizes.put(16f / 9, new Size(499, 281));
+
+ sExpectedMaxSizes.put(4f / 3, new Size(892, 669));
+ sExpectedDefaultSizes.put(4f / 3, new Size(535, 401));
+ sExpectedMinSizes.put(4f / 3, new Size(445, 334));
+
+ sExpectedMaxSizes.put(3f / 4, new Size(669, 892));
+ sExpectedDefaultSizes.put(3f / 4, new Size(401, 535));
+ sExpectedMinSizes.put(3f / 4, new Size(334, 445));
+
+ sExpectedMaxSizes.put(9f / 16, new Size(562, 999));
+ sExpectedDefaultSizes.put(9f / 16, new Size(337, 599));
+ sExpectedMinSizes.put(9f / 16, new Size(281, 499));
+ }
+
+ private void forEveryTestCaseCheck(Map expectedSizes,
+ Function callback) {
+ for (Map.Entry expectedSizesEntry : expectedSizes.entrySet()) {
+ float aspectRatio = expectedSizesEntry.getKey();
+ Size expectedSize = expectedSizesEntry.getValue();
+
+ Assert.assertEquals(expectedSize, callback.apply(aspectRatio));
+ }
+ }
+
+ @Before
+ public void setUp() {
+ initExpectedSizes();
+ setUpStaticSystemPropertiesSession();
+
+ when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
+ when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
+ when(mResources.getString(anyInt())).thenReturn("0x0");
+ when(mResources.getDisplayMetrics())
+ .thenReturn(getContext().getResources().getDisplayMetrics());
+
+ // set up the mock context for spec handler specifically
+ when(mContext.getResources()).thenReturn(mResources);
+
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+
+ // no overridden min edge size by default
+ mPipSizeSpecHandler.setOverrideMinSize(null);
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
+ displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
+
+ // use the parent context (not the mocked one) to obtain the display layout
+ // this is done to avoid unnecessary mocking while allowing for custom display dimensions
+ DisplayLayout displayLayout = new DisplayLayout(displayInfo, getContext().getResources(),
+ false, false);
+ mPipSizeSpecHandler.setDisplayLayout(displayLayout);
+ }
+
+ @After
+ public void cleanUp() {
+ sStaticMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testGetMaxSize() {
+ forEveryTestCaseCheck(sExpectedMaxSizes,
+ (aspectRatio) -> mPipSizeSpecHandler.getMaxSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetDefaultSize() {
+ forEveryTestCaseCheck(sExpectedDefaultSizes,
+ (aspectRatio) -> mPipSizeSpecHandler.getDefaultSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetMinSize() {
+ forEveryTestCaseCheck(sExpectedMinSizes,
+ (aspectRatio) -> mPipSizeSpecHandler.getMinSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetSizeForAspectRatio_noOverrideMinSize() {
+ // an initial size with 16:9 aspect ratio
+ Size initSize = new Size(600, 337);
+
+ Size expectedSize = new Size(337, 599);
+ Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+
+ Assert.assertEquals(expectedSize, actualSize);
+ }
+
+ @Test
+ public void testGetSizeForAspectRatio_withOverrideMinSize() {
+ // an initial size with a 1:1 aspect ratio
+ mPipSizeSpecHandler.setOverrideMinSize(new Size(OVERRIDE_MIN_EDGE_SIZE,
+ OVERRIDE_MIN_EDGE_SIZE));
+ // make sure initial size is same as override min size
+ Size initSize = mPipSizeSpecHandler.getOverrideMinSize();
+
+ Size expectedSize = new Size(40, 71);
+ Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+
+ Assert.assertEquals(expectedSize, actualSize);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 474d6aaf4623b8378de6e0f97387320b7ab0ce85..1515d6057baf68bc68679c2304ce15f36016506e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.Size;
import androidx.test.filters.SmallTest;
@@ -34,7 +35,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -90,6 +91,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
private PipSnapAlgorithm mPipSnapAlgorithm;
private PipMotionHelper mMotionHelper;
private PipResizeGestureHandler mPipResizeGestureHandler;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
private DisplayLayout mDisplayLayout;
private Rect mInsetBounds;
@@ -103,16 +105,17 @@ public class PipTouchHandlerTest extends ShellTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipBoundsState = new PipBoundsState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
- new PipKeepClearAlgorithm() {});
+ new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
- mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, pipMotionHelper,
- mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
+ mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer,
+ pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
// We aren't actually using ShellInit, so just call init directly
mPipTouchHandler.onInit();
mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
@@ -122,6 +125,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
mPipBoundsState.setDisplayLayout(mDisplayLayout);
+ mPipSizeSpecHandler.setDisplayLayout(mDisplayLayout);
mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET,
mPipBoundsState.getDisplayBounds().top + INSET,
mPipBoundsState.getDisplayBounds().right - INSET,
@@ -154,17 +158,22 @@ public class PipTouchHandlerTest extends ShellTestCase {
mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
+ // getting the expected min and max size
+ float aspectRatio = (float) mPipBounds.width() / mPipBounds.height();
+ Size expectedMinSize = mPipSizeSpecHandler.getMinSize(aspectRatio);
+ Size expectedMaxSize = mPipSizeSpecHandler.getMaxSize(aspectRatio);
+
assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds());
verify(mPipResizeGestureHandler, times(1))
- .updateMinSize(mPipBounds.width(), mPipBounds.height());
+ .updateMinSize(expectedMinSize.getWidth(), expectedMinSize.getHeight());
verify(mPipResizeGestureHandler, times(1))
- .updateMaxSize(shorterLength - 2 * mInsetBounds.left,
- shorterLength - 2 * mInsetBounds.left);
+ .updateMaxSize(expectedMaxSize.getWidth(), expectedMaxSize.getHeight());
}
@Test
public void updateMovementBounds_withImeAdjustment_movesPip() {
+ mPipTouchHandler.setEnablePipKeepClearAlgorithm(false);
mFromImeAdjustment = true;
mPipTouchHandler.onImeVisibilityChanged(true /* imeVisible */, mImeHeight);
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 82392ad9a3eb932847c728dcc05f8e45c2700576..b542fae060d1a282d7183653f9142ba0a2b18d93 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
@@ -253,10 +253,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
- public void testGetRecentTasks_groupActiveFreeformTasks() {
+ public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isActive(any())).thenReturn(true);
+ when(DesktopModeStatus.isProto2Enabled()).thenReturn(true);
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -292,6 +292,39 @@ public class RecentTasksControllerTest extends ShellTestCase {
mockitoSession.finishMocking();
}
+ @Test
+ public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() {
+ StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+ DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isProto2Enabled()).thenReturn(false);
+
+ ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ setRawList(t1, t2, t3, t4);
+
+ when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+
+ ArrayList recentTasks = mRecentTasksController.getRecentTasks(
+ MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+ // Expect no grouping of tasks
+ assertEquals(4, recentTasks.size());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(0).getType());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(1).getType());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(2).getType());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(3).getType());
+
+ assertEquals(t1, recentTasks.get(0).getTaskInfo1());
+ assertEquals(t2, recentTasks.get(1).getTaskInfo1());
+ assertEquals(t3, recentTasks.get(2).getTaskInfo1());
+ assertEquals(t4, recentTasks.get(3).getTaskInfo1());
+
+ mockitoSession.finishMocking();
+ }
+
@Test
public void testRemovedTaskRemovesSplit() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
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 ea3af9d96aa44fffab0399c6f65434f45123ffdd..d0e26019f9bf1d3924bd798d418bb9d0d7bd07ee 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
@@ -27,8 +27,6 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -201,7 +199,6 @@ public class SplitScreenControllerTests extends ShellTestCase {
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
- doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
@@ -222,7 +219,6 @@ public class SplitScreenControllerTests extends ShellTestCase {
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
- doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 65e1ea881b264b950f4379e6e9ca9d272fbfb5f3..0b528ba3a9ed1f33b741240b2635ccfe82745df5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -29,11 +29,13 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
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.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -45,6 +47,8 @@ import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -57,6 +61,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -65,6 +70,8 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -98,11 +105,7 @@ public class StageCoordinatorTests extends ShellTestCase {
@Mock
private DisplayInsetsController mDisplayInsetsController;
@Mock
- private Transitions mTransitions;
- @Mock
private TransactionPool mTransactionPool;
- @Mock
- private ShellExecutor mMainExecutor;
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -112,11 +115,16 @@ public class StageCoordinatorTests extends ShellTestCase {
private SurfaceControl mRootLeash;
private ActivityManager.RunningTaskInfo mRootTask;
private StageCoordinator mStageCoordinator;
+ private Transitions mTransitions;
+ private final TestShellExecutor mMainExecutor = new TestShellExecutor();
+ private final ShellExecutor mAnimExecutor = new TestShellExecutor();
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@Before
@UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
+ mTransitions = createTestTransitions();
mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
@@ -136,39 +144,48 @@ public class StageCoordinatorTests extends ShellTestCase {
}
@Test
- public void testMoveToStage() {
+ public void testMoveToStage_splitActiveBackground() {
+ when(mStageCoordinator.isSplitActive()).thenReturn(true);
+
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ verify(mSideStage).addTask(eq(task), eq(wct));
+ assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
+ assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
+ }
+
+ @Test
+ public void testMoveToStage_splitActiveForeground() {
+ when(mStageCoordinator.isSplitActive()).thenReturn(true);
+ when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
+ // Assume current side stage is top or left.
+ mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
- mStageCoordinator.moveToStage(task, STAGE_TYPE_MAIN, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- new WindowContainerTransaction());
- verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
+ final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ verify(mMainStage).addTask(eq(task), eq(wct));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
+ assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
- mStageCoordinator.moveToStage(task, STAGE_TYPE_SIDE, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- new WindowContainerTransaction());
- verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
- assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_TOP_OR_LEFT, wct);
+ verify(mSideStage).addTask(eq(task), eq(wct));
+ assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
+ assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
}
@Test
- public void testMoveToUndefinedStage() {
+ public void testMoveToStage_splitInctive() {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Verify move to undefined stage while split screen not activated moves task to side stage.
- when(mStageCoordinator.isSplitScreenVisible()).thenReturn(false);
- mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
- mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- new WindowContainerTransaction());
- verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
-
- // Verify move to undefined stage after split screen activated moves task based on position.
- when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
- assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
- mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT,
- new WindowContainerTransaction());
- verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
- assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
}
@Test
@@ -329,7 +346,20 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator.onFoldedStateChanged(true);
- verify(mStageCoordinator).onSplitScreenExit();
- verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
+ } else {
+ verify(mStageCoordinator).onSplitScreenExit();
+ verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+ }
+ }
+
+ private Transitions createTestTransitions() {
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mTaskOrganizer, mTransactionPool, mock(DisplayController.class), mMainExecutor,
+ mMainHandler, mAnimExecutor);
+ shellInit.init();
+ return t;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 5ee8bf3006a3030d5908d563b677ae35a884cde4..b31e20fcc4389039b57b8ccd9061ee675d77c76a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -126,12 +126,6 @@ public final class StageTaskListenerTests extends ShellTestCase {
verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
}
- @Test(expected = IllegalArgumentException.class)
- public void testUnknownTaskVanished() {
- final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
- mStageTaskListener.onTaskVanished(task);
- }
-
@Test
public void testTaskVanished() {
// With shell transitions, the transition manages status changes, so skip this test.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 355072116cb1c39b7cf2e4a1f53d5f9daf57e410..1d1aa795173ce040e753a22669a166b4b23e2939 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -49,6 +49,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import org.junit.Before;
import org.junit.Test;
@@ -73,6 +74,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
@Mock private Choreographer mMainChoreographer;
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
+ @Mock private SplitScreenController mSplitScreenController;
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private DesktopModeController mDesktopModeController;
@Mock private DesktopTasksController mDesktopTasksController;
@@ -98,6 +100,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
mSyncQueue,
Optional.of(mDesktopModeController),
Optional.of(mDesktopTasksController),
+ Optional.of(mSplitScreenController),
mDesktopModeWindowDecorFactory,
mMockInputMonitorFactory
);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8f84008e8d2d0b0f45d6d31d96141404004de1f2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.os.SystemClock
+import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
+import android.view.InputDevice
+import androidx.test.filters.SmallTest
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/**
+ * Tests for [DragDetector].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragDetectorTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DragDetectorTest {
+ private val motionEvents = mutableListOf()
+
+ @Mock
+ private lateinit var eventHandler: DragDetector.MotionEventHandler
+
+ private lateinit var dragDetector: DragDetector
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(eventHandler.handleMotionEvent(any())).thenReturn(true)
+
+ dragDetector = DragDetector(eventHandler)
+ dragDetector.setTouchSlop(SLOP)
+ }
+
+ @After
+ fun tearDown() {
+ motionEvents.forEach {
+ it.recycle()
+ }
+ motionEvents.clear()
+ }
+
+ @Test
+ fun testNoMove_passesDownAndUp() {
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+ }
+
+ @Test
+ fun testMoveInSlop_touch_passesDownAndUp() {
+ `when`(eventHandler.handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN
+ })).thenReturn(false)
+
+ assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+
+ val newX = X + SLOP - 1
+ assertFalse(
+ dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
+ verify(eventHandler, never()).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_MOVE
+ })
+
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+ }
+
+ @Test
+ fun testMoveInSlop_mouse_passesDownMoveAndUp() {
+ `when`(eventHandler.handleMotionEvent(argThat {
+ it.action == MotionEvent.ACTION_DOWN
+ })).thenReturn(false)
+
+ assertFalse(dragDetector.onMotionEvent(
+ createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+
+ val newX = X + SLOP - 1
+ assertTrue(dragDetector.onMotionEvent(
+ createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+
+ assertTrue(dragDetector.onMotionEvent(
+ createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_MOUSE
+ })
+ }
+
+ @Test
+ fun testMoveBeyondSlop_passesDownMoveAndUp() {
+ `when`(eventHandler.handleMotionEvent(argThat {
+ it.action == MotionEvent.ACTION_DOWN
+ })).thenReturn(false)
+
+ assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+
+ val newX = X + SLOP + 1
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
+ it.source == InputDevice.SOURCE_TOUCHSCREEN
+ })
+ }
+
+ @Test
+ fun testPassesHoverEnter() {
+ `when`(eventHandler.handleMotionEvent(argThat {
+ it.action == MotionEvent.ACTION_HOVER_ENTER
+ })).thenReturn(false)
+
+ assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y
+ })
+ }
+
+ @Test
+ fun testPassesHoverMove() {
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y
+ })
+ }
+
+ @Test
+ fun testPassesHoverExit() {
+ assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT)))
+ verify(eventHandler).handleMotionEvent(argThat {
+ return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y
+ })
+ }
+
+ private fun createMotionEvent(action: Int, x: Float = X, y: Float = Y, isTouch: Boolean = true):
+ MotionEvent {
+ val time = SystemClock.uptimeMillis()
+ val ev = MotionEvent.obtain(time, time, action, x, y, 0)
+ ev.source = if (isTouch) InputDevice.SOURCE_TOUCHSCREEN else InputDevice.SOURCE_MOUSE
+ motionEvents.add(ev)
+ return ev
+ }
+
+ companion object {
+ private const val SLOP = 10
+ private const val X = 123f
+ private const val Y = 234f
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index ac10ddb0116a7108d824fcebedf4f24514c417e9..94c064bda7631f2d463e13e21a0ec338a1d64dd0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -1,24 +1,32 @@
package com.android.wm.shell.windowdecor
import android.app.ActivityManager
+import android.app.WindowConfiguration
import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
+import android.view.Display
import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
import androidx.test.filters.SmallTest
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
/**
@@ -43,6 +51,13 @@ class TaskPositionerTest : ShellTestCase() {
@Mock
private lateinit var taskBinder: IBinder
+ @Mock
+ private lateinit var mockDisplayController: DisplayController
+ @Mock
+ private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock
+ private lateinit var mockDisplay: Display
+
private lateinit var taskPositioner: TaskPositioner
@Before
@@ -52,19 +67,117 @@ class TaskPositionerTest : ShellTestCase() {
taskPositioner = TaskPositioner(
mockShellTaskOrganizer,
mockWindowDecoration,
+ mockDisplayController,
mockDragStartListener
)
+
`when`(taskToken.asBinder()).thenReturn(taskBinder)
+ `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+ `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+
mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
token = taskToken
+ minWidth = MIN_WIDTH
+ minHeight = MIN_HEIGHT
+ defaultMinSize = DEFAULT_MIN
+ displayId = DISPLAY_ID
configuration.windowConfiguration.bounds = STARTING_BOUNDS
}
+ mockWindowDecoration.mDisplay = mockDisplay
+ `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+ }
+
+ @Test
+ fun testDragResize_notMove_skipsTransactionOnEnd() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10
+ )
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_noEffectiveMove_skipsTransactionOnMoveAndEnd() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragPositioningMove(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10
+ )
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_hasEffectiveMove_issuesTransactionOnMoveAndEnd() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragPositioningMove(
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat()
+ )
+ val rectAfterMove = Rect(STARTING_BOUNDS)
+ rectAfterMove.right += 10
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterMove
+ }
+ })
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10
+ )
+ val rectAfterEnd = Rect(rectAfterMove)
+ rectAfterEnd.top += 10
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterEnd
+ }
+ })
}
@Test
fun testDragResize_move_skipsDragResizingFlag() {
- taskPositioner.onDragResizeStart(
+ taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // Move
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
@@ -73,12 +186,12 @@ class TaskPositionerTest : ShellTestCase() {
// Move the task 10px to the right.
val newX = STARTING_BOUNDS.left.toFloat() + 10
val newY = STARTING_BOUNDS.top.toFloat()
- taskPositioner.onDragResizeMove(
+ taskPositioner.onDragPositioningMove(
newX,
newY
)
- taskPositioner.onDragResizeEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(newX, newY)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -91,7 +204,7 @@ class TaskPositionerTest : ShellTestCase() {
@Test
fun testDragResize_resize_setsDragResizingFlag() {
- taskPositioner.onDragResizeStart(
+ taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
@@ -100,12 +213,12 @@ class TaskPositionerTest : ShellTestCase() {
// Resize the task by 10px to the right.
val newX = STARTING_BOUNDS.right.toFloat() + 10
val newY = STARTING_BOUNDS.top.toFloat()
- taskPositioner.onDragResizeMove(
+ taskPositioner.onDragPositioningMove(
newX,
newY
)
- taskPositioner.onDragResizeEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(newX, newY)
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -123,8 +236,318 @@ class TaskPositionerTest : ShellTestCase() {
})
}
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenLessThanMin() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to width of 95px and height of 5px with min width of 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 5
+ val newY = STARTING_BOUNDS.top.toFloat() + 95
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.top ==
+ STARTING_BOUNDS.top &&
+ change.configuration.windowConfiguration.bounds.bottom ==
+ STARTING_BOUNDS.bottom
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenLessThanMin() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to height of 95px and width of 5px with min width of 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 95
+ val newY = STARTING_BOUNDS.top.toFloat() + 5
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.right ==
+ STARTING_BOUNDS.right &&
+ change.configuration.windowConfiguration.bounds.left ==
+ STARTING_BOUNDS.left
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenNegative() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to height of -5px and width of 95px
+ val newX = STARTING_BOUNDS.right.toFloat() - 5
+ val newY = STARTING_BOUNDS.top.toFloat() + 105
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.top ==
+ STARTING_BOUNDS.top &&
+ change.configuration.windowConfiguration.bounds.bottom ==
+ STARTING_BOUNDS.bottom
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenNegative() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to width of -5px and height of 95px
+ val newX = STARTING_BOUNDS.right.toFloat() - 105
+ val newY = STARTING_BOUNDS.top.toFloat() + 5
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.right ==
+ STARTING_BOUNDS.right &&
+ change.configuration.windowConfiguration.bounds.left ==
+ STARTING_BOUNDS.left
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsRunsWhenResizeBoundsValid() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to height 20px and width 20px with both min height/width equal to 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 80
+ val newY = STARTING_BOUNDS.top.toFloat() + 80
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotRunWithNegativeHeightAndWidth() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to height 5px and width 5px with both min height/width equal to 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 95
+ val newY = STARTING_BOUNDS.top.toFloat() + 95
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_useDefaultMinWhenMinWidthInvalid() {
+ mockWindowDecoration.mTaskInfo.minWidth = -1
+
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to width and height of 3px with invalid minWidth = -1 and defaultMinSize = 5px
+ val newX = STARTING_BOUNDS.right.toFloat() - 97
+ val newY = STARTING_BOUNDS.top.toFloat() + 97
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_useMinWidthWhenValid() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to width and height of 7px with valid minWidth = 10px and defaultMinSize = 5px
+ val newX = STARTING_BOUNDS.right.toFloat() - 93
+ val newY = STARTING_BOUNDS.top.toFloat() + 93
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ fun testDragResize_toDisallowedBounds_freezesAtLimit() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat()
+ )
+
+ // Resize the task by 10px to the right and bottom, a valid destination
+ val newBounds = Rect(
+ STARTING_BOUNDS.left,
+ STARTING_BOUNDS.top,
+ STARTING_BOUNDS.right + 10,
+ STARTING_BOUNDS.bottom + 10)
+ taskPositioner.onDragPositioningMove(
+ newBounds.right.toFloat(),
+ newBounds.bottom.toFloat()
+ )
+
+ // Resize the task by another 10px to the right (allowed) and to just in the disallowed
+ // area of the Y coordinate.
+ val newBounds2 = Rect(
+ newBounds.left,
+ newBounds.top,
+ newBounds.right + 10,
+ DISALLOWED_RESIZE_AREA.top
+ )
+ taskPositioner.onDragPositioningMove(
+ newBounds2.right.toFloat(),
+ newBounds2.bottom.toFloat()
+ )
+
+ taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat())
+
+ // The first resize falls in the allowed area, verify there's a change for it.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(newBounds)
+ }
+ })
+ // The second resize falls in the disallowed area, verify there's no change for it.
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(newBounds2)
+ }
+ })
+ // Instead, there should be a change for its allowed portion (the X movement) with the Y
+ // staying frozen in the last valid resize position.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(
+ Rect(
+ newBounds2.left,
+ newBounds2.top,
+ newBounds2.right,
+ newBounds.bottom // Stayed at the first resize destination.
+ )
+ )
+ }
+ })
+ }
+
+ private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
+ return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
+ bounds == configuration.windowConfiguration.bounds
+ }
+
companion object {
private const val TASK_ID = 5
+ private const val MIN_WIDTH = 10
+ private const val MIN_HEIGHT = 10
+ private const val DENSITY_DPI = 20
+ private const val DEFAULT_MIN = 40
+ private const val DISPLAY_ID = 1
+ private const val NAVBAR_HEIGHT = 50
+ private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ private val DISALLOWED_RESIZE_AREA = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom)
+ private val STABLE_BOUNDS = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+ )
}
}
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 ec4f17fd072ba90b8a5d43eab24d8b4f596abfc3..7e39b5b8f2ce81583aaabe3473baf0460443c628 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -107,6 +107,9 @@ public class WindowDecorationTests extends ShellTestCase {
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
+ private int mCaptionMenuWidthId;
+ private int mCaptionMenuShadowRadiusId;
+ private int mCaptionMenuCornerRadiusId;
@Before
public void setUp() {
@@ -116,8 +119,9 @@ public class WindowDecorationTests extends ShellTestCase {
mRelayoutParams.mLayoutResId = 0;
mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
- // Caption should have fixed width except in testLayoutResultCalculation_fullWidthCaption()
- mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
+ mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width;
+ mCaptionMenuShadowRadiusId = R.dimen.test_caption_menu_shadow_radius;
+ mCaptionMenuCornerRadiusId = R.dimen.test_caption_menu_corner_radius;
mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
@@ -240,7 +244,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
- verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64);
+ verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
@@ -248,7 +252,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlViewHost)
.setView(same(mMockView),
argThat(lp -> lp.height == 64
- && lp.width == 432
+ && lp.width == 300
&& (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
if (ViewRootImpl.CAPTION_ON_SHELL) {
verify(mMockView).setTaskFocusState(true);
@@ -431,7 +435,19 @@ public class WindowDecorationTests extends ShellTestCase {
verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
verify(additionalWindowSurfaceBuilder).build();
verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
- verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 432, 64);
+ final int width = WindowDecoration.loadDimensionPixelSize(
+ mContext.getResources(), mCaptionMenuWidthId);
+ final int height = WindowDecoration.loadDimensionPixelSize(
+ mContext.getResources(), mRelayoutParams.mCaptionHeightId);
+ verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
+ final int shadowRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(),
+ mCaptionMenuShadowRadiusId);
+ verify(mMockSurfaceControlAddWindowT)
+ .setShadowRadius(additionalWindowSurface, shadowRadius);
+ final int cornerRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(),
+ mCaptionMenuCornerRadiusId);
+ verify(mMockSurfaceControlAddWindowT)
+ .setCornerRadius(additionalWindowSurface, cornerRadius);
verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
.create(any(), eq(defaultDisplay), any());
@@ -484,7 +500,6 @@ public class WindowDecorationTests extends ShellTestCase {
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
- mRelayoutParams.mCaptionWidthId = Resources.ID_NULL;
windowDecor.relayout(taskInfo);
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
@@ -558,15 +573,17 @@ public class WindowDecorationTests extends ShellTestCase {
final Resources resources = mDecorWindowContext.getResources();
int x = mRelayoutParams.mCaptionX;
int y = mRelayoutParams.mCaptionY;
- int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId);
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ int shadowRadius = loadDimensionPixelSize(resources, mCaptionMenuShadowRadiusId);
+ int cornerRadius = loadDimensionPixelSize(resources, mCaptionMenuCornerRadiusId);
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
addWindow(R.layout.desktop_mode_decor_handle_menu, name,
mMockSurfaceControlAddWindowT,
x - mRelayoutResult.mDecorContainerOffsetX,
y - mRelayoutResult.mDecorContainerOffsetY,
- width, height);
+ width, height, shadowRadius, cornerRadius);
return additionalWindow;
}
}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
index 5ecec4ddd1adfad736e2c4c465497bd84119b0c7..0e67ff58b1cfa41fab814695bd8b7e7bdd866d6f 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
@@ -111,7 +111,7 @@ public final class LowLightDreamManager {
mAmbientLightMode = ambientLightMode;
- mDreamManager.setSystemDreamComponent(mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT
- ? mLowLightDreamComponent : null);
+ boolean shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT;
+ mDreamManager.setSystemDreamComponent(shouldEnterLowLight ? mLowLightDreamComponent : null);
}
}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
new file mode 100644
index 0000000000000000000000000000000000000000..874a2d5af75e2c6dacdeb851404d469e15c411ee
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dream.lowlight;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class that allows listening and running animations before entering or exiting low light.
+ */
+@Singleton
+public class LowLightTransitionCoordinator {
+ /**
+ * Listener that is notified before low light entry.
+ */
+ public interface LowLightEnterListener {
+ /**
+ * Callback that is notified before the device enters low light.
+ *
+ * @return an optional animator that will be waited upon before entering low light.
+ */
+ Animator onBeforeEnterLowLight();
+ }
+
+ /**
+ * Listener that is notified before low light exit.
+ */
+ public interface LowLightExitListener {
+ /**
+ * Callback that is notified before the device exits low light.
+ *
+ * @return an optional animator that will be waited upon before exiting low light.
+ */
+ Animator onBeforeExitLowLight();
+ }
+
+ private LowLightEnterListener mLowLightEnterListener;
+ private LowLightExitListener mLowLightExitListener;
+
+ @Inject
+ public LowLightTransitionCoordinator() {
+ }
+
+ /**
+ * Sets the listener for the low light enter event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ public void setLowLightEnterListener(@Nullable LowLightEnterListener lowLightEnterListener) {
+ mLowLightEnterListener = lowLightEnterListener;
+ }
+
+ /**
+ * Sets the listener for the low light exit event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ public void setLowLightExitListener(@Nullable LowLightExitListener lowLightExitListener) {
+ mLowLightExitListener = lowLightExitListener;
+ }
+
+ /**
+ * Notifies listeners that the device is about to enter or exit low light.
+ *
+ * @param entering true if listeners should be notified before entering low light, false if this
+ * is notifying before exiting.
+ * @param callback callback that will be run after listeners complete.
+ */
+ void notifyBeforeLowLightTransition(boolean entering, Runnable callback) {
+ Animator animator = null;
+
+ if (entering && mLowLightEnterListener != null) {
+ animator = mLowLightEnterListener.onBeforeEnterLowLight();
+ } else if (!entering && mLowLightExitListener != null) {
+ animator = mLowLightExitListener.onBeforeExitLowLight();
+ }
+
+ // If the listener returned an animator to indicate it was running an animation, run the
+ // callback after the animation completes, otherwise call the callback directly.
+ if (animator != null) {
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ callback.run();
+ }
+ });
+ } else {
+ callback.run();
+ }
+ }
+}
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
index 91a170f7ae143e559bd8b5102138b1ab69a72032..f860613fc4690857d5af04ba85aef7f797d2f6a5 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
@@ -46,42 +46,38 @@ public class LowLightDreamManagerTest {
@Mock
private ComponentName mDreamComponent;
+ LowLightDreamManager mLowLightDreamManager;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ mLowLightDreamManager = new LowLightDreamManager(mDreamManager,
+ mDreamComponent);
}
@Test
public void setAmbientLightMode_lowLight_setSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
verify(mDreamManager).setSystemDreamComponent(mDreamComponent);
}
@Test
public void setAmbientLightMode_regularLight_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
verify(mDreamManager).setSystemDreamComponent(null);
}
@Test
public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
// Set to low light first.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
clearInvocations(mDreamManager);
// Return to default unknown mode.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
verify(mDreamManager).setSystemDreamComponent(null);
}
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..81e1e33d622049d4fba9634736ba35f52b5e10d1
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dream.lowlight;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+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;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class LowLightTransitionCoordinatorTest {
+ @Mock
+ private LowLightTransitionCoordinator.LowLightEnterListener mEnterListener;
+
+ @Mock
+ private LowLightTransitionCoordinator.LowLightExitListener mExitListener;
+
+ @Mock
+ private Animator mAnimator;
+
+ @Captor
+ private ArgumentCaptor mAnimatorListenerCaptor;
+
+ @Mock
+ private Runnable mRunnable;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void onEnterCalledOnListeners() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightEnterListener(mEnterListener);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ verify(mEnterListener).onBeforeEnterLowLight();
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void onExitCalledOnListeners() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightExitListener(mExitListener);
+
+ coordinator.notifyBeforeLowLightTransition(false, mRunnable);
+
+ verify(mExitListener).onBeforeExitLowLight();
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void listenerNotCalledAfterRemoval() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightEnterListener(mEnterListener);
+ coordinator.setLowLightEnterListener(null);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ verifyZeroInteractions(mEnterListener);
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void runnableCalledAfterAnimationEnds() {
+ when(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator);
+
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+ coordinator.setLowLightEnterListener(mEnterListener);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
+ verifyZeroInteractions(mRunnable);
+
+ // Runnable is run once the animation ends.
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(null);
+ verify(mRunnable).run();
+ }
+}
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index a285462eef742f36f2e8a700eb889ec00b84dbb9..d101a1bdfb3777b1a4541b883ca3b8fc437c7152 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -39,6 +39,9 @@
#include "VectorDrawable.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/FunctorDrawable.h"
+#ifdef __ANDROID__
+#include "renderthread/CanvasContext.h"
+#endif
namespace android {
namespace uirenderer {
@@ -434,7 +437,19 @@ struct DrawPoints final : Op {
size_t count;
SkPaint paint;
void draw(SkCanvas* c, const SkMatrix&) const {
- c->drawPoints(mode, count, pod(this), paint);
+ if (paint.isAntiAlias()) {
+ c->drawPoints(mode, count, pod