Merge "SpatializerHelper: fix init of compatibility for attached devices" into tm-dev
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cac1374..4681d49 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -116,8 +116,8 @@
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName);
method public long getTotalRam();
- method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidProcessCapabilities(int);
- method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidProcessState(int);
+ method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessCapabilities(int);
+ method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessState(int);
method public void holdLock(android.os.IBinder, int);
method public static boolean isHighEndGfx();
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 458dd5d..abd6017 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3710,10 +3710,16 @@
/**
* Returns the process state of this uid.
*
+ * If the caller does not hold {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+ * permission, they can only query process state of UIDs running in the same user as the caller.
+ *
* @hide
*/
@TestApi
- @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ @RequiresPermission(allOf = {
+ Manifest.permission.PACKAGE_USAGE_STATS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ }, conditional = true)
public int getUidProcessState(int uid) {
try {
return getService().getUidProcessState(uid, mContext.getOpPackageName());
@@ -3725,10 +3731,17 @@
/**
* Returns the process capability of this uid.
*
+ * If the caller does not hold {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+ * permission, they can only query process capabilities of UIDs running in the same user
+ * as the caller.
+ *
* @hide
*/
@TestApi
- @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ @RequiresPermission(allOf = {
+ Manifest.permission.PACKAGE_USAGE_STATS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ }, conditional = true)
public @ProcessCapability int getUidProcessCapabilities(int uid) {
try {
return getService().getUidProcessCapabilities(uid, mContext.getOpPackageName());
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 49a6158..4efe9df 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -100,6 +100,8 @@
String callingPackage);
void unregisterUidObserver(in IUidObserver observer);
boolean isUidActive(int uid, String callingPackage);
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)")
int getUidProcessState(int uid, in String callingPackage);
@UnsupportedAppUsage
int checkPermission(in String permission, int pid, int uid);
@@ -742,6 +744,8 @@
/** Called by PendingIntent.queryIntentComponents() */
ParceledListSlice queryIntentComponentsForIntentSender(in IIntentSender sender, int matchFlags);
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)")
int getUidProcessCapabilities(int uid, in String callingPackage);
/** Blocks until all broadcast queues become idle. */
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index c5ab82d..1148120 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -171,39 +171,53 @@
}
/**
- * @see #computeSha256DigestForLargeFile(String, String)
+ * Creates a fixed size buffer based on whether the device is low ram or not. This is to be used
+ * with the {@link #computeSha256DigestForLargeFile(String, byte[])} and
+ * {@link #computeSha256DigestForLargeFile(String, byte[], String)} methods.
+ * @return a byte array of size {@link #LOW_RAM_BUFFER_SIZE_BYTES} if the device is a low RAM
+ * device, otherwise a byte array of size {@link #HIGH_RAM_BUFFER_SIZE_BYTES}
*/
- public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath) {
- return computeSha256DigestForLargeFile(filePath, null);
+ public static @NonNull byte[] createLargeFileBuffer() {
+ int bufferSize = ActivityManager.isLowRamDeviceStatic()
+ ? LOW_RAM_BUFFER_SIZE_BYTES : HIGH_RAM_BUFFER_SIZE_BYTES;
+ return new byte[bufferSize];
}
/**
- * Computes the SHA256 digest of large files.
- * @param filePath The path to which the file's content is to be hashed.
- * @param separator Separator between each pair of characters, such as colon, or null to omit.
- * @return The digest or null if an error occurs.
+ * @see #computeSha256DigestForLargeFile(String, byte[], String)
*/
public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath,
- @Nullable String separator) {
+ @NonNull byte[] fileBuffer) {
+ return computeSha256DigestForLargeFile(filePath, fileBuffer, null);
+ }
+
+ /**
+ * Computes the SHA256 digest of large files. This is typically useful for large APEXs.
+ * @param filePath The path to which the file's content is to be hashed.
+ * @param fileBuffer A buffer to read file's content into memory. It is strongly recommended to
+ * make use of the {@link #createLargeFileBuffer()} method to create this
+ * buffer.
+ * @param separator Separator between each pair of characters, such as colon, or null to omit.
+ * @return The SHA256 digest or null if an error occurs.
+ */
+ public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath,
+ @NonNull byte[] fileBuffer, @Nullable String separator) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA256");
messageDigest.reset();
} catch (NoSuchAlgorithmException e) {
- // this shouldn't happen!
+ // this really shouldn't happen!
return null;
}
- boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
- int bufferSize = isLowRamDevice ? LOW_RAM_BUFFER_SIZE_BYTES : HIGH_RAM_BUFFER_SIZE_BYTES;
-
File f = new File(filePath);
try {
- DigestInputStream digestStream = new DigestInputStream(new FileInputStream(f),
+ DigestInputStream digestInputStream = new DigestInputStream(new FileInputStream(f),
messageDigest);
- byte[] buffer = new byte[bufferSize];
- while (digestStream.read(buffer) != -1);
+ while (digestInputStream.read(fileBuffer) != -1);
} catch (IOException e) {
+ e.printStackTrace();
return null;
}
diff --git a/core/java/android/view/IPinnedTaskListener.aidl b/core/java/android/view/IPinnedTaskListener.aidl
index 595a846..e4e2d6f 100644
--- a/core/java/android/view/IPinnedTaskListener.aidl
+++ b/core/java/android/view/IPinnedTaskListener.aidl
@@ -44,26 +44,10 @@
void onImeVisibilityChanged(boolean imeVisible, int imeHeight);
/**
- * Called when the set of actions for the current PiP activity changes, or when the listener
- * is first registered to allow the listener to synchronize its state with the controller.
- */
- void onActionsChanged(in ParceledListSlice<RemoteAction> actions, in RemoteAction closeAction);
-
- /**
* Called by the window manager to notify the listener that Activity (was or is in pinned mode)
* is hidden (either stopped or removed). This is generally used as a signal to reset saved
* reentry fraction and size.
* {@param componentName} represents the application component of PiP window.
*/
void onActivityHidden(in ComponentName componentName);
-
- /**
- * Called by the window manager when the aspect ratio is reset.
- */
- void onAspectRatioChanged(float aspectRatio);
-
- /**
- * Called by the window manager when the expanded aspect ratio is reset.
- */
- void onExpandedAspectRatioChanged(float aspectRatio);
}
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index cba0e97..0f27989 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.os.Trace.TRACE_TAG_GRAPHICS;
+
import static java.util.Objects.requireNonNull;
import android.annotation.BinderThread;
@@ -27,6 +29,7 @@
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.CloseGuard;
import android.util.Log;
@@ -48,6 +51,12 @@
IBinder.DeathRecipient {
private static final String TAG = "ScrollCaptureConnection";
+ private static final String TRACE_TRACK = "Scroll Capture";
+ private static final String START_CAPTURE = "startCapture";
+ private static final String REQUEST_IMAGE = "requestImage";
+
+ private static final String END_CAPTURE = "endCapture";
+ private static final String SESSION = "Session";
private final Object mLock = new Object();
private final Rect mScrollBounds;
@@ -62,6 +71,7 @@
private volatile boolean mActive;
private volatile boolean mConnected;
+ private int mTraceId;
/**
* Constructs a ScrollCaptureConnection.
@@ -86,6 +96,9 @@
@Override
public ICancellationSignal startCapture(@NonNull Surface surface,
@NonNull IScrollCaptureCallbacks remote) throws RemoteException {
+ mTraceId = System.identityHashCode(surface);
+ Trace.asyncTraceForTrackBegin(TRACE_TAG_GRAPHICS, TRACE_TRACK, SESSION, mTraceId);
+ Trace.asyncTraceForTrackBegin(TRACE_TAG_GRAPHICS, TRACE_TRACK, START_CAPTURE, mTraceId);
mCloseGuard.open("ScrollCaptureConnection.close");
if (!surface.isValid()) {
@@ -116,11 +129,13 @@
close();
}
mCancellation = null;
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, START_CAPTURE, mTraceId);
}
@BinderThread
@Override
public ICancellationSignal requestImage(Rect requestRect) throws RemoteException {
+ Trace.asyncTraceForTrackBegin(TRACE_TAG_GRAPHICS, TRACE_TRACK, REQUEST_IMAGE, mTraceId);
checkActive();
cancelPendingAction();
ICancellationSignal cancellation = CancellationSignal.createTransport();
@@ -144,11 +159,13 @@
} finally {
mCancellation = null;
}
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, REQUEST_IMAGE, mTraceId);
}
@BinderThread
@Override
public ICancellationSignal endCapture() throws RemoteException {
+ Trace.asyncTraceForTrackBegin(TRACE_TAG_GRAPHICS, TRACE_TRACK, END_CAPTURE, mTraceId);
checkActive();
cancelPendingAction();
ICancellationSignal cancellation = CancellationSignal.createTransport();
@@ -174,17 +191,22 @@
mCancellation = null;
close();
}
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, END_CAPTURE, mTraceId);
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, SESSION, mTraceId);
}
@Override
public void binderDied() {
+ Trace.instantForTrack(TRACE_TAG_GRAPHICS, TRACE_TRACK, "binderDied");
Log.e(TAG, "Controlling process just died.");
close();
+
}
@BinderThread
@Override
public void close() {
+ Trace.instantForTrack(TRACE_TAG_GRAPHICS, TRACE_TRACK, "close");
if (mActive) {
Log.w(TAG, "close(): capture session still active! Ending now.");
cancelPendingAction();
@@ -201,11 +223,13 @@
mRemote = null;
mLocal = null;
mCloseGuard.close();
+ Trace.endSection();
Reference.reachabilityFence(this);
}
private void cancelPendingAction() {
if (mCancellation != null) {
+ Trace.instantForTrack(TRACE_TAG_GRAPHICS, TRACE_TRACK, "CancellationSignal.cancel");
Log.w(TAG, "cancelling pending operation.");
mCancellation.cancel();
mCancellation = null;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 6c4933e..a1ce39e 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3883,8 +3883,8 @@
@Deprecated
public Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) {
checkPreconditions(sc);
- if (colorSpace.getId() == ColorSpace.Named.DCI_P3.ordinal()) {
- setDataSpace(sc, DataSpace.DATASPACE_DCI_P3);
+ if (colorSpace.getId() == ColorSpace.Named.DISPLAY_P3.ordinal()) {
+ setDataSpace(sc, DataSpace.DATASPACE_DISPLAY_P3);
} else {
setDataSpace(sc, DataSpace.DATASPACE_SRGB);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b7a2aa0..6665ab6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -321,6 +321,11 @@
private static final int UNSET_SYNC_ID = -1;
+ /**
+ * Minimum time to wait before reporting changes to keep clear areas.
+ */
+ private static final int KEEP_CLEAR_AREA_REPORT_RATE_MILLIS = 100;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
@@ -796,6 +801,8 @@
new ViewRootRectTracker(v -> v.collectPreferKeepClearRects());
private final ViewRootRectTracker mUnrestrictedKeepClearRectsTracker =
new ViewRootRectTracker(v -> v.collectUnrestrictedPreferKeepClearRects());
+ private List<Rect> mPendingKeepClearAreas;
+ private List<Rect> mPendingUnrestrictedKeepClearAreas;
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
@@ -4824,15 +4831,42 @@
unrestrictedKeepClearRects = Collections.emptyList();
}
- try {
- mWindowSession.reportKeepClearAreasChanged(mWindow, restrictedKeepClearRects,
- unrestrictedKeepClearRects);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (mHandler.hasMessages(MSG_REPORT_KEEP_CLEAR_RECTS)) {
+ // Keep clear areas have been reported recently, wait before reporting new set
+ // of keep clear areas
+ mPendingKeepClearAreas = restrictedKeepClearRects;
+ mPendingUnrestrictedKeepClearAreas = unrestrictedKeepClearRects;
+ } else {
+ mHandler.sendEmptyMessageDelayed(MSG_REPORT_KEEP_CLEAR_RECTS,
+ KEEP_CLEAR_AREA_REPORT_RATE_MILLIS);
+ try {
+ mWindowSession.reportKeepClearAreasChanged(mWindow, restrictedKeepClearRects,
+ unrestrictedKeepClearRects);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
}
+ void reportKeepClearAreasChanged() {
+ final List<Rect> restrictedKeepClearRects = mPendingKeepClearAreas;
+ final List<Rect> unrestrictedKeepClearRects = mPendingUnrestrictedKeepClearAreas;
+ if (restrictedKeepClearRects == null && unrestrictedKeepClearRects == null) {
+ return;
+ }
+
+ mPendingKeepClearAreas = null;
+ mPendingUnrestrictedKeepClearAreas = null;
+
+ try {
+ mWindowSession.reportKeepClearAreasChanged(mWindow, restrictedKeepClearRects,
+ unrestrictedKeepClearRects);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Requests that the root render node is invalidated next time we perform a draw, such that
* {@link WindowCallbacks#onPostDraw} gets called.
@@ -5322,6 +5356,7 @@
private static final int MSG_REQUEST_SCROLL_CAPTURE = 33;
private static final int MSG_WINDOW_TOUCH_MODE_CHANGED = 34;
private static final int MSG_KEEP_CLEAR_RECTS_CHANGED = 35;
+ private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36;
final class ViewRootHandler extends Handler {
@@ -5598,6 +5633,9 @@
case MSG_KEEP_CLEAR_RECTS_CHANGED: {
keepClearRectsChanged();
} break;
+ case MSG_REPORT_KEEP_CLEAR_RECTS: {
+ reportKeepClearAreasChanged();
+ } break;
case MSG_REQUEST_SCROLL_CAPTURE:
handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj);
break;
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 4ad232a..8bd0f7b 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -15,10 +15,8 @@
*/
package com.android.internal.app;
-import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.annotation.StringRes;
import android.app.AppGlobals;
import android.app.admin.DevicePolicyEventLogger;
import android.content.ContentResolver;
@@ -33,7 +31,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
-import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.R;
@@ -348,30 +345,6 @@
ResolverListAdapter activeListAdapter);
/**
- * Updates padding and visibilities as a result of an orientation change.
- * <p>They are not updated automatically, because the view is cached when created.
- * <p>When overridden, make sure to always call the super method.
- */
- void updateAfterConfigChange() {
- for (int i = 0; i < getItemCount(); i++) {
- ViewGroup emptyStateView = getItem(i).getEmptyStateView();
- ImageView icon = emptyStateView.findViewById(R.id.resolver_empty_state_icon);
- updateIconVisibility(icon, emptyStateView);
- }
- }
-
- private void updateIconVisibility(ImageView icon, ViewGroup emptyStateView) {
- if (isSpinnerShowing(emptyStateView)) {
- icon.setVisibility(View.INVISIBLE);
- } else if (mWorkProfileUserHandle != null
- && !getContext().getResources().getBoolean(R.bool.resolver_landscape_phone)) {
- icon.setVisibility(View.VISIBLE);
- } else {
- icon.setVisibility(View.GONE);
- }
- }
-
- /**
* The empty state screens are shown according to their priority:
* <ol>
* <li>(highest priority) cross-profile disabled by policy (handled in
@@ -461,27 +434,13 @@
}
}
- protected void showEmptyState(ResolverListAdapter activeListAdapter,
- @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes) {
- showEmptyState(activeListAdapter, iconRes, titleRes, subtitleRes, /* buttonOnClick */ null);
+ protected void showEmptyState(ResolverListAdapter activeListAdapter, String title,
+ String subtitle) {
+ showEmptyState(activeListAdapter, title, subtitle, /* buttonOnClick */ null);
}
protected void showEmptyState(ResolverListAdapter activeListAdapter,
- @DrawableRes int iconRes, String title, String subtitle) {
- showEmptyState(activeListAdapter, iconRes, title, subtitle, /* buttonOnClick */ null);
- }
-
- protected void showEmptyState(ResolverListAdapter activeListAdapter,
- @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes,
- View.OnClickListener buttonOnClick) {
- String title = titleRes == 0 ? null : mContext.getString(titleRes);
- String subtitle = subtitleRes == 0 ? null : mContext.getString(subtitleRes);
- showEmptyState(activeListAdapter, iconRes, title, subtitle, buttonOnClick);
- }
-
- protected void showEmptyState(ResolverListAdapter activeListAdapter,
- @DrawableRes int iconRes, String title, String subtitle,
- View.OnClickListener buttonOnClick) {
+ String title, String subtitle, View.OnClickListener buttonOnClick) {
ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(activeListAdapter.getUserHandle()));
descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
@@ -507,10 +466,6 @@
button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
button.setOnClickListener(buttonOnClick);
- ImageView icon = emptyStateView.findViewById(R.id.resolver_empty_state_icon);
- icon.setImageResource(iconRes);
- updateIconVisibility(icon, emptyStateView);
-
activeListAdapter.markTabLoaded();
}
@@ -537,7 +492,6 @@
}
private void showSpinner(View emptyStateView) {
- emptyStateView.findViewById(R.id.resolver_empty_state_icon).setVisibility(View.INVISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.VISIBLE);
@@ -545,7 +499,6 @@
}
private void resetViewVisibilitiesForWorkProfileEmptyState(View emptyStateView) {
- emptyStateView.findViewById(R.id.resolver_empty_state_icon).setVisibility(View.VISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.VISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.VISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
@@ -554,7 +507,6 @@
}
private void resetViewVisibilitiesForConsumerUserEmptyState(View emptyStateView) {
- emptyStateView.findViewById(R.id.resolver_empty_state_icon).setVisibility(View.GONE);
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.GONE);
emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.GONE);
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.GONE);
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 9164089..d35d5ca 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -193,7 +193,6 @@
protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter,
View.OnClickListener listener) {
showEmptyState(activeListAdapter,
- R.drawable.ic_work_apps_off,
getWorkAppPausedTitle(),
/* subtitle = */ null,
listener);
@@ -203,12 +202,10 @@
protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
if (mIsSendAction) {
showEmptyState(activeListAdapter,
- R.drawable.ic_sharing_disabled,
getCrossProfileBlockedTitle(),
getCantShareWithWorkMessage());
} else {
showEmptyState(activeListAdapter,
- R.drawable.ic_sharing_disabled,
getCrossProfileBlockedTitle(),
getCantAccessWorkMessage());
}
@@ -218,12 +215,10 @@
protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
if (mIsSendAction) {
showEmptyState(activeListAdapter,
- R.drawable.ic_sharing_disabled,
getCrossProfileBlockedTitle(),
getCantShareWithPersonalMessage());
} else {
showEmptyState(activeListAdapter,
- R.drawable.ic_sharing_disabled,
getCrossProfileBlockedTitle(),
getCantAccessPersonalMessage());
}
@@ -231,19 +226,13 @@
@Override
protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
- getNoPersonalAppsAvailableMessage(),
- /* subtitle= */ null);
+ showEmptyState(listAdapter, getNoPersonalAppsAvailableMessage(), /* subtitle= */ null);
}
@Override
protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
- getNoWorkAppsAvailableMessage(),
- /* subtitle = */ null);
+ showEmptyState(listAdapter, getNoWorkAppsAvailableMessage(), /* subtitle = */ null);
}
private String getWorkAppPausedTitle() {
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 17c2dc0..3d93b2a 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -673,7 +673,6 @@
getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing),
buttonBar.getPaddingRight(),
getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing));
- mMultiProfilePagerAdapter.updateAfterConfigChange();
}
@Override // ResolverListCommunicator
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index f4e568b..0b33501 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -26,7 +26,6 @@
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.content.res.Resources;
import android.os.UserHandle;
import android.view.LayoutInflater;
import android.view.View;
@@ -74,24 +73,6 @@
mShouldShowNoCrossProfileIntentsEmptyState = shouldShowNoCrossProfileIntentsEmptyState;
}
- @Override
- void updateAfterConfigChange() {
- super.updateAfterConfigChange();
- for (ResolverProfileDescriptor descriptor : mItems) {
- View emptyStateCont =
- descriptor.rootView.findViewById(R.id.resolver_empty_state_container);
- Resources resources = getContext().getResources();
- emptyStateCont.setPadding(
- emptyStateCont.getPaddingLeft(),
- resources.getDimensionPixelSize(
- R.dimen.resolver_empty_state_container_padding_top),
- emptyStateCont.getPaddingRight(),
- resources.getDimensionPixelSize(
- R.dimen.resolver_empty_state_container_padding_bottom));
-
- }
- }
-
private ResolverProfileDescriptor createProfileDescriptor(
ResolverListAdapter adapter) {
final LayoutInflater inflater = LayoutInflater.from(getContext());
@@ -203,7 +184,6 @@
protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter,
View.OnClickListener listener) {
showEmptyState(activeListAdapter,
- R.drawable.ic_work_apps_off,
getWorkAppPausedTitle(),
/* subtitle = */ null,
listener);
@@ -212,7 +192,6 @@
@Override
protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
showEmptyState(activeListAdapter,
- R.drawable.ic_sharing_disabled,
getCrossProfileBlockedTitle(),
getCantAccessWorkMessage());
}
@@ -220,7 +199,6 @@
@Override
protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
showEmptyState(activeListAdapter,
- R.drawable.ic_sharing_disabled,
getCrossProfileBlockedTitle(),
getCantAccessPersonalMessage());
}
@@ -228,7 +206,6 @@
@Override
protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
getNoPersonalAppsAvailableMessage(),
/* subtitle = */ null);
}
@@ -236,7 +213,6 @@
@Override
protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
getNoWorkAppsAvailableMessage(),
/* subtitle= */ null);
}
diff --git a/core/res/res/drawable/ic_no_apps.xml b/core/res/res/drawable/ic_no_apps.xml
deleted file mode 100644
index 4d296bd..0000000
--- a/core/res/res/drawable/ic_no_apps.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<vector android:height="32dp" android:viewportHeight="24"
- android:viewportWidth="24" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="@color/resolver_empty_state_icon" android:fillType="evenOdd" android:pathData="M18.8123,20.0145L21.3999,22.6021L22.602,21.4L2.602,1.4L1.3999,2.6021L3.9873,5.1895C3.6248,5.552 3.3998,6.052 3.3998,6.602C3.3998,7.702 4.2998,8.602 5.3998,8.602C5.9498,8.602 6.4498,8.377 6.8123,8.0145L9.9873,11.1895C9.6248,11.552 9.3998,12.052 9.3998,12.602C9.3998,13.702 10.2998,14.602 11.3998,14.602C11.9498,14.602 12.4498,14.377 12.8123,14.0145L15.9873,17.1895C15.6248,17.552 15.3998,18.052 15.3998,18.602C15.3998,19.702 16.2998,20.602 17.3998,20.602C17.9498,20.602 18.4498,20.377 18.8123,20.0145ZM17.3998,8.602C16.2998,8.602 15.3998,7.7021 15.3998,6.602C15.3998,5.502 16.2998,4.602 17.3998,4.602C18.4998,4.602 19.3998,5.502 19.3998,6.602C19.3998,7.7021 18.4998,8.602 17.3998,8.602ZM5.3998,14.6021C6.4998,14.6021 7.3998,13.7021 7.3998,12.6021C7.3998,11.5021 6.4998,10.6021 5.3998,10.6021C4.2998,10.6021 3.3998,11.5021 3.3998,12.6021C3.3998,13.7021 4.2998,14.6021 5.3998,14.6021ZM7.3998,18.6021C7.3998,19.7021 6.4998,20.6021 5.3998,20.6021C4.2998,20.6021 3.3998,19.7021 3.3998,18.6021C3.3998,17.5021 4.2998,16.6021 5.3998,16.6021C6.4998,16.6021 7.3998,17.5021 7.3998,18.6021ZM13.3998,18.6021C13.3998,19.7021 12.4998,20.6021 11.3998,20.6021C10.2998,20.6021 9.3998,19.7021 9.3998,18.6021C9.3998,17.5021 10.2998,16.6021 11.3998,16.6021C12.4998,16.6021 13.3998,17.5021 13.3998,18.6021ZM13.3999,6.602C13.3999,7.547 12.7357,8.3444 11.8511,8.5504L9.4516,6.1509C9.6576,5.2663 10.4549,4.602 11.3999,4.602C12.4999,4.602 13.3999,5.502 13.3999,6.602ZM17.8511,14.5504C18.7357,14.3444 19.3999,13.547 19.3999,12.6021C19.3999,11.5021 18.4999,10.6021 17.3999,10.6021C16.4549,10.6021 15.6576,11.2663 15.4516,12.1509L17.8511,14.5504Z"/>
-</vector>
diff --git a/core/res/res/drawable/ic_sharing_disabled.xml b/core/res/res/drawable/ic_sharing_disabled.xml
deleted file mode 100644
index d488cdb..0000000
--- a/core/res/res/drawable/ic_sharing_disabled.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<vector android:height="32dp" android:viewportHeight="24"
- android:viewportWidth="24" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="@color/resolver_empty_state_icon" android:fillType="evenOdd" android:pathData="M19.7225,20.9245L21.2011,22.4031L22.4032,21.201L2.8022,1.6L1.6001,2.8021L8.1265,9.3284L7.64,9.612C7.1,9.112 6.39,8.802 5.6,8.802C3.94,8.802 2.6,10.142 2.6,11.802C2.6,13.462 3.94,14.802 5.6,14.802C6.39,14.802 7.1,14.492 7.64,13.992L14.69,18.112C14.64,18.332 14.6,18.562 14.6,18.802C14.6,20.462 15.94,21.802 17.6,21.802C18.43,21.802 19.18,21.467 19.7225,20.9245ZM16.8938,18.0958L18.3063,19.5083C18.125,19.6895 17.875,19.802 17.6,19.802C17.05,19.802 16.6,19.352 16.6,18.802C16.6,18.527 16.7125,18.277 16.8938,18.0958ZM15.1871,16.3891L9.3881,10.5901L8.51,11.102C8.56,11.332 8.6,11.562 8.6,11.802C8.6,12.042 8.56,12.272 8.51,12.502L15.1871,16.3891ZM15.56,6.992L12.4382,8.8119L11.1766,7.5503L14.69,5.502C14.64,5.282 14.6,5.042 14.6,4.802C14.6,3.142 15.94,1.802 17.6,1.802C19.26,1.802 20.6,3.142 20.6,4.802C20.6,6.462 19.26,7.802 17.6,7.802C16.81,7.802 16.09,7.492 15.56,6.992ZM18.6,4.802C18.6,4.252 18.15,3.802 17.6,3.802C17.05,3.802 16.6,4.252 16.6,4.802C16.6,5.352 17.05,5.802 17.6,5.802C18.15,5.802 18.6,5.352 18.6,4.802ZM5.6,12.802C5.05,12.802 4.6,12.352 4.6,11.802C4.6,11.252 5.05,10.802 5.6,10.802C6.15,10.802 6.6,11.252 6.6,11.802C6.6,12.352 6.15,12.802 5.6,12.802Z"/>
-</vector>
diff --git a/core/res/res/drawable/ic_work_apps_off.xml b/core/res/res/drawable/ic_work_apps_off.xml
deleted file mode 100644
index f62eb27..0000000
--- a/core/res/res/drawable/ic_work_apps_off.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<vector android:height="32dp" android:viewportHeight="24.0"
- android:viewportWidth="24.0" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="@color/resolver_empty_state_icon" android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v1.17L10.83,8L20,8v9.17l1.98,1.98c0,-0.05 0.02,-0.1 0.02,-0.16L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2zM19,19L8,8 6,6 2.81,2.81 1.39,4.22 3.3,6.13C2.54,6.41 2.01,7.14 2.01,8L2,19c0,1.11 0.89,2 2,2h14.17l1.61,1.61 1.41,-1.41 -0.37,-0.37L19,19zM4,19L4,8h1.17l11,11L4,19z"/>
-</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index 8594c33..d5174f8 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -30,36 +30,30 @@
android:paddingTop="@dimen/resolver_empty_state_container_padding_top"
android:paddingBottom="@dimen/resolver_empty_state_container_padding_bottom"
android:gravity="center_horizontal">
- <ImageView
- android:id="@+id/resolver_empty_state_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_centerHorizontal="true" />
<TextView
android:id="@+id/resolver_empty_state_title"
android:layout_below="@+id/resolver_empty_state_icon"
- android:layout_marginTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@string/config_headlineFontFamilyMedium"
android:textColor="@color/resolver_empty_state_text"
- android:textSize="14sp"
+ android:textSize="18sp"
android:gravity="center_horizontal"
android:layout_centerHorizontal="true" />
<TextView
android:id="@+id/resolver_empty_state_subtitle"
android:layout_below="@+id/resolver_empty_state_title"
- android:layout_marginTop="4dp"
+ android:layout_marginTop="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/resolver_empty_state_text"
- android:textSize="12sp"
+ android:textSize="14sp"
android:gravity="center_horizontal"
android:layout_centerHorizontal="true" />
<Button
android:id="@+id/resolver_empty_state_button"
android:layout_below="@+id/resolver_empty_state_subtitle"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="16dp"
android:text="@string/resolver_switch_on_work"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d9d1a08..b73f96e 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -959,7 +959,7 @@
<dimen name="resolver_max_collapsed_height_with_default_with_tabs">300dp</dimen>
<dimen name="resolver_tab_text_size">14sp</dimen>
<dimen name="resolver_title_padding_bottom">0dp</dimen>
- <dimen name="resolver_empty_state_container_padding_top">8dp</dimen>
+ <dimen name="resolver_empty_state_container_padding_top">48dp</dimen>
<dimen name="resolver_empty_state_container_padding_bottom">8dp</dimen>
<dimen name="chooser_action_button_icon_size">18dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8226ec4..55c1cb9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4303,9 +4303,6 @@
<java-symbol type="string" name="resolver_no_work_apps_available" />
<java-symbol type="string" name="resolver_no_personal_apps_available" />
<java-symbol type="string" name="resolver_switch_on_work" />
- <java-symbol type="drawable" name="ic_work_apps_off" />
- <java-symbol type="drawable" name="ic_sharing_disabled" />
- <java-symbol type="drawable" name="ic_no_apps" />
<java-symbol type="drawable" name="ic_screenshot_edit" />
<java-symbol type="dimen" name="resolver_empty_state_height" />
<java-symbol type="dimen" name="resolver_empty_state_height_with_tabs" />
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index bfa6ce5..74cad1a 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -881,7 +881,7 @@
mAddRipple = false;
if (mRunningAnimations.size() > 0 && !addRipple) {
// update paint when view is invalidated
- getRipplePaint();
+ updateRipplePaint();
}
drawContent(canvas);
drawPatternedBackground(canvas, cx, cy);
@@ -940,7 +940,7 @@
startBackgroundAnimation();
}
if (mBackgroundOpacity == 0) return;
- Paint p = getRipplePaint();
+ Paint p = updateRipplePaint();
float newOpacity = mBackgroundOpacity;
final int origAlpha = p.getAlpha();
final int alpha = Math.min((int) (origAlpha * newOpacity + 0.5f), 255);
@@ -968,7 +968,7 @@
@NonNull
private RippleAnimationSession.AnimationProperties<Float, Paint> createAnimationProperties(
float x, float y, float cx, float cy, float w, float h) {
- Paint p = new Paint(getRipplePaint());
+ Paint p = new Paint(updateRipplePaint());
float radius = getComputedRadius();
RippleAnimationSession.AnimationProperties<Float, Paint> properties;
RippleShader shader = new RippleShader();
@@ -1108,11 +1108,6 @@
drawContent(mMaskCanvas);
}
mMaskCanvas.restoreToCount(saveCount);
- if (mState.mRippleStyle == STYLE_PATTERNED) {
- for (int i = 0; i < mRunningAnimations.size(); i++) {
- mRunningAnimations.get(i).getProperties().getShader().setShader(mMaskShader);
- }
- }
}
private int getMaskType() {
@@ -1169,7 +1164,7 @@
final float y = mHotspotBounds.exactCenterY();
canvas.translate(x, y);
- final Paint p = getRipplePaint();
+ final Paint p = updateRipplePaint();
if (background != null && background.isVisible()) {
background.draw(canvas, p);
@@ -1194,7 +1189,7 @@
}
@UnsupportedAppUsage
- Paint getRipplePaint() {
+ Paint updateRipplePaint() {
if (mRipplePaint == null) {
mRipplePaint = new Paint();
mRipplePaint.setAntiAlias(true);
@@ -1215,6 +1210,12 @@
mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
}
mMaskShader.setLocalMatrix(mMaskMatrix);
+
+ if (mState.mRippleStyle == STYLE_PATTERNED) {
+ for (int i = 0; i < mRunningAnimations.size(); i++) {
+ mRunningAnimations.get(i).getProperties().getShader().setShader(mMaskShader);
+ }
+ }
}
// Grab the color for the current state and cut the alpha channel in
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index 0f37695..1655fba 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -252,7 +252,7 @@
mPropX = CanvasProperty.createFloat(getCurrentX());
mPropY = CanvasProperty.createFloat(getCurrentY());
mPropRadius = CanvasProperty.createFloat(getCurrentRadius());
- final Paint paint = mOwner.getRipplePaint();
+ final Paint paint = mOwner.updateRipplePaint();
mPropPaint = CanvasProperty.createPaint(paint);
final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius);
@@ -290,7 +290,7 @@
opacity.setInterpolator(LINEAR_INTERPOLATOR);
opacity.addListener(mAnimationListener);
opacity.setStartDelay(computeFadeOutDelay());
- opacity.setStartValue(mOwner.getRipplePaint().getAlpha());
+ opacity.setStartValue(mOwner.updateRipplePaint().getAlpha());
mPendingHwAnimators.add(opacity);
invalidateSelf();
}
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 e50b9a1..81caf77 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -16,7 +16,6 @@
package androidx.window.extensions.embedding;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.Activity;
@@ -49,7 +48,8 @@
class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
/** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */
- private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
+ @VisibleForTesting
+ final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
/**
* Mapping from the client assigned unique token to the TaskFragment parent
@@ -120,25 +120,29 @@
* @param secondaryFragmentBounds the initial bounds for the secondary TaskFragment
* @param activityIntent Intent to start the secondary Activity with.
* @param activityOptions ActivityOptions to start the secondary Activity with.
+ * @param windowingMode the windowing mode to set for the TaskFragments.
*/
void startActivityToSide(@NonNull WindowContainerTransaction wct,
@NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds,
@NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken,
@NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent,
- @Nullable Bundle activityOptions, @NonNull SplitRule rule) {
+ @Nullable Bundle activityOptions, @NonNull SplitRule rule,
+ @WindowingMode int windowingMode) {
final IBinder ownerToken = launchingActivity.getActivityToken();
// Create or resize the launching TaskFragment.
if (mFragmentInfos.containsKey(launchingFragmentToken)) {
resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds);
+ wct.setWindowingMode(mFragmentInfos.get(launchingFragmentToken).getToken(),
+ windowingMode);
} else {
createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
- launchingFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, launchingActivity);
+ launchingFragmentBounds, windowingMode, launchingActivity);
}
// Create a TaskFragment for the secondary activity.
createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken,
- secondaryFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, activityIntent,
+ secondaryFragmentBounds, windowingMode, activityIntent,
activityOptions);
// Set adjacent to each other so that the containers below will be invisible.
@@ -153,6 +157,7 @@
void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
resizeTaskFragment(wct, fragmentToken, new Rect());
setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
+ setWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
}
/**
@@ -255,6 +260,15 @@
wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
}
+ private void setWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken,
+ @WindowingMode int windowingMode) {
+ if (!mFragmentInfos.containsKey(fragmentToken)) {
+ throw new IllegalArgumentException(
+ "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
+ }
+ wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
+ }
+
void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
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 2328f76..b370e59 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -257,9 +257,9 @@
if (taskContainer == null) {
return;
}
- final boolean wasInPip = isInPictureInPicture(taskContainer.getConfiguration());
+ final boolean wasInPip = taskContainer.isInPictureInPicture();
final boolean isInPIp = isInPictureInPicture(config);
- taskContainer.setConfiguration(config);
+ taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode());
// We need to check the animation override when enter/exit PIP or has bounds changed.
boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
@@ -278,8 +278,9 @@
* bounds is large enough for at least one split rule.
*/
private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
- if (!taskContainer.isTaskBoundsInitialized()) {
- // We don't know about the Task bounds yet.
+ if (!taskContainer.isTaskBoundsInitialized()
+ || !taskContainer.isWindowingModeInitialized()) {
+ // We don't know about the Task bounds/windowingMode yet.
return;
}
@@ -293,7 +294,7 @@
private boolean supportSplit(@NonNull TaskContainer taskContainer) {
// No split inside PIP.
- if (isInPictureInPicture(taskContainer.getConfiguration())) {
+ if (taskContainer.isInPictureInPicture()) {
return false;
}
// Check if the parent container bounds can support any split rule.
@@ -461,8 +462,12 @@
if (!taskContainer.setTaskBounds(taskBounds)) {
Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
}
- updateAnimationOverride(taskContainer);
}
+ if (!taskContainer.isWindowingModeInitialized()) {
+ taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration()
+ .windowConfiguration.getWindowingMode());
+ }
+ updateAnimationOverride(taskContainer);
return container;
}
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 ee5a322..d842349 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -16,10 +16,11 @@
package androidx.window.extensions.embedding;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.Activity;
import android.app.WindowConfiguration;
+import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
@@ -111,13 +112,16 @@
primaryActivity, primaryRectBounds, null);
// Create new empty task fragment
+ final int taskId = primaryContainer.getTaskId();
final TaskFragmentContainer secondaryContainer = mController.newContainer(
- null /* activity */, primaryActivity, primaryContainer.getTaskId());
+ null /* activity */, primaryActivity, taskId);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
rule, isLtr(primaryActivity, rule));
+ final int windowingMode = mController.getTaskContainer(taskId)
+ .getWindowingModeForSplitTaskFragment(secondaryRectBounds);
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
primaryActivity.getActivityToken(), secondaryRectBounds,
- WINDOWING_MODE_MULTI_WINDOW);
+ windowingMode);
secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
// Set adjacent to each other so that the containers below will be invisible.
@@ -173,7 +177,7 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
createTaskFragment(wct, newContainer.getTaskFragmentToken(),
- launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_MULTI_WINDOW);
+ launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
applyTransaction(wct);
return newContainer;
@@ -189,15 +193,17 @@
@NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) {
TaskFragmentContainer container = mController.getContainerWithActivity(
activity.getActivityToken());
+ final int taskId = container != null ? container.getTaskId() : activity.getTaskId();
if (container == null || container == containerToAvoid) {
- container = mController.newContainer(activity, activity.getTaskId());
-
+ container = mController.newContainer(activity, taskId);
+ final int windowingMode = mController.getTaskContainer(taskId)
+ .getWindowingModeForSplitTaskFragment(bounds);
final TaskFragmentCreationParams fragmentOptions =
createFragmentOptions(
container.getTaskFragmentToken(),
activity.getActivityToken(),
bounds,
- WINDOWING_MODE_MULTI_WINDOW);
+ windowingMode);
wct.createTaskFragment(fragmentOptions);
wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
@@ -206,6 +212,9 @@
container.setLastRequestedBounds(bounds);
} else {
resizeTaskFragmentIfRegistered(wct, container, bounds);
+ final int windowingMode = mController.getTaskContainer(taskId)
+ .getWindowingModeForSplitTaskFragment(bounds);
+ updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
}
return container;
@@ -237,14 +246,17 @@
launchingActivity.getTaskId());
}
+ final int taskId = primaryContainer.getTaskId();
TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */,
- launchingActivity, primaryContainer.getTaskId());
+ launchingActivity, taskId);
+ final int windowingMode = mController.getTaskContainer(taskId)
+ .getWindowingModeForSplitTaskFragment(primaryRectBounds);
final WindowContainerTransaction wct = new WindowContainerTransaction();
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
- activityIntent, activityOptions, rule);
+ activityIntent, activityOptions, rule, windowingMode);
if (isPlaceholder) {
// When placeholder is launched in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
@@ -292,6 +304,12 @@
// When placeholder is shown in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
}
+ final TaskContainer taskContainer = mController.getTaskContainer(
+ updatedContainer.getTaskId());
+ final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+ primaryRectBounds);
+ updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
+ updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
}
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@@ -323,6 +341,15 @@
resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
}
+ private void updateTaskFragmentWindowingModeIfRegistered(
+ @NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container,
+ @WindowingMode int windowingMode) {
+ if (container.getInfo() != null) {
+ wct.setWindowingMode(container.getInfo().getToken(), windowingMode);
+ }
+ }
+
@Override
void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@Nullable Rect bounds) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index be79301..3c0762d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -16,9 +16,14 @@
package androidx.window.extensions.embedding;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.res.Configuration;
+import android.app.WindowConfiguration;
+import android.app.WindowConfiguration.WindowingMode;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
@@ -37,9 +42,9 @@
/** Available window bounds of this Task. */
private final Rect mTaskBounds = new Rect();
- /** Configuration of the Task. */
- @Nullable
- private Configuration mConfiguration;
+ /** Windowing mode of this Task. */
+ @WindowingMode
+ private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
/** Active TaskFragments in this Task. */
final List<TaskFragmentContainer> mContainers = new ArrayList<>();
@@ -81,13 +86,42 @@
return !mTaskBounds.isEmpty();
}
- @Nullable
- Configuration getConfiguration() {
- return mConfiguration;
+ void setWindowingMode(int windowingMode) {
+ mWindowingMode = windowingMode;
}
- void setConfiguration(@Nullable Configuration configuration) {
- mConfiguration = configuration;
+ /** Whether the Task windowing mode has been initialized. */
+ boolean isWindowingModeInitialized() {
+ return mWindowingMode != WINDOWING_MODE_UNDEFINED;
+ }
+
+ /**
+ * Returns the windowing mode for the TaskFragments below this Task, which should be split with
+ * other TaskFragments.
+ *
+ * @param taskFragmentBounds Requested bounds for the TaskFragment. It will be empty when
+ * the pair of TaskFragments are stacked due to the limited space.
+ */
+ @WindowingMode
+ int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) {
+ // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it
+ // will be set to UNDEFINED which will then inherit the Task windowing mode.
+ if (taskFragmentBounds == null || taskFragmentBounds.isEmpty()) {
+ return WINDOWING_MODE_UNDEFINED;
+ }
+ // We use WINDOWING_MODE_MULTI_WINDOW when the Task is fullscreen.
+ // However, when the Task is in other multi windowing mode, such as Freeform, we need to
+ // have the activity windowing mode to match the Task, otherwise things like
+ // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the
+ // Task windowing mode if the Task is in multi window.
+ // TODO we won't need this anymore after we migrate Freeform caption to WM Shell.
+ return WindowConfiguration.inMultiWindowMode(mWindowingMode)
+ ? mWindowingMode
+ : WINDOWING_MODE_MULTI_WINDOW;
+ }
+
+ boolean isInPictureInPicture() {
+ return mWindowingMode == WINDOWING_MODE_PINNED;
}
/** Whether there is any {@link TaskFragmentContainer} below this Task. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index b06ce4c..1f12c448 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -16,15 +16,23 @@
package androidx.window.extensions.embedding;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import android.content.res.Configuration;
+import android.graphics.Point;
import android.platform.test.annotations.Presubmit;
+import android.window.TaskFragmentInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -35,6 +43,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+
/**
* Test class for {@link JetpackTaskFragmentOrganizer}.
*
@@ -48,6 +58,8 @@
private static final int TASK_ID = 10;
@Mock
+ private WindowContainerTransaction mTransaction;
+ @Mock
private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
private JetpackTaskFragmentOrganizer mOrganizer;
@@ -91,4 +103,24 @@
verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
}
+
+ @Test
+ public void testExpandTaskFragment() {
+ final TaskFragmentContainer container = new TaskFragmentContainer(null, TASK_ID);
+ final TaskFragmentInfo info = createMockInfo(container);
+ mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
+ container.setInfo(info);
+
+ mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken());
+
+ verify(mTransaction).setWindowingMode(container.getInfo().getToken(),
+ WINDOWING_MODE_UNDEFINED);
+ }
+
+ private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) {
+ return new TaskFragmentInfo(container.getTaskFragmentToken(),
+ mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
+ false /* isVisible */, new ArrayList<>(), new Point(),
+ false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */);
+ }
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 9fb08df..c7feb7e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -16,6 +16,13 @@
package androidx.window.extensions.embedding;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -64,6 +71,56 @@
}
@Test
+ public void testIsWindowingModeInitialized() {
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+
+ assertFalse(taskContainer.isWindowingModeInitialized());
+
+ taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ assertTrue(taskContainer.isWindowingModeInitialized());
+ }
+
+ @Test
+ public void testGetWindowingModeForSplitTaskFragment() {
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final Rect splitBounds = new Rect(0, 0, 500, 1000);
+
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW,
+ taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+
+ taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW,
+ taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+
+ taskContainer.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ assertEquals(WINDOWING_MODE_FREEFORM,
+ taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+
+ // Empty bounds means the split pair are stacked, so it should be UNDEFINED which will then
+ // inherit the Task windowing mode
+ assertEquals(WINDOWING_MODE_UNDEFINED,
+ taskContainer.getWindowingModeForSplitTaskFragment(new Rect()));
+ }
+
+ @Test
+ public void testIsInPictureInPicture() {
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+
+ assertFalse(taskContainer.isInPictureInPicture());
+
+ taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ assertFalse(taskContainer.isInPictureInPicture());
+
+ taskContainer.setWindowingMode(WINDOWING_MODE_PINNED);
+
+ assertTrue(taskContainer.isInPictureInPicture());
+ }
+
+ @Test
public void testIsEmpty() {
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
diff --git a/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml b/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml
new file mode 100644
index 0000000..7475aba
--- /dev/null
+++ b/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_focused="true">
+ <set>
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="scaleX"
+ android:valueFrom="1.0"
+ android:valueTo="1.1"
+ android:valueType="floatType"/>
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="scaleY"
+ android:valueFrom="1.0"
+ android:valueTo="1.1"
+ android:valueType="floatType"/>
+ </set>
+ </item>
+ <item android:state_focused="false">
+ <set>
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="scaleX"
+ android:valueFrom="1.1"
+ android:valueTo="1.0"
+ android:valueType="floatType"/>
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="scaleY"
+ android:valueFrom="1.1"
+ android:valueTo="1.0"
+ android:valueType="floatType"/>
+ </set>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index dbd5a9b..7a3ee23 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -21,10 +21,13 @@
android:layout_height="match_parent"
android:gravity="center|top">
+ <!-- Matches the PiP app content -->
<View
android:id="@+id/tv_pip"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:alpha="0"
+ android:background="@color/tv_pip_menu_background"
android:layout_marginTop="@dimen/pip_menu_outer_space"
android:layout_marginStart="@dimen/pip_menu_outer_space"
android:layout_marginEnd="@dimen/pip_menu_outer_space"/>
@@ -33,7 +36,6 @@
android:id="@+id/tv_pip_menu_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:gravity="center_horizontal"
android:layout_alignTop="@+id/tv_pip"
android:layout_alignStart="@+id/tv_pip"
android:layout_alignEnd="@+id/tv_pip"
@@ -49,15 +51,12 @@
android:layout_alignStart="@+id/tv_pip"
android:layout_alignEnd="@+id/tv_pip"
android:layout_alignBottom="@+id/tv_pip"
- android:gravity="center_vertical"
android:scrollbars="none">
<LinearLayout
android:id="@+id/tv_pip_menu_action_buttons"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center"
- android:layout_gravity="center"
+ android:layout_height="wrap_content"
android:orientation="horizontal"
android:alpha="0">
@@ -73,11 +72,20 @@
android:text="@string/pip_fullscreen" />
<com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_close_white"
+ android:text="@string/pip_close" />
+
+ <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
android:id="@+id/tv_pip_menu_move_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pip_ic_move_white"
- android:text="@String/pip_move" />
+ android:text="@string/pip_move" />
<com.android.wm.shell.pip.tv.TvPipMenuActionButton
android:id="@+id/tv_pip_menu_expand_button"
@@ -87,15 +95,6 @@
android:visibility="gone"
android:text="@string/pip_collapse" />
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
- android:id="@+id/tv_pip_menu_close_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_close_white"
- android:text="@string/pip_close" />
-
- <!-- More TvPipMenuActionButtons may be added here at runtime. -->
-
<Space
android:layout_width="@dimen/pip_menu_button_wrapper_margin"
android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
index a86a145..db96d8d 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
@@ -20,10 +20,17 @@
android:id="@+id/button"
android:layout_width="@dimen/pip_menu_button_size"
android:layout_height="@dimen/pip_menu_button_size"
- android:layout_margin="@dimen/pip_menu_button_margin"
- android:background="@drawable/tv_pip_button_bg"
+ android:padding="@dimen/pip_menu_button_margin"
+ android:stateListAnimator="@animator/tv_pip_menu_action_button_animator"
android:focusable="true">
+ <View android:id="@+id/background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:duplicateParentState="true"
+ android:background="@drawable/tv_pip_button_bg"/>
+
<ImageView android:id="@+id/icon"
android:layout_width="@dimen/pip_menu_icon_size"
android:layout_height="@dimen/pip_menu_icon_size"
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 776b18e..02e726f 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -16,7 +16,7 @@
-->
<resources>
<!-- The dimensions to user for picture-in-picture action buttons. -->
- <dimen name="pip_menu_button_size">40dp</dimen>
+ <dimen name="pip_menu_button_size">48dp</dimen>
<dimen name="pip_menu_button_radius">20dp</dimen>
<dimen name="pip_menu_icon_size">20dp</dimen>
<dimen name="pip_menu_button_margin">4dp</dimen>
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 1dd5ebc..72c8141 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
@@ -30,6 +30,7 @@
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -41,6 +42,7 @@
import com.android.wm.shell.pip.tv.TvPipController;
import com.android.wm.shell.pip.tv.TvPipMenuController;
import com.android.wm.shell.pip.tv.TvPipNotificationController;
+import com.android.wm.shell.pip.tv.TvPipTaskOrganizer;
import com.android.wm.shell.pip.tv.TvPipTransition;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -67,6 +69,7 @@
PipTransitionController pipTransitionController,
TvPipNotificationController tvPipNotificationController,
TaskStackListenerImpl taskStackListener,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper windowManagerShellWrapper,
@ShellMainThread ShellExecutor mainExecutor,
@@ -82,6 +85,7 @@
pipMediaController,
tvPipNotificationController,
taskStackListener,
+ pipParamsChangedForwarder,
displayController,
windowManagerShellWrapper,
mainExecutor,
@@ -163,15 +167,22 @@
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
PipTransitionController pipTransitionController,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenControllerOptional,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
- return new PipTaskOrganizer(context,
+ return new TvPipTaskOrganizer(context,
syncTransactionQueue, pipTransitionState, tvPipBoundsState, tvPipBoundsAlgorithm,
tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
- pipTransitionController, splitScreenControllerOptional,
+ pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional,
displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
+
+ @WMSingleton
+ @Provides
+ static PipParamsChangedForwarder providePipParamsChangedForwarder() {
+ return new PipParamsChangedForwarder();
+ }
}
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 e43f4fc..7513e51 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
@@ -54,6 +54,7 @@
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -216,14 +217,14 @@
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(context, displayController,
pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
- pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTouchHandler,
- pipTransitionController, windowManagerShellWrapper, taskStackListener,
- oneHandedController, mainExecutor));
+ pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
+ taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor));
}
@WMSingleton
@@ -297,6 +298,7 @@
PipAnimationController pipAnimationController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
PipTransitionController pipTransitionController,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenControllerOptional,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@@ -304,7 +306,7 @@
return new PipTaskOrganizer(context,
syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
- pipTransitionController, splitScreenControllerOptional,
+ pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional,
displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
@@ -391,4 +393,10 @@
rootTaskDisplayAreaOrganizer
);
}
+
+ @WMSingleton
+ @Provides
+ static PipParamsChangedForwarder providePipParamsChangedForwarder() {
+ return new PipParamsChangedForwarder();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
index 87eca74..ce98458 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
@@ -16,9 +16,7 @@
package com.android.wm.shell.pip;
-import android.app.RemoteAction;
import android.content.ComponentName;
-import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
import android.view.IPinnedTaskListener;
import android.view.WindowManagerGlobal;
@@ -72,31 +70,12 @@
}
}
- private void onActionsChanged(ParceledListSlice<RemoteAction> actions,
- RemoteAction closeAction) {
- for (PinnedTaskListener listener : mListeners) {
- listener.onActionsChanged(actions, closeAction);
- }
- }
-
private void onActivityHidden(ComponentName componentName) {
for (PinnedTaskListener listener : mListeners) {
listener.onActivityHidden(componentName);
}
}
- private void onAspectRatioChanged(float aspectRatio) {
- for (PinnedTaskListener listener : mListeners) {
- listener.onAspectRatioChanged(aspectRatio);
- }
- }
-
- private void onExpandedAspectRatioChanged(float aspectRatio) {
- for (PinnedTaskListener listener : mListeners) {
- listener.onExpandedAspectRatioChanged(aspectRatio);
- }
- }
-
@BinderThread
private class PinnedTaskListenerImpl extends IPinnedTaskListener.Stub {
@Override
@@ -114,35 +93,11 @@
}
@Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
- RemoteAction closeAction) {
- mMainExecutor.execute(() -> {
- PinnedStackListenerForwarder.this.onActionsChanged(actions, closeAction);
- });
- }
-
- @Override
public void onActivityHidden(ComponentName componentName) {
mMainExecutor.execute(() -> {
PinnedStackListenerForwarder.this.onActivityHidden(componentName);
});
}
-
- @Override
- public void onAspectRatioChanged(float aspectRatio) {
- mMainExecutor.execute(() -> {
- PinnedStackListenerForwarder.this.onAspectRatioChanged(aspectRatio);
- });
- }
-
- @Override
- public void onExpandedAspectRatioChanged(float aspectRatio) {
- mMainExecutor.execute(() -> {
- PinnedStackListenerForwarder.this.onExpandedAspectRatioChanged(aspectRatio);
- });
- }
-
-
}
/**
@@ -154,13 +109,6 @@
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
- RemoteAction closeAction) {}
-
public void onActivityHidden(ComponentName componentName) {}
-
- public void onAspectRatioChanged(float aspectRatio) {}
-
- public void onExpandedAspectRatioChanged(float aspectRatio) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index f6ff294..16f1d1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -26,12 +26,13 @@
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.RemoteAction;
-import android.content.pm.ParceledListSlice;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import java.util.List;
+
/**
* Interface to allow {@link com.android.wm.shell.pip.PipTaskOrganizer} to call into
* PiP menu when certain events happen (task appear/vanish, PiP move, etc.)
@@ -66,7 +67,7 @@
/**
* Given a set of actions, update the menu.
*/
- void setAppActions(ParceledListSlice<RemoteAction> appActions, RemoteAction closeAction);
+ void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction);
/**
* Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipParamsChangedForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipParamsChangedForwarder.java
new file mode 100644
index 0000000..21ba854
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipParamsChangedForwarder.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip;
+
+import android.app.RemoteAction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Forwards changes to the Picture-in-Picture params to all listeners.
+ */
+public class PipParamsChangedForwarder {
+
+ private final List<PipParamsChangedCallback>
+ mPipParamsChangedListeners = new ArrayList<>();
+
+ /**
+ * Add a listener that implements at least one of the callbacks.
+ */
+ public void addListener(PipParamsChangedCallback listener) {
+ if (mPipParamsChangedListeners.contains(listener)) {
+ return;
+ }
+ mPipParamsChangedListeners.add(listener);
+ }
+
+ /**
+ * Call to notify all listeners of the changed aspect ratio.
+ */
+ public void notifyAspectRatioChanged(float aspectRatio) {
+ for (PipParamsChangedCallback listener : mPipParamsChangedListeners) {
+ listener.onAspectRatioChanged(aspectRatio);
+ }
+ }
+
+ /**
+ * Call to notify all listeners of the changed expanded aspect ratio.
+ */
+ public void notifyExpandedAspectRatioChanged(float aspectRatio) {
+ for (PipParamsChangedCallback listener : mPipParamsChangedListeners) {
+ listener.onExpandedAspectRatioChanged(aspectRatio);
+ }
+ }
+
+ /**
+ * Call to notify all listeners of the changed title.
+ */
+ public void notifyTitleChanged(CharSequence title) {
+ String value = title == null ? null : title.toString();
+ for (PipParamsChangedCallback listener : mPipParamsChangedListeners) {
+ listener.onTitleChanged(value);
+ }
+ }
+
+ /**
+ * Call to notify all listeners of the changed subtitle.
+ */
+ public void notifySubtitleChanged(CharSequence subtitle) {
+ String value = subtitle == null ? null : subtitle.toString();
+ for (PipParamsChangedCallback listener : mPipParamsChangedListeners) {
+ listener.onSubtitleChanged(value);
+ }
+ }
+
+ /**
+ * Call to notify all listeners of the changed app actions or close action.
+ */
+ public void notifyActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) {
+ for (PipParamsChangedCallback listener : mPipParamsChangedListeners) {
+ listener.onActionsChanged(actions, closeAction);
+ }
+ }
+
+ /**
+ * Contains callbacks for PiP params changes. Subclasses can choose which changes they want to
+ * listen to by only overriding those selectively.
+ */
+ public interface PipParamsChangedCallback {
+
+ /**
+ * Called if aspect ratio changed.
+ */
+ default void onAspectRatioChanged(float aspectRatio) {
+ }
+
+ /**
+ * Called if expanded aspect ratio changed.
+ */
+ default void onExpandedAspectRatioChanged(float aspectRatio) {
+ }
+
+ /**
+ * Called if either the actions or the close action changed.
+ */
+ default void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) {
+ }
+
+ /**
+ * Called if the title changed.
+ */
+ default void onTitleChanged(String title) {
+ }
+
+ /**
+ * Called if the subtitle changed.
+ */
+ default void onSubtitleChanged(String subtitle) {
+ }
+ }
+}
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 fbdf6f0..4690e16 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
@@ -62,7 +62,6 @@
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.util.Rational;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -127,6 +126,7 @@
private final @NonNull PipMenuController mPipMenuController;
private final PipAnimationController mPipAnimationController;
private final PipTransitionController mPipTransitionController;
+ protected final PipParamsChangedForwarder mPipParamsChangedForwarder;
private final PipUiEventLogger mPipUiEventLoggerLogger;
private final int mEnterAnimationDuration;
private final int mExitAnimationDuration;
@@ -219,7 +219,7 @@
private long mLastOneShotAlphaAnimationTime;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
- private PictureInPictureParams mPictureInPictureParams;
+ protected PictureInPictureParams mPictureInPictureParams;
private IntConsumer mOnDisplayIdChangeCallback;
/**
* The end transaction of PiP animation for switching between PiP and fullscreen with
@@ -259,6 +259,7 @@
@NonNull PipAnimationController pipAnimationController,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
@NonNull PipTransitionController pipTransitionController,
+ @NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@@ -271,6 +272,7 @@
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
mPipTransitionController = pipTransitionController;
+ mPipParamsChangedForwarder = pipParamsChangedForwarder;
mEnterAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
mExitAnimationDuration = context.getResources()
@@ -559,6 +561,14 @@
mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
setBoundsStateForEntry(mTaskInfo.topActivity, mPictureInPictureParams,
mTaskInfo.topActivityInfo);
+ if (mPictureInPictureParams != null) {
+ mPipParamsChangedForwarder.notifyActionsChanged(mPictureInPictureParams.getActions(),
+ mPictureInPictureParams.getCloseAction());
+ mPipParamsChangedForwarder.notifyTitleChanged(
+ mPictureInPictureParams.getTitle());
+ mPipParamsChangedForwarder.notifySubtitleChanged(
+ mPictureInPictureParams.getSubtitle());
+ }
mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
@@ -819,17 +829,13 @@
mPipBoundsState.setOverrideMinSize(
mPipBoundsAlgorithm.getMinimalSize(info.topActivityInfo));
final PictureInPictureParams newParams = info.pictureInPictureParams;
- if (newParams == null || !applyPictureInPictureParams(newParams)) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Ignored onTaskInfoChanged with PiP param: %s", TAG, newParams);
+
+ // mPictureInPictureParams is only null if there is no PiP
+ if (newParams == null || mPictureInPictureParams == null) {
return;
}
- // Aspect ratio changed, re-calculate bounds if valid.
- final Rect destinationBounds = mPipBoundsAlgorithm.getAdjustedDestinationBounds(
- mPipBoundsState.getBounds(), mPipBoundsState.getAspectRatio());
- Objects.requireNonNull(destinationBounds, "Missing destination bounds");
- scheduleAnimateResizePip(destinationBounds, mEnterAnimationDuration,
- null /* updateBoundsCallback */);
+ applyNewPictureInPictureParams(newParams);
+ mPictureInPictureParams = newParams;
}
@Override
@@ -1076,20 +1082,19 @@
}
/**
- * @return {@code true} if the aspect ratio is changed since no other parameters within
- * {@link PictureInPictureParams} would affect the bounds.
+ * Handles all changes to the PictureInPictureParams.
*/
- private boolean applyPictureInPictureParams(@NonNull PictureInPictureParams params) {
- final Rational currentAspectRatio =
- mPictureInPictureParams != null ? mPictureInPictureParams.getAspectRatio()
- : null;
- final boolean aspectRatioChanged = !Objects.equals(currentAspectRatio,
- params.getAspectRatio());
- mPictureInPictureParams = params;
- if (aspectRatioChanged) {
- mPipBoundsState.setAspectRatio(params.getAspectRatioFloat());
+ protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) {
+ if (PipUtils.aspectRatioChanged(params.getAspectRatioFloat(),
+ mPictureInPictureParams.getAspectRatioFloat())) {
+ mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat());
}
- return aspectRatioChanged;
+ if (PipUtils.remoteActionsChanged(params.getActions(), mPictureInPictureParams.getActions())
+ || !PipUtils.remoteActionsMatch(params.getCloseAction(),
+ mPictureInPictureParams.getCloseAction())) {
+ mPipParamsChangedForwarder.notifyActionsChanged(params.getActions(),
+ params.getCloseAction());
+ }
}
/**
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 d7b69ad..c6cf8b8 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
@@ -21,6 +21,7 @@
import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager.RootTaskInfo;
+import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Context;
import android.os.RemoteException;
@@ -29,10 +30,16 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import java.util.List;
+import java.util.Objects;
+
/** A class that includes convenience methods. */
public class PipUtils {
private static final String TAG = "PipUtils";
+ // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
+ private static final double EPSILON = 1e-7;
+
/**
* @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
* The component name may be null if no such activity exists.
@@ -58,4 +65,45 @@
}
return new Pair<>(null, 0);
}
+
+ /**
+ * @return true if the aspect ratios differ
+ */
+ public static boolean aspectRatioChanged(float aspectRatio1, float aspectRatio2) {
+ return Math.abs(aspectRatio1 - aspectRatio2) > EPSILON;
+ }
+
+ /**
+ * Checks whether title, description and intent match.
+ * Comparing icons would be good, but using equals causes false negatives
+ */
+ public static boolean remoteActionsMatch(RemoteAction action1, RemoteAction action2) {
+ if (action1 == action2) return true;
+ if (action1 == null || action2 == null) return false;
+ return Objects.equals(action1.getTitle(), action2.getTitle())
+ && Objects.equals(action1.getContentDescription(), action2.getContentDescription())
+ && Objects.equals(action1.getActionIntent(), action2.getActionIntent());
+ }
+
+ /**
+ * Returns true if the actions in the lists match each other according to {@link
+ * PipUtils#remoteActionsMatch(RemoteAction, RemoteAction)}, including their position.
+ */
+ public static boolean remoteActionsChanged(List<RemoteAction> list1, List<RemoteAction> list2) {
+ if (list1 == null && list2 == null) {
+ return false;
+ }
+ if (list1 == null || list2 == null) {
+ return true;
+ }
+ if (list1.size() != list2.size()) {
+ return true;
+ }
+ for (int i = 0; i < list1.size(); i++) {
+ if (!remoteActionsMatch(list1.get(i), list2.get(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
}
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 bbec4ec..4942987 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
@@ -22,7 +22,6 @@
import android.app.ActivityManager;
import android.app.RemoteAction;
import android.content.Context;
-import android.content.pm.ParceledListSlice;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -120,9 +119,11 @@
private final SystemWindows mSystemWindows;
private final Optional<SplitScreenController> mSplitScreenController;
private final PipUiEventLogger mPipUiEventLogger;
- private ParceledListSlice<RemoteAction> mAppActions;
+
+ private List<RemoteAction> mAppActions;
private RemoteAction mCloseAction;
- private ParceledListSlice<RemoteAction> mMediaActions;
+ private List<RemoteAction> mMediaActions;
+
private SyncRtSurfaceTransactionApplier mApplier;
private int mMenuState;
@@ -131,7 +132,7 @@
private ActionListener mMediaActionListener = new ActionListener() {
@Override
public void onMediaActionsChanged(List<RemoteAction> mediaActions) {
- mMediaActions = new ParceledListSlice<>(mediaActions);
+ mMediaActions = new ArrayList<>(mediaActions);
updateMenuActions();
}
};
@@ -183,6 +184,9 @@
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
setShellRootAccessibilityWindow();
+
+ // Make sure the initial actions are set
+ updateMenuActions();
}
private void detachPipMenuView() {
@@ -457,7 +461,7 @@
* Sets the menu actions to the actions provided by the current PiP menu.
*/
@Override
- public void setAppActions(ParceledListSlice<RemoteAction> appActions,
+ public void setAppActions(List<RemoteAction> appActions,
RemoteAction closeAction) {
mAppActions = appActions;
mCloseAction = closeAction;
@@ -479,7 +483,7 @@
/**
* @return the best set of actions to show in the PiP menu.
*/
- private ParceledListSlice<RemoteAction> resolveMenuActions() {
+ private List<RemoteAction> resolveMenuActions() {
if (isValidActions(mAppActions)) {
return mAppActions;
}
@@ -491,17 +495,16 @@
*/
private void updateMenuActions() {
if (mPipMenuView != null) {
- final ParceledListSlice<RemoteAction> menuActions = resolveMenuActions();
mPipMenuView.setActions(mPipBoundsState.getBounds(),
- menuActions == null ? null : menuActions.getList(), mCloseAction);
+ resolveMenuActions(), mCloseAction);
}
}
/**
* Returns whether the set of actions are valid.
*/
- private static boolean isValidActions(ParceledListSlice<?> actions) {
- return actions != null && actions.getList().size() > 0;
+ private static boolean isValidActions(List<?> actions) {
+ return actions != null && actions.size() > 0;
}
/**
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 272331b..2e8b5b7 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
@@ -40,7 +40,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
-import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
@@ -80,6 +79,7 @@
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -88,6 +88,8 @@
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
+import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
@@ -113,10 +115,12 @@
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
private TaskStackListenerImpl mTaskStackListener;
+ private PipParamsChangedForwarder mPipParamsChangedForwarder;
private Optional<OneHandedController> mOneHandedController;
protected final PipImpl mImpl;
private final Rect mTmpInsetBounds = new Rect();
+ private final int mEnterAnimationDuration;
private boolean mIsInFixedRotation;
private PipAnimationListener mPinnedStackAnimationRecentsCallback;
@@ -270,12 +274,6 @@
}
@Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
- RemoteAction closeAction) {
- mMenuController.setAppActions(actions, closeAction);
- }
-
- @Override
public void onActivityHidden(ComponentName componentName) {
if (componentName.equals(mPipBoundsState.getLastPipComponentName())) {
// The activity was removed, we don't want to restore to the reentry state
@@ -283,14 +281,6 @@
mPipBoundsState.setLastPipComponentName(null);
}
}
-
- @Override
- public void onAspectRatioChanged(float aspectRatio) {
- // TODO(b/169373982): Remove this callback as it is redundant with PipTaskOrg params
- // change.
- mPipBoundsState.setAspectRatio(aspectRatio);
- mTouchHandler.onAspectRatioChanged();
- }
}
/**
@@ -305,6 +295,7 @@
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -315,8 +306,9 @@
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
- phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, oneHandedController, mainExecutor)
+ phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ oneHandedController, mainExecutor)
.mImpl;
}
@@ -334,6 +326,7 @@
PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor
) {
@@ -360,6 +353,11 @@
mOneHandedController = oneHandedController;
mPipTransitionController = pipTransitionController;
mTaskStackListener = taskStackListener;
+
+ mEnterAnimationDuration = mContext.getResources()
+ .getInteger(R.integer.config_pipEnterAnimationDuration);
+ mPipParamsChangedForwarder = pipParamsChangedForwarder;
+
//TODO: move this to ShellInit when PipController can be injected
mMainExecutor.execute(this::init);
}
@@ -457,6 +455,34 @@
}
});
+ mPipParamsChangedForwarder.addListener(
+ new PipParamsChangedForwarder.PipParamsChangedCallback() {
+ @Override
+ public void onAspectRatioChanged(float ratio) {
+ mPipBoundsState.setAspectRatio(ratio);
+
+ final Rect destinationBounds =
+ mPipBoundsAlgorithm.getAdjustedDestinationBounds(
+ mPipBoundsState.getBounds(),
+ mPipBoundsState.getAspectRatio());
+ Objects.requireNonNull(destinationBounds, "Missing destination bounds");
+ mPipTaskOrganizer.scheduleAnimateResizePip(destinationBounds,
+ mEnterAnimationDuration,
+ null /* updateBoundsCallback */);
+
+ mTouchHandler.onAspectRatioChanged();
+ updateMovementBounds(null /* toBounds */, false /* fromRotation */,
+ false /* fromImeAdjustment */, false /* fromShelfAdjustment */,
+ null /* windowContainerTransaction */);
+ }
+
+ @Override
+ public void onActionsChanged(List<RemoteAction> actions,
+ RemoteAction closeAction) {
+ mMenuController.setAppActions(actions, closeAction);
+ }
+ });
+
mOneHandedController.ifPresent(controller -> {
controller.asOneHanded().registerTransitionCallback(
new OneHandedTransitionCallback() {
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 a3048bd..ca22882 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
@@ -77,6 +77,9 @@
public void setBoundsStateForEntry(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams params, PipBoundsAlgorithm pipBoundsAlgorithm) {
super.setBoundsStateForEntry(componentName, activityInfo, params, pipBoundsAlgorithm);
+ if (params == null) {
+ return;
+ }
setDesiredTvExpandedAspectRatio(params.getExpandedAspectRatioFloat(), true);
}
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 0e1f5a2..8326588 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
@@ -27,7 +27,6 @@
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -48,6 +47,7 @@
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsState;
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.tv.TvPipKeepClearAlgorithm.Placement;
@@ -55,6 +55,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
import java.util.Set;
/**
@@ -66,7 +67,6 @@
private static final String TAG = "TvPipController";
static final boolean DEBUG = false;
- private static final double EPS = 1e-7;
private static final int NONEXISTENT_TASK_ID = -1;
@Retention(RetentionPolicy.SOURCE)
@@ -127,6 +127,7 @@
PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController,
TaskStackListenerImpl taskStackListener,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShell,
ShellExecutor mainExecutor,
@@ -141,6 +142,7 @@
pipMediaController,
pipNotificationController,
taskStackListener,
+ pipParamsChangedForwarder,
displayController,
wmShell,
mainExecutor,
@@ -157,6 +159,7 @@
PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController,
TaskStackListenerImpl taskStackListener,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShell,
ShellExecutor mainExecutor,
@@ -183,6 +186,7 @@
loadConfigurations();
+ registerPipParamsChangedListener(pipParamsChangedForwarder);
registerTaskStackListenerCallback(taskStackListener);
registerWmShellPinnedStackListener(wmShell);
displayController.addDisplayWindowListener(this);
@@ -380,6 +384,7 @@
animationDuration, rect -> {
mTvPipMenuController.updateExpansionState();
});
+ mTvPipMenuController.onPipTransitionStarted(bounds);
}
/**
@@ -540,6 +545,73 @@
});
}
+ private void registerPipParamsChangedListener(PipParamsChangedForwarder provider) {
+ provider.addListener(new PipParamsChangedForwarder.PipParamsChangedCallback() {
+ @Override
+ public void onActionsChanged(List<RemoteAction> actions,
+ RemoteAction closeAction) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onActionsChanged()", TAG);
+
+ mTvPipMenuController.setAppActions(actions, closeAction);
+ mCloseAction = closeAction;
+ }
+
+ @Override
+ public void onAspectRatioChanged(float ratio) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onAspectRatioChanged: %f", TAG, ratio);
+
+ mTvPipBoundsState.setAspectRatio(ratio);
+ if (!mTvPipBoundsState.isTvPipExpanded()) {
+ updatePinnedStackBounds();
+ }
+ }
+
+ @Override
+ public void onExpandedAspectRatioChanged(float ratio) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onExpandedAspectRatioChanged: %f", TAG, ratio);
+
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false);
+ mTvPipMenuController.updateExpansionState();
+
+ // 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
+ // --> update bounds, but don't toggle
+ if (mTvPipBoundsState.isTvPipExpanded() && ratio != 0) {
+ mTvPipBoundsAlgorithm.updateExpandedPipSize();
+ updatePinnedStackBounds();
+ }
+
+ // 2) PiP is expanded, but expanded PiP was disabled
+ // --> collapse PiP
+ if (mTvPipBoundsState.isTvPipExpanded() && ratio == 0) {
+ int saveGravity = mTvPipBoundsAlgorithm
+ .updateGravityOnExpandToggled(mPreviousGravity, false);
+ if (saveGravity != Gravity.NO_GRAVITY) {
+ mPreviousGravity = saveGravity;
+ }
+ mTvPipBoundsState.setTvPipExpanded(false);
+ updatePinnedStackBounds();
+ }
+
+ // 3) PiP not expanded and not manually collapsed and expand was enabled
+ // --> expand to new ratio
+ if (!mTvPipBoundsState.isTvPipExpanded() && ratio != 0
+ && !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
+ mTvPipBoundsAlgorithm.updateExpandedPipSize();
+ int saveGravity = mTvPipBoundsAlgorithm
+ .updateGravityOnExpandToggled(mPreviousGravity, true);
+ if (saveGravity != Gravity.NO_GRAVITY) {
+ mPreviousGravity = saveGravity;
+ }
+ mTvPipBoundsState.setTvPipExpanded(true);
+ updatePinnedStackBounds();
+ }
+ }
+ });
+ }
+
private void registerWmShellPinnedStackListener(WindowManagerShellWrapper wmShell) {
try {
wmShell.addPinnedStackListener(new PinnedStackListenerForwarder.PinnedTaskListener() {
@@ -563,86 +635,6 @@
updatePinnedStackBounds();
}
}
-
- @Override
- public void onAspectRatioChanged(float ratio) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onAspectRatioChanged: %f", TAG, ratio);
- }
-
- boolean ratioChanged = mTvPipBoundsState.getAspectRatio() != ratio;
- mTvPipBoundsState.setAspectRatio(ratio);
-
- if (!mTvPipBoundsState.isTvPipExpanded() && ratioChanged) {
- updatePinnedStackBounds();
- }
- }
-
- @Override
- public void onExpandedAspectRatioChanged(float ratio) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onExpandedAspectRatioChanged: %f", TAG, ratio);
- }
-
- // 0) No update to the ratio --> don't do anything
-
- if (Math.abs(mTvPipBoundsState.getDesiredTvExpandedAspectRatio() - ratio)
- < EPS) {
- return;
- }
-
- mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false);
-
- // 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
- // --> update bounds, but don't toggle
- if (mTvPipBoundsState.isTvPipExpanded() && ratio != 0) {
- mTvPipBoundsAlgorithm.updateExpandedPipSize();
- updatePinnedStackBounds();
- }
-
- // 2) PiP is expanded, but expanded PiP was disabled
- // --> collapse PiP
- if (mTvPipBoundsState.isTvPipExpanded() && ratio == 0) {
- int saveGravity = mTvPipBoundsAlgorithm
- .updateGravityOnExpandToggled(mPreviousGravity, false);
- if (saveGravity != Gravity.NO_GRAVITY) {
- mPreviousGravity = saveGravity;
- }
- mTvPipBoundsState.setTvPipExpanded(false);
- updatePinnedStackBounds();
- }
-
- // 3) PiP not expanded and not manually collapsed and expand was enabled
- // --> expand to new ratio
- if (!mTvPipBoundsState.isTvPipExpanded() && ratio != 0
- && !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
- mTvPipBoundsAlgorithm.updateExpandedPipSize();
- int saveGravity = mTvPipBoundsAlgorithm
- .updateGravityOnExpandToggled(mPreviousGravity, true);
- if (saveGravity != Gravity.NO_GRAVITY) {
- mPreviousGravity = saveGravity;
- }
- mTvPipBoundsState.setTvPipExpanded(true);
- updatePinnedStackBounds();
- }
- }
-
- @Override
- public void onMovementBoundsChanged(boolean fromImeAdjustment) {}
-
- @Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
- RemoteAction closeAction) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onActionsChanged()", TAG);
- }
-
- mTvPipMenuController.setAppActions(actions, closeAction);
- mCloseAction = closeAction;
- }
});
} catch (RemoteException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
index abbc614..a09aab6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
@@ -33,6 +33,7 @@
*/
public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
private final ImageView mIconImageView;
+ private final View mButtonBackgroundView;
private final View mButtonView;
private OnClickListener mOnClickListener;
@@ -57,6 +58,7 @@
mIconImageView = findViewById(R.id.icon);
mButtonView = findViewById(R.id.button);
+ mButtonBackgroundView = findViewById(R.id.background);
final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
final TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr,
@@ -132,9 +134,17 @@
getResources().getColorStateList(
isCustomCloseAction ? R.color.tv_pip_menu_close_icon
: R.color.tv_pip_menu_icon));
- mButtonView.setBackgroundTintList(getResources()
+ mButtonBackgroundView.setBackgroundTintList(getResources()
.getColorStateList(isCustomCloseAction ? R.color.tv_pip_menu_close_icon_bg
: R.color.tv_pip_menu_icon_bg));
}
+ @Override
+ public String toString() {
+ if (mButtonView.getContentDescription() == null) {
+ return TvPipMenuActionButton.class.getSimpleName();
+ }
+ return mButtonView.getContentDescription().toString();
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 2d67254..132c044 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ParceledListSlice;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -169,6 +168,7 @@
mPipMenuView.setListener(this);
setUpViewSurfaceZOrder(mPipMenuView, 1);
addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
+ maybeUpdateMenuViewActions();
}
private void attachPipBackgroundView() {
@@ -199,6 +199,9 @@
void notifyPipAnimating(boolean animating) {
mPipMenuView.setEduTextActive(!animating);
+ if (!animating) {
+ mPipMenuView.onPipTransitionFinished();
+ }
}
void showMovementMenuOnly() {
@@ -235,6 +238,13 @@
} else {
mPipMenuView.showButtonsMenu();
}
+ mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
+ }
+
+ void onPipTransitionStarted(Rect finishBounds) {
+ if (mPipMenuView != null) {
+ mPipMenuView.onPipTransitionStarted(finishBounds);
+ }
}
private void maybeCloseEduText() {
@@ -336,12 +346,12 @@
}
@Override
- public void setAppActions(ParceledListSlice<RemoteAction> actions, RemoteAction closeAction) {
+ public void setAppActions(List<RemoteAction> actions, RemoteAction closeAction) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: setAppActions()", TAG);
}
- updateAdditionalActionsList(mAppActions, actions.getList(), closeAction);
+ updateAdditionalActionsList(mAppActions, actions, closeAction);
}
private void onMediaActionsChanged(List<RemoteAction> actions) {
@@ -559,7 +569,7 @@
menuBounds.height()));
if (mPipMenuView != null) {
- mPipMenuView.updateLayout(destinationBounds);
+ mPipMenuView.updateBounds(destinationBounds);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 5b0db8c..868e456 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -44,8 +44,10 @@
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -53,12 +55,12 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
/**
* A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu
@@ -69,6 +71,8 @@
private static final String TAG = "TvPipMenuView";
private static final boolean DEBUG = TvPipController.DEBUG;
+ private static final int FIRST_CUSTOM_ACTION_POSITION = 3;
+
@Nullable
private Listener mListener;
@@ -90,15 +94,21 @@
private final ImageView mArrowDown;
private final ImageView mArrowLeft;
- private final ViewGroup mScrollView;
- private final ViewGroup mHorizontalScrollView;
+ private final ScrollView mScrollView;
+ private final HorizontalScrollView mHorizontalScrollView;
+ private View mFocusedButton;
private Rect mCurrentPipBounds;
+ private boolean mMoveMenuIsVisible;
+ private boolean mButtonMenuIsVisible;
private final TvPipMenuActionButton mExpandButton;
private final TvPipMenuActionButton mCloseButton;
+ private boolean mSwitchingOrientation;
+
private final int mPipMenuFadeAnimationDuration;
+ private final int mResizeAnimationDuration;
public TvPipMenuView(@NonNull Context context) {
this(context, null);
@@ -146,8 +156,11 @@
mEduTextView = findViewById(R.id.tv_pip_menu_edu_text);
mEduTextContainerView = findViewById(R.id.tv_pip_menu_edu_text_container);
+ mResizeAnimationDuration = context.getResources().getInteger(
+ R.integer.config_pipResizeAnimationDuration);
mPipMenuFadeAnimationDuration = context.getResources()
.getInteger(R.integer.pip_menu_fade_animation_duration);
+
mPipMenuOuterSpace = context.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
mPipMenuBorderWidth = context.getResources()
@@ -203,43 +216,179 @@
heightAnimation.start();
}
- void updateLayout(Rect updatedPipBounds) {
+ void onPipTransitionStarted(Rect finishBounds) {
+ // Fade out content by fading in view on top.
+ if (mCurrentPipBounds != null && finishBounds != null) {
+ boolean ratioChanged = PipUtils.aspectRatioChanged(
+ mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
+ finishBounds.width() / (float) finishBounds.height());
+ if (ratioChanged) {
+ mPipView.animate()
+ .alpha(1f)
+ .setInterpolator(TvPipInterpolators.EXIT)
+ .setDuration(mResizeAnimationDuration / 2)
+ .start();
+ }
+ }
+
+ // Update buttons.
+ final boolean vertical = finishBounds.height() > finishBounds.width();
+ final boolean orientationChanged =
+ vertical != (mActionButtonsContainer.getOrientation() == LinearLayout.VERTICAL);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: update menu layout: %s", TAG, updatedPipBounds.toShortString());
+ "%s: onPipTransitionStarted(), orientation changed %b", TAG, orientationChanged);
+ if (!orientationChanged) {
+ return;
+ }
- boolean previouslyVertical =
- mCurrentPipBounds != null && mCurrentPipBounds.height() > mCurrentPipBounds.width();
- boolean vertical = updatedPipBounds.height() > updatedPipBounds.width();
+ if (mButtonMenuIsVisible) {
+ mSwitchingOrientation = true;
+ mActionButtonsContainer.animate()
+ .alpha(0)
+ .setInterpolator(TvPipInterpolators.EXIT)
+ .setDuration(mResizeAnimationDuration / 2)
+ .withEndAction(() -> {
+ changeButtonScrollOrientation(finishBounds);
+ updateButtonGravity(finishBounds);
+ // Only make buttons visible again in onPipTransitionFinished to keep in
+ // sync with PiP content alpha animation.
+ });
+ } else {
+ changeButtonScrollOrientation(finishBounds);
+ updateButtonGravity(finishBounds);
+ }
+ }
- mCurrentPipBounds = updatedPipBounds;
+ void onPipTransitionFinished() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipTransitionFinished()", TAG);
+
+ // Fade in content by fading out view on top.
+ mPipView.animate()
+ .alpha(0f)
+ .setDuration(mResizeAnimationDuration / 2)
+ .setInterpolator(TvPipInterpolators.ENTER)
+ .start();
+
+ // Update buttons.
+ if (mSwitchingOrientation) {
+ mActionButtonsContainer.animate()
+ .alpha(1)
+ .setInterpolator(TvPipInterpolators.ENTER)
+ .setDuration(mResizeAnimationDuration / 2);
+ } else {
+ refocusPreviousButton();
+ }
+ mSwitchingOrientation = false;
+ }
+
+ /**
+ * Also updates the button gravity.
+ */
+ void updateBounds(Rect updatedBounds) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
+ updatedBounds.height());
+ mCurrentPipBounds = updatedBounds;
+ if (!mSwitchingOrientation) {
+ updateButtonGravity(mCurrentPipBounds);
+ }
updatePipFrameBounds();
+ }
- if (previouslyVertical == vertical) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: no update for menu layout", TAG);
- }
- return;
- } else {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: change menu layout to vertical: %b", TAG, vertical);
+ private void changeButtonScrollOrientation(Rect bounds) {
+ final boolean vertical = bounds.height() > bounds.width();
+
+ final ViewGroup oldScrollView = vertical ? mHorizontalScrollView : mScrollView;
+ final ViewGroup newScrollView = vertical ? mScrollView : mHorizontalScrollView;
+
+ if (oldScrollView.getChildCount() == 1) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: orientation changed", TAG);
+ oldScrollView.removeView(mActionButtonsContainer);
+ oldScrollView.setVisibility(GONE);
+ mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL
+ : LinearLayout.HORIZONTAL);
+ newScrollView.addView(mActionButtonsContainer);
+ newScrollView.setVisibility(VISIBLE);
+ if (mFocusedButton != null) {
+ mFocusedButton.requestFocus();
}
}
+ }
+
+ /**
+ * Change button gravity based on new dimensions
+ */
+ private void updateButtonGravity(Rect bounds) {
+ final boolean vertical = bounds.height() > bounds.width();
+ // Use Math.max since the possible orientation change might not have been applied yet.
+ final int buttonsSize = Math.max(mActionButtonsContainer.getHeight(),
+ mActionButtonsContainer.getWidth());
+
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: buttons container width: %s, height: %s", TAG,
+ mActionButtonsContainer.getWidth(), mActionButtonsContainer.getHeight());
+
+ final boolean buttonsFit =
+ vertical ? buttonsSize < bounds.height()
+ : buttonsSize < bounds.width();
+ final int buttonGravity = buttonsFit ? Gravity.CENTER
+ : (vertical ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL);
+
+ final LayoutParams params = (LayoutParams) mActionButtonsContainer.getLayoutParams();
+ params.gravity = buttonGravity;
+ mActionButtonsContainer.setLayoutParams(params);
+
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: vertical: %b, buttonsFit: %b, gravity: %s", TAG, vertical, buttonsFit,
+ Gravity.toString(buttonGravity));
+ }
+
+ private void refocusPreviousButton() {
+ if (mMoveMenuIsVisible || mCurrentPipBounds == null || mFocusedButton == null) {
+ return;
+ }
+ final boolean vertical = mCurrentPipBounds.height() > mCurrentPipBounds.width();
+
+ if (!mFocusedButton.hasFocus()) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: request focus from: %s", TAG, mFocusedButton);
+ mFocusedButton.requestFocus();
+ } else {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: already focused: %s", TAG, mFocusedButton);
+ }
+
+ // Do we need to scroll?
+ final Rect buttonBounds = new Rect();
+ final Rect scrollBounds = new Rect();
+ if (vertical) {
+ mScrollView.getDrawingRect(scrollBounds);
+ } else {
+ mHorizontalScrollView.getDrawingRect(scrollBounds);
+ }
+ mFocusedButton.getHitRect(buttonBounds);
+
+ if (scrollBounds.contains(buttonBounds)) {
+ // Button is already completely visible, don't scroll
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: not scrolling", TAG);
+ return;
+ }
+
+ // Scrolling so the button is visible to the user.
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: scrolling to focused button", TAG);
if (vertical) {
- mHorizontalScrollView.removeView(mActionButtonsContainer);
- mScrollView.addView(mActionButtonsContainer);
+ mScrollView.smoothScrollTo((int) mFocusedButton.getX(),
+ (int) mFocusedButton.getY());
} else {
- mScrollView.removeView(mActionButtonsContainer);
- mHorizontalScrollView.addView(mActionButtonsContainer);
+ mHorizontalScrollView.smoothScrollTo((int) mFocusedButton.getX(),
+ (int) mFocusedButton.getY());
}
- mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL
- : LinearLayout.HORIZONTAL);
-
- mScrollView.setVisibility(vertical ? VISIBLE : GONE);
- mHorizontalScrollView.setVisibility(vertical ? GONE : VISIBLE);
}
Rect getPipMenuContainerBounds(Rect pipBounds) {
@@ -300,6 +449,8 @@
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
}
+ mButtonMenuIsVisible = false;
+ mMoveMenuIsVisible = true;
showButtonsMenu(false);
showMovementHints(gravity);
setFrameHighlighted(true);
@@ -310,19 +461,34 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showButtonsMenu()", TAG);
}
+
+ mButtonMenuIsVisible = true;
+ mMoveMenuIsVisible = false;
showButtonsMenu(true);
hideMovementHints();
setFrameHighlighted(true);
+
+ // Always focus on the first button when opening the menu, except directly after moving.
+ if (mFocusedButton == null) {
+ // Focus on first button (there is a Space at position 0)
+ mFocusedButton = mActionButtonsContainer.getChildAt(1);
+ // Reset scroll position.
+ mScrollView.scrollTo(0, 0);
+ mHorizontalScrollView.scrollTo(
+ isLayoutRtl() ? mActionButtonsContainer.getWidth() : 0, 0);
+ }
+ refocusPreviousButton();
}
/**
* Hides all menu views, including the menu frame.
*/
void hideAllUserControls() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: hideAllUserControls()", TAG);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideAllUserControls()", TAG);
+ mFocusedButton = null;
+ mButtonMenuIsVisible = false;
+ mMoveMenuIsVisible = false;
showButtonsMenu(false);
hideMovementHints();
setFrameHighlighted(false);
@@ -348,6 +514,13 @@
});
}
+ /**
+ * Button order:
+ * - Fullscreen
+ * - Close
+ * - Custom actions (app or media actions)
+ * - System actions
+ */
void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
Handler mainHandler) {
if (DEBUG) {
@@ -370,13 +543,13 @@
final int actionsNumber = actions.size();
int buttonsNumber = mAdditionalButtons.size();
if (actionsNumber > buttonsNumber) {
- // Add buttons until we have enough to display all of the actions.
+ // Add buttons until we have enough to display all the actions.
while (actionsNumber > buttonsNumber) {
TvPipMenuActionButton button = new TvPipMenuActionButton(mContext);
button.setOnClickListener(this);
mActionButtonsContainer.addView(button,
- mActionButtonsContainer.getChildCount() - 1);
+ FIRST_CUSTOM_ACTION_POSITION + buttonsNumber);
mAdditionalButtons.add(button);
buttonsNumber++;
@@ -398,30 +571,27 @@
final TvPipMenuActionButton button = mAdditionalButtons.get(index);
// Remove action if it matches the custom close action.
- if (actionsMatch(action, closeAction)) {
+ if (PipUtils.remoteActionsMatch(action, closeAction)) {
button.setVisibility(GONE);
continue;
}
setActionForButton(action, button, mainHandler);
}
- }
- /**
- * Checks whether title, description and intent match.
- * Comparing icons would be good, but using equals causes false negatives
- */
- private boolean actionsMatch(RemoteAction action1, RemoteAction action2) {
- if (action1 == action2) return true;
- if (action1 == null || action2 == null) return false;
- return Objects.equals(action1.getTitle(), action2.getTitle())
- && Objects.equals(action1.getContentDescription(), action2.getContentDescription())
- && Objects.equals(action1.getActionIntent(), action2.getActionIntent());
+ if (mCurrentPipBounds != null) {
+ updateButtonGravity(mCurrentPipBounds);
+ refocusPreviousButton();
+ }
}
private void setActionForButton(RemoteAction action, TvPipMenuActionButton button,
Handler mainHandler) {
button.setVisibility(View.VISIBLE); // Ensure the button is visible.
- button.setTextAndDescription(action.getContentDescription());
+ if (action.getContentDescription().length() > 0) {
+ button.setTextAndDescription(action.getContentDescription());
+ } else {
+ button.setTextAndDescription(action.getTitle());
+ }
button.setEnabled(action.isEnabled());
button.setTag(action);
action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
@@ -472,12 +642,11 @@
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: dispatchKeyEvent, action: %d, keycode: %d",
- TAG, event.getAction(), event.getKeyCode());
- }
if (mListener != null && event.getAction() == ACTION_UP) {
+ if (!mMoveMenuIsVisible) {
+ mFocusedButton = mActionButtonsContainer.getFocusedChild();
+ }
+
switch (event.getKeyCode()) {
case KEYCODE_BACK:
mListener.onBackPress();
@@ -539,6 +708,10 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showUserActions: %b", TAG, show);
}
+ if (show) {
+ mActionButtonsContainer.setVisibility(VISIBLE);
+ refocusPreviousButton();
+ }
animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
}
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
new file mode 100644
index 0000000..42fd1aa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -0,0 +1,85 @@
+/*
+ * 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.tv;
+
+import android.app.PictureInPictureParams;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+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.PipUtils;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * TV specific changes to the PipTaskOrganizer.
+ */
+public class TvPipTaskOrganizer extends PipTaskOrganizer {
+
+ public TvPipTaskOrganizer(Context context,
+ @NonNull SyncTransactionQueue syncTransactionQueue,
+ @NonNull PipTransitionState pipTransitionState,
+ @NonNull PipBoundsState pipBoundsState,
+ @NonNull PipBoundsAlgorithm boundsHandler,
+ @NonNull PipMenuController pipMenuController,
+ @NonNull PipAnimationController pipAnimationController,
+ @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
+ @NonNull PipTransitionController pipTransitionController,
+ @NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
+ Optional<SplitScreenController> splitScreenOptional,
+ @NonNull DisplayController displayController,
+ @NonNull PipUiEventLogger pipUiEventLogger,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ ShellExecutor mainExecutor) {
+ super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, boundsHandler,
+ pipMenuController, pipAnimationController, surfaceTransactionHelper,
+ pipTransitionController, pipParamsChangedForwarder, splitScreenOptional,
+ displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ }
+
+ @Override
+ protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) {
+ super.applyNewPictureInPictureParams(params);
+ if (PipUtils.aspectRatioChanged(params.getExpandedAspectRatioFloat(),
+ mPictureInPictureParams.getExpandedAspectRatioFloat())) {
+ mPipParamsChangedForwarder.notifyExpandedAspectRatioChanged(
+ params.getExpandedAspectRatioFloat());
+ }
+ if (!Objects.equals(params.getTitle(), mPictureInPictureParams.getTitle())) {
+ mPipParamsChangedForwarder.notifyTitleChanged(params.getTitle());
+ }
+ if (!Objects.equals(params.getSubtitle(), mPictureInPictureParams.getSubtitle())) {
+ mPipParamsChangedForwarder.notifySubtitleChanged(params.getSubtitle());
+ }
+ }
+}
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 def9ad2..4b85496 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
@@ -81,6 +81,7 @@
private PipBoundsState mPipBoundsState;
private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private PipParamsChangedForwarder mPipParamsChangedForwarder;
private ComponentName mComponent1;
private ComponentName mComponent2;
@@ -97,11 +98,10 @@
mMainExecutor = new TestShellExecutor();
mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext,
mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
- mPipBoundsAlgorithm, mMockPhonePipMenuController,
- mMockPipAnimationController, mMockPipSurfaceTransactionHelper,
- mMockPipTransitionController, mMockOptionalSplitScreen,
- mMockDisplayController, mMockPipUiEventLogger,
- mMockShellTaskOrganizer, mMainExecutor));
+ mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
+ mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
+ mPipParamsChangedForwarder, mMockOptionalSplitScreen, mMockDisplayController,
+ mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor));
mMainExecutor.flushAll();
preparePipTaskOrg();
}
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 deb955b..5368b7d 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
@@ -48,6 +48,7 @@
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -86,6 +87,7 @@
@Mock private TaskStackListenerImpl mMockTaskStackListener;
@Mock private ShellExecutor mMockExecutor;
@Mock private Optional<OneHandedController> mMockOneHandedController;
+ @Mock private PipParamsChangedForwarder mPipParamsChangedForwarder;
@Mock private DisplayLayout mMockDisplayLayout1;
@Mock private DisplayLayout mMockDisplayLayout2;
@@ -102,7 +104,8 @@
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mMockOneHandedController, mMockExecutor);
+ mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController,
+ mMockExecutor);
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
}
@@ -134,7 +137,8 @@
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mMockOneHandedController, mMockExecutor));
+ mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController,
+ mMockExecutor));
}
@Test
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 0e62490..2fe7b16 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -152,8 +152,12 @@
BluetoothGattCharacteristic characteristic,
byte[] value,
int status) {
- Log.d(TAG, "onCharacteristicRead " + status);
+ Log.d(TAG, "onCharacteristicRead status:" + status);
+ StackTraceElement[] elements = Thread.currentThread().getStackTrace();
+ for (StackTraceElement element : elements) {
+ Log.i(TAG, " " + element);
+ }
// switch to receiving notifications after initial characteristic read
mBluetoothGatt.setCharacteristicNotification(characteristic, true);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index aa0ef91..ee7b7d6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -46,7 +46,6 @@
import com.android.settingslib.testutils.shadow.ShadowRouter2Manager;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -259,7 +258,7 @@
final List<MediaRoute2Info> routes = new ArrayList<>();
routes.add(info);
- when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);
+ mShadowRouter2Manager.setTransferableRoutes(routes);
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
@@ -732,8 +731,14 @@
}
@Test
- @Ignore
- public void shouldDisableMediaOutput_infosSizeEqual1_returnsTrue() {
+ public void shouldDisableMediaOutput_infosIsEmpty_returnsTrue() {
+ mShadowRouter2Manager.setTransferableRoutes(new ArrayList<>());
+
+ assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isTrue();
+ }
+
+ @Test
+ public void shouldDisableMediaOutput_infosSizeEqual1_returnsFalse() {
final MediaRoute2Info info = mock(MediaRoute2Info.class);
final List<MediaRoute2Info> infos = new ArrayList<>();
infos.add(info);
@@ -741,7 +746,7 @@
when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
- assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isTrue();
+ assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
}
@Test
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index ccfd3a3..b230438 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -14,140 +14,131 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<FrameLayout
+<com.android.systemui.screenshot.DraggableConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/clipboard_ui"
android:theme="@style/FloatingOverlay"
android:alpha="0"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
- android:id="@+id/background_protection"
- android:layout_height="@dimen/overlay_bg_protection_height"
- android:layout_width="match_parent"
- android:layout_gravity="bottom"
- android:src="@drawable/overlay_actions_background_protection"/>
- <com.android.systemui.screenshot.DraggableConstraintLayout
- android:id="@+id/clipboard_ui"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:id="@+id/actions_container_background"
+ android:visibility="gone"
+ android:layout_height="0dp"
+ android:layout_width="0dp"
+ android:elevation="4dp"
+ android:background="@drawable/action_chip_container_background"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ app:layout_constraintBottom_toBottomOf="@+id/actions_container"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/actions_container"
+ app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
+ <HorizontalScrollView
+ android:id="@+id/actions_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+ android:paddingEnd="@dimen/overlay_action_container_padding_right"
+ android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+ android:elevation="4dp"
+ android:scrollbars="none"
+ android:layout_marginBottom="4dp"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintWidth_percent="1.0"
+ app:layout_constraintWidth_max="wrap"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/preview_border"
+ app:layout_constraintEnd_toEndOf="parent">
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:animateLayoutChanges="true">
+ <include layout="@layout/overlay_action_chip"
+ android:id="@+id/remote_copy_chip"/>
+ <include layout="@layout/overlay_action_chip"
+ android:id="@+id/edit_chip"/>
+ </LinearLayout>
+ </HorizontalScrollView>
+ <View
+ android:id="@+id/preview_border"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="@dimen/overlay_offset_x"
+ android:layout_marginBottom="@dimen/overlay_offset_y"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/actions_container_background"
+ android:elevation="7dp"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
+ android:background="@drawable/overlay_border"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_preview_end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierMargin="@dimen/overlay_border_width"
+ app:barrierDirection="end"
+ app:constraint_referenced_ids="clipboard_preview"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_preview_top"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierDirection="top"
+ app:barrierMargin="@dimen/overlay_border_width_neg"
+ app:constraint_referenced_ids="clipboard_preview"/>
+ <FrameLayout
+ android:id="@+id/clipboard_preview"
+ android:elevation="7dp"
+ android:background="@drawable/overlay_preview_background"
+ android:clipChildren="true"
+ android:clipToOutline="true"
+ android:clipToPadding="true"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_margin="@dimen/overlay_border_width"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ app:layout_constraintBottom_toBottomOf="@id/preview_border"
+ app:layout_constraintStart_toStartOf="@id/preview_border"
+ app:layout_constraintEnd_toEndOf="@id/preview_border"
+ app:layout_constraintTop_toTopOf="@id/preview_border">
+ <TextView android:id="@+id/text_preview"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center|start"
+ android:ellipsize="end"
+ android:autoSizeTextType="uniform"
+ android:autoSizeMinTextSize="10sp"
+ android:autoSizeMaxTextSize="200sp"
+ android:textColor="?attr/overlayButtonTextColor"
+ android:background="?androidprv:attr/colorAccentSecondary"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
<ImageView
- android:id="@+id/actions_container_background"
- android:visibility="gone"
- android:layout_height="0dp"
- android:layout_width="0dp"
- android:elevation="1dp"
- android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- app:layout_constraintBottom_toBottomOf="@+id/actions_container"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
- <HorizontalScrollView
- android:id="@+id/actions_container"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
- android:paddingEnd="@dimen/overlay_action_container_padding_right"
- android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
- android:elevation="1dp"
- android:scrollbars="none"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintWidth_percent="1.0"
- app:layout_constraintWidth_max="wrap"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@+id/preview_border"
- app:layout_constraintEnd_toEndOf="parent">
- <LinearLayout
- android:id="@+id/actions"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:animateLayoutChanges="true">
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/remote_copy_chip"/>
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/edit_chip"/>
- </LinearLayout>
- </HorizontalScrollView>
- <View
- android:id="@+id/preview_border"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_marginStart="@dimen/overlay_offset_x"
- android:layout_marginBottom="@dimen/overlay_offset_y"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toBottomOf="@id/actions_container_background"
- android:elevation="@dimen/overlay_preview_elevation"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
- android:background="@drawable/overlay_border"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_preview_end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierMargin="@dimen/overlay_border_width"
- app:barrierDirection="end"
- app:constraint_referenced_ids="clipboard_preview"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_preview_top"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierDirection="top"
- app:barrierMargin="@dimen/overlay_border_width_neg"
- app:constraint_referenced_ids="clipboard_preview"/>
- <FrameLayout
- android:id="@+id/clipboard_preview"
- android:elevation="@dimen/overlay_preview_elevation"
- android:background="@drawable/overlay_preview_background"
- android:clipChildren="true"
- android:clipToOutline="true"
- android:clipToPadding="true"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_margin="@dimen/overlay_border_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- app:layout_constraintBottom_toBottomOf="@id/preview_border"
- app:layout_constraintStart_toStartOf="@id/preview_border"
- app:layout_constraintEnd_toEndOf="@id/preview_border"
- app:layout_constraintTop_toTopOf="@id/preview_border">
- <TextView android:id="@+id/text_preview"
- android:textFontWeight="500"
- android:padding="8dp"
- android:gravity="center|start"
- android:ellipsize="end"
- android:autoSizeTextType="uniform"
- android:autoSizeMinTextSize="10sp"
- android:autoSizeMaxTextSize="200sp"
- android:textColor="?attr/overlayButtonTextColor"
- android:background="?androidprv:attr/colorAccentSecondary"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_height="@dimen/clipboard_preview_size"/>
- <ImageView
- android:id="@+id/image_preview"
- android:scaleType="fitCenter"
- android:adjustViewBounds="true"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- </FrameLayout>
- <FrameLayout
- android:id="@+id/dismiss_button"
- android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
- android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- android:elevation="@dimen/overlay_dismiss_button_elevation"
- android:visibility="gone"
- android:alpha="0"
- app:layout_constraintStart_toEndOf="@id/clipboard_preview"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview"
- app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
- android:contentDescription="@string/clipboard_dismiss_description">
- <ImageView
- android:id="@+id/dismiss_image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
- android:src="@drawable/overlay_cancel"/>
- </FrameLayout>
- </com.android.systemui.screenshot.DraggableConstraintLayout>
-</FrameLayout>
\ No newline at end of file
+ android:id="@+id/image_preview"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+ <FrameLayout
+ android:id="@+id/dismiss_button"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ android:elevation="10dp"
+ android:visibility="gone"
+ android:alpha="0"
+ app:layout_constraintStart_toEndOf="@id/clipboard_preview"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+ app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+ android:contentDescription="@string/clipboard_dismiss_description">
+ <ImageView
+ android:id="@+id/dismiss_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:src="@drawable/overlay_cancel"/>
+ </FrameLayout>
+</com.android.systemui.screenshot.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml
index 0e9700f..dc86afa 100644
--- a/packages/SystemUI/res/layout/media_output_list_item.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item.xml
@@ -129,6 +129,8 @@
android:gravity="center_vertical">
<CheckBox
android:id="@+id/check_box"
+ android:focusable="false"
+ android:importantForAccessibility="no"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index ef030ba..4d5bf53 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -62,7 +62,6 @@
android:id="@+id/lock_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="@dimen/lock_icon_padding"
android:layout_gravity="center"
android:scaleType="centerCrop"/>
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
index 6d52a30..1838663 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
@@ -35,7 +35,6 @@
android:id="@+id/udfps_aod_fp"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="@dimen/lock_icon_padding"
android:layout_gravity="center"
android:scaleType="centerCrop"
app:lottie_autoPlay="false"
@@ -47,7 +46,6 @@
android:id="@+id/udfps_lockscreen_fp"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="@dimen/lock_icon_padding"
android:layout_gravity="center"
android:scaleType="centerCrop"
app:lottie_autoPlay="false"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 2b1c47f..630fb36 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -123,6 +123,8 @@
public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
// The current app is in immersive mode
public static final int SYSUI_STATE_IMMERSIVE_MODE = 1 << 24;
+ // The voice interaction session window is showing
+ public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -149,7 +151,8 @@
SYSUI_STATE_DEVICE_DOZING,
SYSUI_STATE_BACK_DISABLED,
SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
- SYSUI_STATE_IMMERSIVE_MODE
+ SYSUI_STATE_IMMERSIVE_MODE,
+ SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING
})
public @interface SystemUiStateFlags {}
@@ -184,6 +187,7 @@
str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0
? "bubbles_mange_menu_expanded" : "");
str.add((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0 ? "immersive_mode" : "");
+ str.add((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0 ? "vis_win_showing" : "");
return str.toString();
}
@@ -254,9 +258,11 @@
* disabled.
*/
public static boolean isBackGestureDisabled(int sysuiStateFlags) {
- // Always allow when the bouncer/global actions is showing (even on top of the keyguard)
+ // Always allow when the bouncer/global actions/voice session is showing (even on top of
+ // the keyguard)
if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
- || (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0) {
+ || (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0
+ || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) {
return false;
}
if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index e63e675..e191365 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -56,6 +56,7 @@
@NonNull private final RectF mSensorRect;
@NonNull private PointF mLockIconCenter = new PointF(0f, 0f);
private int mRadius;
+ private int mLockIconPadding;
private ImageView mLockIcon;
private ImageView mBgView;
@@ -125,9 +126,13 @@
* Set the location of the lock icon.
*/
@VisibleForTesting
- public void setCenterLocation(@NonNull PointF center, int radius) {
+ public void setCenterLocation(@NonNull PointF center, int radius, int drawablePadding) {
mLockIconCenter = center;
mRadius = radius;
+ mLockIconPadding = drawablePadding;
+
+ mLockIcon.setPadding(mLockIconPadding, mLockIconPadding, mLockIconPadding,
+ mLockIconPadding);
// mSensorProps coordinates assume portrait mode which is OK b/c the keyguard is always in
// portrait.
@@ -221,6 +226,7 @@
pw.println(" Center in px (x, y)= ("
+ mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
pw.println(" Radius in pixels: " + mRadius);
+ pw.println(" Drawable padding: " + mLockIconPadding);
pw.println(" mIconType=" + typeToString(mIconType));
pw.println(" mAod=" + mAod);
pw.println("Lock Icon View actual measurements:");
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 95cda8b..2b217f0 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -31,8 +31,6 @@
import android.graphics.Rect;
import android.graphics.drawable.AnimatedStateListDrawable;
import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Process;
import android.os.VibrationAttributes;
import android.util.DisplayMetrics;
@@ -125,6 +123,7 @@
private float mHeightPixels;
private float mWidthPixels;
private int mBottomPaddingPx;
+ private int mScaledPaddingPx;
private boolean mShowUnlockIcon;
private boolean mShowLockIcon;
@@ -341,6 +340,10 @@
mHeightPixels = bounds.bottom;
mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom);
+ final int defaultPaddingPx =
+ getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
+ mScaledPaddingPx = (int) (defaultPaddingPx * mAuthController.getScaleFactor());
+
mUnlockedLabel = mView.getContext().getResources().getString(
R.string.accessibility_unlock_button);
mLockedLabel = mView.getContext()
@@ -351,15 +354,13 @@
private void updateLockIconLocation() {
if (mUdfpsSupported) {
- FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
- final SensorLocationInternal location = props.getLocation();
- mView.setCenterLocation(new PointF(location.sensorLocationX, location.sensorLocationY),
- location.sensorRadius);
+ mView.setCenterLocation(mAuthController.getUdfpsLocation(),
+ mAuthController.getUdfpsRadius(), mScaledPaddingPx);
} else {
mView.setCenterLocation(
new PointF(mWidthPixels / 2,
mHeightPixels - mBottomPaddingPx - sLockIconRadiusPx),
- sLockIconRadiusPx);
+ sLockIconRadiusPx, mScaledPaddingPx);
}
mView.getHitRect(mSensorTouchLocation);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d4dad73..b6b9065 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -444,6 +444,27 @@
}
/**
+ * @return the radius of UDFPS on the screen in pixels
+ */
+ public int getUdfpsRadius() {
+ if (mUdfpsController == null || mUdfpsBounds == null) {
+ return -1;
+ }
+ return mUdfpsBounds.height() / 2;
+ }
+
+ /**
+ * @return the scale factor representing the user's current resolution / the stable
+ * (default) resolution
+ */
+ public float getScaleFactor() {
+ if (mUdfpsController == null || mUdfpsController.mOverlayParams == null) {
+ return 1f;
+ }
+ return mUdfpsController.mOverlayParams.getScaleFactor();
+ }
+
+ /**
* @return where the fingerprint sensor exists in pixels in portrait mode. devices without an
* overridden value will use the default value even if they don't have a fingerprint sensor
*/
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 59c658f..49e378e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -22,6 +22,10 @@
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@@ -46,6 +50,17 @@
private static final float STROKE_WIDTH_DP = 12f;
private static final Interpolator DEACCEL = new DecelerateInterpolator();
+ private static final VibrationEffect VIBRATE_EFFECT_ERROR =
+ VibrationEffect.createWaveform(new long[] {0, 5, 55, 60}, -1);
+ private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY);
+
+ private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
+
+ private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
private final float mStrokeWidthPx;
@ColorInt private final int mProgressColor;
@ColorInt private final int mHelpColor;
@@ -54,6 +69,9 @@
@NonNull private final Interpolator mCheckmarkInterpolator;
@NonNull private final Paint mBackgroundPaint;
@NonNull private final Paint mFillPaint;
+ @NonNull private final Vibrator mVibrator;
+ @NonNull private final boolean mIsAccessibilityEnabled;
+ @NonNull private final Context mContext;
private boolean mAfterFirstTouch;
@@ -76,11 +94,12 @@
@NonNull private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
+ mContext = context;
mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
mProgressColor = context.getColor(R.color.udfps_enroll_progress);
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
- final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
- if (!isAccessbilityEnabled) {
+ mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
+ if (!mIsAccessibilityEnabled) {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
} else {
@@ -106,6 +125,8 @@
mFillPaint.setStyle(Paint.Style.STROKE);
mFillPaint.setStrokeCap(Paint.Cap.ROUND);
+ mVibrator = mContext.getSystemService(Vibrator.class);
+
mProgressUpdateListener = animation -> {
mProgress = (float) animation.getAnimatedValue();
invalidateSelf();
@@ -141,14 +162,41 @@
}
private void updateState(int remainingSteps, int totalSteps, boolean showingHelp) {
- updateProgress(remainingSteps, totalSteps);
+ updateProgress(remainingSteps, totalSteps, showingHelp);
updateFillColor(showingHelp);
}
- private void updateProgress(int remainingSteps, int totalSteps) {
+ private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) {
if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
return;
}
+
+ if (mShowingHelp) {
+ if (mVibrator != null && mIsAccessibilityEnabled) {
+ mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
+ VIBRATE_EFFECT_ERROR, getClass().getSimpleName() + "::onEnrollmentHelp",
+ FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
+ }
+ } else {
+ // If the first touch is an error, remainingSteps will be -1 and the callback
+ // doesn't come from onEnrollmentHelp. If we are in the accessibility flow,
+ // we still would like to vibrate.
+ if (mVibrator != null) {
+ if (remainingSteps == -1 && mIsAccessibilityEnabled) {
+ mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
+ VIBRATE_EFFECT_ERROR,
+ getClass().getSimpleName() + "::onFirstTouchError",
+ FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
+ } else if (remainingSteps != -1 && !mIsAccessibilityEnabled) {
+ mVibrator.vibrate(Process.myUid(),
+ mContext.getOpPackageName(),
+ SUCCESS_VIBRATION_EFFECT,
+ getClass().getSimpleName() + "::OnEnrollmentProgress",
+ HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
+ }
+ }
+ }
+
mRemainingSteps = remainingSteps;
mTotalSteps = totalSteps;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 937b813..9139699 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -61,6 +61,7 @@
private AnimatorSet mBackgroundInAnimator = new AnimatorSet();
private int mAlpha; // 0-255
+ private float mScaleFactor = 1;
// AOD anti-burn-in offsets
private final int mMaxBurnInOffsetX;
@@ -172,6 +173,22 @@
mLockScreenFp.invalidate(); // updated with a valueCallback
}
+ void setScaleFactor(float scale) {
+ mScaleFactor = scale;
+ }
+
+ void updatePadding() {
+ if (mLockScreenFp == null || mAodFp == null) {
+ return;
+ }
+
+ final int defaultPaddingPx =
+ getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
+ final int padding = (int) (defaultPaddingPx * mScaleFactor);
+ mLockScreenFp.setPadding(padding, padding, padding, padding);
+ mAodFp.setPadding(padding, padding, padding, padding);
+ }
+
/**
* @param alpha between 0 and 255
*/
@@ -257,6 +274,7 @@
mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp);
mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg);
+ updatePadding();
updateColor();
updateAlpha();
parent.addView(view);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 416b8ee..8b0f36f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -154,6 +154,8 @@
updateGenericBouncerVisibility();
mConfigurationController.addCallback(mConfigurationListener);
getPanelExpansionStateManager().addExpansionListener(mPanelExpansionListener);
+ updateScaleFactor();
+ mView.updatePadding();
updateAlpha();
updatePauseAuth();
@@ -367,6 +369,15 @@
}
}
+ /**
+ * Update the scale factor based on the device's resolution.
+ */
+ private void updateScaleFactor() {
+ if (mUdfpsController != null && mUdfpsController.mOverlayParams != null) {
+ mView.setScaleFactor(mUdfpsController.mOverlayParams.getScaleFactor());
+ }
+ }
+
private final StatusBarStateController.StateListener mStateListener =
new StatusBarStateController.StateListener() {
@Override
@@ -486,6 +497,8 @@
@Override
public void onConfigChanged(Configuration newConfig) {
+ updateScaleFactor();
+ mView.updatePadding();
mView.updateColor();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index eef9d5b..bd67a7f 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -122,7 +122,6 @@
private final AccessibilityManager mAccessibilityManager;
private final TextClassifier mTextClassifier;
- private final FrameLayout mContainer;
private final DraggableConstraintLayout mView;
private final View mClipboardPreview;
private final ImageView mImagePreview;
@@ -177,9 +176,8 @@
setWindowFocusable(false);
- mContainer = (FrameLayout)
+ mView = (DraggableConstraintLayout)
LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null);
- mView = requireNonNull(mContainer.findViewById(R.id.clipboard_ui));
mActionContainerBackground =
requireNonNull(mView.findViewById(R.id.actions_container_background));
mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
@@ -201,13 +199,6 @@
public void onSwipeDismissInitiated(Animator animator) {
mUiEventLogger.log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
mExitAnimator = animator;
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- super.onAnimationStart(animation);
- mContainer.animate().alpha(0).setDuration(animation.getDuration()).start();
- }
- });
}
@Override
@@ -231,7 +222,7 @@
attachWindow();
withWindowAttached(() -> {
- mWindow.setContentView(mContainer);
+ mWindow.setContentView(mView);
updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
mView.requestLayout();
});
@@ -308,7 +299,7 @@
} else {
mRemoteCopyChip.setVisibility(View.GONE);
}
- withWindowAttached(() -> mContainer.post(this::animateIn));
+ withWindowAttached(() -> mView.post(this::animateIn));
mTimeoutHandler.resetTimeout();
}
@@ -508,7 +499,7 @@
rootAnim.setInterpolator(linearInterpolator);
rootAnim.setDuration(66);
rootAnim.addUpdateListener(animation -> {
- mContainer.setAlpha(animation.getAnimatedFraction());
+ mView.setAlpha(animation.getAnimatedFraction());
});
ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
@@ -553,7 +544,7 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mContainer.setAlpha(1);
+ mView.setAlpha(1);
mTimeoutHandler.resetTimeout();
}
});
@@ -568,9 +559,7 @@
ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
rootAnim.setInterpolator(linearInterpolator);
rootAnim.setDuration(100);
- rootAnim.addUpdateListener(animation -> {
- mContainer.setAlpha(1 - animation.getAnimatedFraction());
- });
+ rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
scaleAnim.setInterpolator(scaleInterpolator);
@@ -647,7 +636,7 @@
private void reset() {
mView.setTranslationX(0);
- mContainer.setAlpha(0);
+ mView.setAlpha(0);
mActionContainerBackground.setVisibility(View.GONE);
resetActionChips();
mTimeoutHandler.cancelTimeout();
@@ -706,8 +695,9 @@
}
DisplayCutout cutout = insets.getDisplayCutout();
Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+ Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
if (cutout == null) {
- p.setMargins(0, 0, 0, navBarInsets.bottom);
+ p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
} else {
Insets waterfall = cutout.getWaterfallInsets();
if (orientation == ORIENTATION_PORTRAIT) {
@@ -715,14 +705,16 @@
waterfall.left,
Math.max(cutout.getSafeInsetTop(), waterfall.top),
waterfall.right,
- Math.max(cutout.getSafeInsetBottom(),
- Math.max(navBarInsets.bottom, waterfall.bottom)));
+ Math.max(imeInsets.bottom,
+ Math.max(cutout.getSafeInsetBottom(),
+ Math.max(navBarInsets.bottom, waterfall.bottom))));
} else {
p.setMargins(
- Math.max(cutout.getSafeInsetLeft(), waterfall.left),
+ waterfall.left,
waterfall.top,
- Math.max(cutout.getSafeInsetRight(), waterfall.right),
- Math.max(navBarInsets.bottom, waterfall.bottom));
+ waterfall.right,
+ Math.max(imeInsets.bottom,
+ Math.max(navBarInsets.bottom, waterfall.bottom)));
}
}
mView.setLayoutParams(p);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index b21a886..fb09132 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -38,6 +38,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.shared.recents.utilities.Utilities
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
@@ -819,6 +820,12 @@
return false
}
+ // We don't do the shared element on tablets because they're large and the smartspace has to
+ // fly across large distances, which is distracting.
+ if (Utilities.isTablet(context)) {
+ return false
+ }
+
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 5a32e91..20417af 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -50,7 +50,6 @@
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
@@ -553,30 +552,6 @@
// refreshState is required here to resize the text views (and prevent ellipsis)
mMediaViewController.refreshState();
-
- // Use OnPreDrawListeners to enforce zero alpha on these views for a frame.
- // TransitionLayout insists on resetting the alpha of these views to 1 when onLayout
- // is called which causes the animation to look bad. These suppress that behavior.
- titleText.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- titleText.setAlpha(0);
- titleText.getViewTreeObserver().removeOnPreDrawListener(this);
- return true;
- }
- });
-
- artistText.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- artistText.setAlpha(0);
- artistText.getViewTreeObserver().removeOnPreDrawListener(this);
- return true;
- }
- });
-
return Unit.INSTANCE;
},
() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt
index 9a1a6d3..48f4a16 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt
@@ -18,8 +18,6 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
-import android.animation.AnimatorSet
-import com.android.internal.annotations.VisibleForTesting
/**
* MetadataAnimationHandler controls the current state of the MediaControlPanel's transition motion.
@@ -33,37 +31,37 @@
private val enterAnimator: Animator
) : AnimatorListenerAdapter() {
- private val animator: AnimatorSet
private var postExitUpdate: (() -> Unit)? = null
private var postEnterUpdate: (() -> Unit)? = null
private var targetData: Any? = null
val isRunning: Boolean
- get() = animator.isRunning
+ get() = enterAnimator.isRunning || exitAnimator.isRunning
fun setNext(targetData: Any, postExit: () -> Unit, postEnter: () -> Unit): Boolean {
if (targetData != this.targetData) {
this.targetData = targetData
postExitUpdate = postExit
postEnterUpdate = postEnter
- if (!animator.isRunning) {
- animator.start()
+ if (!isRunning) {
+ exitAnimator.start()
}
return true
}
return false
}
- override fun onAnimationEnd(animator: Animator) {
- if (animator === exitAnimator) {
+ override fun onAnimationEnd(anim: Animator) {
+ if (anim === exitAnimator) {
postExitUpdate?.let { it() }
postExitUpdate = null
+ enterAnimator.start()
}
- if (animator === enterAnimator) {
+ if (anim === enterAnimator) {
// Another new update appeared while entering
if (postExitUpdate != null) {
- this.animator.start()
+ exitAnimator.start()
} else {
postEnterUpdate?.let { it() }
postEnterUpdate = null
@@ -74,13 +72,5 @@
init {
exitAnimator.addListener(this)
enterAnimator.addListener(this)
- animator = buildAnimatorSet(exitAnimator, enterAnimator)
- }
-
- @VisibleForTesting
- protected open fun buildAnimatorSet(exit: Animator, enter: Animator): AnimatorSet {
- val result = AnimatorSet()
- result.playSequentially(exitAnimator, enterAnimator)
- return result
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index ddcba3a..07001ee 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -118,6 +118,7 @@
mCheckBox.setVisibility(View.GONE);
mStatusIcon.setVisibility(View.GONE);
mEndTouchArea.setVisibility(View.GONE);
+ mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
mContainerLayout.setOnClickListener(null);
mContainerLayout.setContentDescription(null);
mTitleText.setTextColor(mController.getColorItemContent());
@@ -170,7 +171,7 @@
setSingleLineLayout(getItemTitle(device), true /* bFocused */,
true /* showSeekBar */,
false /* showProgressBar */, false /* showStatus */);
- setUpContentDescriptionForActiveDevice(device);
+ setUpContentDescriptionForView(mContainerLayout, false, device);
mCheckBox.setOnCheckedChangeListener(null);
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(true);
@@ -181,6 +182,9 @@
mEndTouchArea.setVisibility(View.VISIBLE);
mEndTouchArea.setOnClickListener(null);
mEndTouchArea.setOnClickListener((v) -> mCheckBox.performClick());
+ mEndTouchArea.setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ setUpContentDescriptionForView(mEndTouchArea, true, device);
} else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) {
mStatusIcon.setImageDrawable(
mContext.getDrawable(R.drawable.media_output_status_check));
@@ -190,7 +194,7 @@
true /* showSeekBar */,
false /* showProgressBar */, true /* showStatus */);
initSeekbar(device);
- setUpContentDescriptionForActiveDevice(device);
+ setUpContentDescriptionForView(mContainerLayout, false, device);
mCurrentActivePosition = position;
} else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
mCheckBox.setOnCheckedChangeListener(null);
@@ -258,9 +262,10 @@
}
}
- private void setUpContentDescriptionForActiveDevice(MediaDevice device) {
- mContainerLayout.setClickable(false);
- mContainerLayout.setContentDescription(
+ private void setUpContentDescriptionForView(View view, boolean clickable,
+ MediaDevice device) {
+ view.setClickable(clickable);
+ view.setContentDescription(
mContext.getString(device.getDeviceType()
== MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
? R.string.accessibility_bluetooth_name
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index c723fbb..9768e70 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -41,6 +41,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import android.annotation.FloatRange;
import android.app.ActivityTaskManager;
@@ -77,6 +78,8 @@
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
@@ -551,6 +554,30 @@
private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
= this::cleanupAfterDeath;
+ private final IVoiceInteractionSessionListener mVoiceInteractionSessionListener =
+ new IVoiceInteractionSessionListener.Stub() {
+ @Override
+ public void onVoiceSessionShown() {
+ // Do nothing
+ }
+
+ @Override
+ public void onVoiceSessionHidden() {
+ // Do nothing
+ }
+
+ @Override
+ public void onVoiceSessionWindowVisibilityChanged(boolean visible) {
+ mContext.getMainExecutor().execute(() ->
+ OverviewProxyService.this.onVoiceSessionWindowVisibilityChanged(visible));
+ }
+
+ @Override
+ public void onSetUiHints(Bundle hints) {
+ // Do nothing
+ }
+ };
+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public OverviewProxyService(Context context, CommandQueue commandQueue,
@@ -569,6 +596,7 @@
ScreenLifecycle screenLifecycle,
UiEventLogger uiEventLogger,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
+ AssistUtils assistUtils,
DumpManager dumpManager) {
super(broadcastDispatcher);
mContext = context;
@@ -640,6 +668,9 @@
startConnectionToCurrentUser();
mStartingSurface = startingSurface;
mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
+
+ // Listen for assistant changes
+ assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener);
}
@Override
@@ -648,6 +679,11 @@
internalConnectToCurrentUser();
}
+ public void onVoiceSessionWindowVisibilityChanged(boolean visible) {
+ mSysUiState.setFlag(SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, visible)
+ .commitUpdate(mContext.getDisplayId());
+ }
+
public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
boolean gestureSwipeLeft) {
try {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 4d0feff..be923a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -20,8 +20,6 @@
import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
-import static junit.framework.Assert.assertEquals;
-
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -87,6 +85,7 @@
@TestableLooper.RunWithLooper
public class LockIconViewControllerTest extends SysuiTestCase {
private static final String UNLOCKED_LABEL = "unlocked";
+ private static final int PADDING = 10;
private MockitoSession mStaticMockSession;
@@ -149,6 +148,8 @@
when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds);
when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
+ when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING);
+ when(mAuthController.getScaleFactor()).thenReturn(1f);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
@@ -181,16 +182,32 @@
@Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
- Pair<Integer, PointF> udfps = setupUdfps();
+ Pair<Integer, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
// WHEN lock icon view controller is initialized and attached
mLockIconViewController.init();
captureAttachListener();
mAttachListener.onViewAttachedToWindow(mLockIconView);
- // THEN lock icon view location is updated with the same coordinates as fpProps
- verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(udfps.first));
- assertEquals(udfps.second, mPointCaptor.getValue());
+ // THEN lock icon view location is updated to the udfps location with UDFPS radius
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
+ }
+
+ @Test
+ public void testUpdatePaddingBasedOnResolutionScale() {
+ // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
+ Pair<Integer, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
+ when(mAuthController.getScaleFactor()).thenReturn(5f);
+
+ // WHEN lock icon view controller is initialized and attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lock icon view location is updated with the scaled radius
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING * 5));
}
@Test
@@ -212,8 +229,8 @@
mDelayableExecutor.runAllReady();
// THEN lock icon view location is updated with the same coordinates as fpProps
- verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(udfps.first));
- assertEquals(udfps.second, mPointCaptor.getValue());
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
}
@Test
@@ -400,7 +417,7 @@
List.of(new SensorLocationInternal("" /* displayId */,
(int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
- when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
+ when(mAuthController.getUdfpsRadius()).thenReturn(radius);
return new Pair(radius, udfpsLocation);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 76171b2..94254da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -947,7 +947,7 @@
// Rebinding should not trigger animation
player.bindPlayer(mediaData, PACKAGE)
- verify(mockAnimator, times(1)).start()
+ verify(mockAnimator, times(2)).start()
}
@Test
@@ -969,7 +969,7 @@
// Bind trigges new animation
player.bindPlayer(data1, PACKAGE)
- verify(mockAnimator, times(2)).start()
+ verify(mockAnimator, times(3)).start()
whenever(mockAnimator.isRunning()).thenReturn(true)
// Rebind before animation end binds corrct data
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt
index 52cb902..311aa96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt
@@ -18,7 +18,6 @@
import org.mockito.Mockito.`when` as whenever
import android.animation.Animator
-import android.animation.AnimatorSet
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -44,7 +43,6 @@
private interface Callback : () -> Unit
private lateinit var handler: MetadataAnimationHandler
- @Mock private lateinit var animatorSet: AnimatorSet
@Mock private lateinit var enterAnimator: Animator
@Mock private lateinit var exitAnimator: Animator
@Mock private lateinit var postExitCB: Callback
@@ -54,11 +52,7 @@
@Before
fun setUp() {
- handler = object : MetadataAnimationHandler(exitAnimator, enterAnimator) {
- override fun buildAnimatorSet(exit: Animator, enter: Animator): AnimatorSet {
- return animatorSet
- }
- }
+ handler = MetadataAnimationHandler(exitAnimator, enterAnimator)
}
@After
@@ -69,22 +63,31 @@
val cb = { fail("Unexpected callback") }
handler.setNext("data-1", cb, cb)
- verify(animatorSet).start()
+ verify(exitAnimator).start()
}
@Test
fun executeAnimationEnd_runsCallacks() {
+ // We expect this first call to only start the exit animator
handler.setNext("data-1", postExitCB, postEnterCB)
- verify(animatorSet, times(1)).start()
+ verify(exitAnimator, times(1)).start()
+ verify(enterAnimator, never()).start()
verify(postExitCB, never()).invoke()
+ verify(postEnterCB, never()).invoke()
+ // After the exit animator completes,
+ // the exit cb should run, and enter animation should start
handler.onAnimationEnd(exitAnimator)
- verify(animatorSet, times(1)).start()
+ verify(exitAnimator, times(1)).start()
+ verify(enterAnimator, times(1)).start()
verify(postExitCB, times(1)).invoke()
verify(postEnterCB, never()).invoke()
+ // After the exit animator completes,
+ // the enter cb should run without other state changes
handler.onAnimationEnd(enterAnimator)
- verify(animatorSet, times(1)).start()
+ verify(exitAnimator, times(1)).start()
+ verify(enterAnimator, times(1)).start()
verify(postExitCB, times(1)).invoke()
verify(postEnterCB, times(1)).invoke()
}
@@ -120,38 +123,58 @@
val postExitCB2 = mock(Callback::class.java)
val postEnterCB2 = mock(Callback::class.java)
+ // We expect this first call to only start the exit animator
handler.setNext("data-1", postExitCB, postEnterCB)
- verify(animatorSet, times(1)).start()
+ verify(exitAnimator, times(1)).start()
+ verify(enterAnimator, never()).start()
verify(postExitCB, never()).invoke()
verify(postExitCB2, never()).invoke()
verify(postEnterCB, never()).invoke()
verify(postEnterCB2, never()).invoke()
- whenever(animatorSet.isRunning()).thenReturn(true)
+ // After the exit animator completes,
+ // the exit cb should run, and enter animation should start
+ whenever(exitAnimator.isRunning()).thenReturn(true)
+ whenever(enterAnimator.isRunning()).thenReturn(false)
handler.onAnimationEnd(exitAnimator)
- verify(animatorSet, times(1)).start()
+ verify(exitAnimator, times(1)).start()
+ verify(enterAnimator, times(1)).start()
verify(postExitCB, times(1)).invoke()
verify(postExitCB2, never()).invoke()
verify(postEnterCB, never()).invoke()
verify(postEnterCB2, never()).invoke()
+ // Setting new data before the enter animator completes should not trigger
+ // the exit animator an additional time (since it's already running)
+ whenever(exitAnimator.isRunning()).thenReturn(false)
+ whenever(enterAnimator.isRunning()).thenReturn(true)
handler.setNext("data-2", postExitCB2, postEnterCB2)
+ verify(exitAnimator, times(1)).start()
+
+ // Finishing the enterAnimator should cause the exitAnimator to fire again
+ // since the data change and additional time. No enterCB should be executed.
handler.onAnimationEnd(enterAnimator)
- verify(animatorSet, times(2)).start()
+ verify(exitAnimator, times(2)).start()
+ verify(enterAnimator, times(1)).start()
verify(postExitCB, times(1)).invoke()
verify(postExitCB2, never()).invoke()
verify(postEnterCB, never()).invoke()
verify(postEnterCB2, never()).invoke()
+ // Continuing the sequence, this triggers the enter animator an additional time
handler.onAnimationEnd(exitAnimator)
- verify(animatorSet, times(2)).start()
+ verify(exitAnimator, times(2)).start()
+ verify(enterAnimator, times(2)).start()
verify(postExitCB, times(1)).invoke()
verify(postExitCB2, times(1)).invoke()
verify(postEnterCB, never()).invoke()
verify(postEnterCB2, never()).invoke()
+ // And finally the enter animator completes,
+ // triggering the correct postEnterCallback to fire
handler.onAnimationEnd(enterAnimator)
- verify(animatorSet, times(2)).start()
+ verify(exitAnimator, times(2)).start()
+ verify(enterAnimator, times(2)).start()
verify(postExitCB, times(1)).invoke()
verify(postExitCB2, times(1)).invoke()
verify(postEnterCB, never()).invoke()
@@ -172,6 +195,7 @@
fun enterAnimatorEndsWithoutCallback_noAnimatiorStart() {
handler.onAnimationEnd(enterAnimator)
- verify(animatorSet, never()).start()
+ verify(exitAnimator, never()).start()
+ verify(enterAnimator, never()).start()
}
}
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 877ee82..fa52ac9 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -500,6 +500,7 @@
// ones appearing out of the blue. Thus, we're going to only go through our cache to check
// for changes, rather than freshly invoking `getInstalledPackages()` and
// `getInstalledModules()`
+ byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer();
for (Map.Entry<String, Long> entry : mBinaryLastUpdateTimes.entrySet()) {
String packageName = entry.getKey();
try {
@@ -513,7 +514,7 @@
// compute the digest for the updated package
String sha256digest = PackageUtils.computeSha256DigestForLargeFile(
- packageInfo.applicationInfo.sourceDir);
+ packageInfo.applicationInfo.sourceDir, largeFileBuffer);
if (sha256digest == null) {
Slog.e(TAG, "Failed to compute SHA256sum for file at "
+ packageInfo.applicationInfo.sourceDir);
@@ -545,11 +546,13 @@
// In general, we care about all APEXs, *and* all Modules, which may include some APKs.
// First, we deal with all installed APEXs.
+ byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer();
for (PackageInfo packageInfo : getInstalledApexs()) {
ApplicationInfo appInfo = packageInfo.applicationInfo;
// compute SHA256 for these APEXs
- String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir);
+ String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir,
+ largeFileBuffer);
if (sha256digest == null) {
Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
packageInfo.packageName));
@@ -585,7 +588,7 @@
// compute SHA256 digest for these modules
String sha256digest = PackageUtils.computeSha256DigestForLargeFile(
- appInfo.sourceDir);
+ appInfo.sourceDir, largeFileBuffer);
if (sha256digest == null) {
Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
packageName));
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 91f6eeb..7c6ccc9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7166,6 +7166,11 @@
enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
"getUidProcessState");
}
+ // In case the caller is requesting processState of an app in a different user,
+ // then verify the caller has INTERACT_ACROSS_USERS_FULL permission
+ mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ UserHandle.getUserId(uid), false /* allowAll */, ALLOW_FULL_ONLY,
+ "getUidProcessState", callingPackage); // Ignore return value
synchronized (mProcLock) {
if (mPendingStartActivityUids.isPendingTopUid(uid)) {
@@ -7181,6 +7186,11 @@
enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
"getUidProcessState");
}
+ // In case the caller is requesting processCapabilities of an app in a different user,
+ // then verify the caller has INTERACT_ACROSS_USERS_FULL permission
+ mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ UserHandle.getUserId(uid), false /* allowAll */, ALLOW_FULL_ONLY,
+ "getUidProcessCapabilities", callingPackage); // Ignore return value
synchronized (mProcLock) {
return mProcessList.getUidProcessCapabilityLOSP(uid);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 3e5786e..402491d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -219,6 +219,8 @@
return runStopService(pw);
case "broadcast":
return runSendBroadcast(pw);
+ case "compact":
+ return runCompact(pw);
case "instrument":
getOutPrintWriter().println("Error: must be invoked through 'am instrument'.");
return -1;
@@ -966,6 +968,36 @@
return 0;
}
+ @NeverCompile
+ int runCompact(PrintWriter pw) {
+ String processName = getNextArgRequired();
+ String uid = getNextArgRequired();
+ String op = getNextArgRequired();
+ ProcessRecord app;
+ synchronized (mInternal.mProcLock) {
+ app = mInternal.getProcessRecordLocked(processName, Integer.parseInt(uid));
+ }
+ pw.println("Process record found pid: " + app.mPid);
+ if (op.equals("full")) {
+ pw.println("Executing full compaction for " + app.mPid);
+ synchronized (mInternal.mProcLock) {
+ mInternal.mOomAdjuster.mCachedAppOptimizer.compactAppFull(app, true);
+ }
+ pw.println("Finished full compaction for " + app.mPid);
+ } else if (op.equals("some")) {
+ pw.println("Executing some compaction for " + app.mPid);
+ synchronized (mInternal.mProcLock) {
+ mInternal.mOomAdjuster.mCachedAppOptimizer.compactAppSome(app, true);
+ }
+ pw.println("Finished some compaction for " + app.mPid);
+ } else {
+ getErrPrintWriter().println("Error: unknown compact command '" + op + "'");
+ return -1;
+ }
+
+ return 0;
+ }
+
int runDumpHeap(PrintWriter pw) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
boolean managed = true;
@@ -3446,6 +3478,10 @@
pw.println(" --allow-background-activity-starts: The receiver may start activities");
pw.println(" even if in the background.");
pw.println(" --async: Send without waiting for the completion of the receiver.");
+ pw.println(" compact <process_name> <Package UID> [some|full]");
+ pw.println(" Force process compaction.");
+ pw.println(" some: execute file compaction.");
+ pw.println(" full: execute anon + file compaction.");
pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]");
pw.println(" [--user <USER_ID> | current]");
pw.println(" [--no-hidden-api-checks [--no-test-api-access]]");
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index ff569a6..a172018 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -88,6 +88,12 @@
@VisibleForTesting static final String KEY_FREEZER_DEBOUNCE_TIMEOUT =
"freeze_debounce_timeout";
+ // RSS Indices
+ private static final int RSS_TOTAL_INDEX = 0;
+ private static final int RSS_FILE_INDEX = 1;
+ private static final int RSS_ANON_INDEX = 2;
+ private static final int RSS_SWAP_INDEX = 3;
+
// Phenotype sends int configurations and we map them to the strings we'll use on device,
// preventing a weird string value entering the kernel.
private static final int COMPACT_ACTION_NONE = 0;
@@ -101,11 +107,13 @@
private static final int COMPACT_ACTION_FILE_FLAG = 1;
private static final int COMPACT_ACTION_ANON_FLAG = 2;
+ private static final String ATRACE_COMPACTION_TRACK = "Compaction";
+
// Defaults for phenotype flags.
@VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
@VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true;
- @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE;
@VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL;
+ @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE;
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
@@ -152,6 +160,11 @@
static final int SET_FROZEN_PROCESS_MSG = 3;
static final int REPORT_UNFREEZE_MSG = 4;
+ // When free swap falls below this percentage threshold any full (file + anon)
+ // compactions will be downgraded to file only compactions to reduce pressure
+ // on swap resources as file.
+ static final double COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD = 0.2;
+
static final int DO_FREEZE = 1;
static final int REPORT_UNFREEZE = 2;
@@ -440,6 +453,16 @@
pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime()
+ ": " + app.getPid() + " " + app.processName);
}
+
+ if (!mPendingCompactionProcesses.isEmpty()) {
+ pw.println(" Pending compactions:");
+ size = mPendingCompactionProcesses.size();
+ for (int i = 0; i < size; i++) {
+ ProcessRecord app = mPendingCompactionProcesses.get(i);
+ pw.println(" pid: " + app.getPid() + ". name: " + app.processName
+ + ". hasPendingCompact: " + app.mOptRecord.hasPendingCompact());
+ }
+ }
}
if (DEBUG_COMPACTION) {
for (Map.Entry<Integer, LastCompactionStats> entry
@@ -454,10 +477,16 @@
}
@GuardedBy("mProcLock")
- void compactAppSome(ProcessRecord app) {
+ void compactAppSome(ProcessRecord app, boolean force) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME);
- if (!app.mOptRecord.hasPendingCompact()) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM, " compactAppSome requested for " + app.processName + " force: " + force);
+ }
+ if (force || !app.mOptRecord.hasPendingCompact()) {
+ Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
+ "compactAppSome " + app.processName != null ? app.processName : "");
app.mOptRecord.setHasPendingCompact(true);
+ app.mOptRecord.setForceCompact(force);
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
mCompactionHandler.obtainMessage(
@@ -466,19 +495,31 @@
}
@GuardedBy("mProcLock")
- void compactAppFull(ProcessRecord app) {
- // Apply OOM adj score throttle for Full App Compaction.
- if ((app.mState.getSetAdj() < mCompactThrottleMinOomAdj
- || app.mState.getSetAdj() > mCompactThrottleMaxOomAdj)
+ void compactAppFull(ProcessRecord app, boolean force) {
+ boolean oomAdjEnteredCached = (app.mState.getSetAdj() < mCompactThrottleMinOomAdj
+ || app.mState.getSetAdj() > mCompactThrottleMaxOomAdj)
&& app.mState.getCurAdj() >= mCompactThrottleMinOomAdj
- && app.mState.getCurAdj() <= mCompactThrottleMaxOomAdj) {
+ && app.mState.getCurAdj() <= mCompactThrottleMaxOomAdj;
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ " compactAppFull requested for " + app.processName + " force: " + force
+ + " oomAdjEnteredCached: " + oomAdjEnteredCached);
+ }
+ // Apply OOM adj score throttle for Full App Compaction.
+ if (force || oomAdjEnteredCached) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL);
if (!app.mOptRecord.hasPendingCompact()) {
+ Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
+ "compactAppFull " + app.processName != null ? app.processName : "");
app.mOptRecord.setHasPendingCompact(true);
+ app.mOptRecord.setForceCompact(force);
mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+ } else if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ " compactAppFull Skipped for " + app.processName
+ + " since it has a pending compact");
}
} else {
if (DEBUG_COMPACTION) {
@@ -493,6 +534,8 @@
void compactAppPersistent(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT);
if (!app.mOptRecord.hasPendingCompact()) {
+ Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
+ "compactAppPersistent " + app.processName != null ? app.processName : "");
app.mOptRecord.setHasPendingCompact(true);
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
@@ -511,6 +554,8 @@
void compactAppBfgs(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS);
if (!app.mOptRecord.hasPendingCompact()) {
+ Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
+ "compactAppBfgs " + app.processName != null ? app.processName : "");
app.mOptRecord.setHasPendingCompact(true);
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
@@ -527,6 +572,8 @@
void compactAllSystem() {
if (useCompaction()) {
+ Trace.instantForTrack(
+ Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, "compactAllSystem");
mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
COMPACT_SYSTEM_MSG));
}
@@ -545,6 +592,11 @@
static private native void cancelCompaction();
/**
+ * Retrieves the free swap percentage.
+ */
+ static private native double getFreeSwapPercent();
+
+ /**
* Reads the flag value from DeviceConfig to determine whether app compaction
* should be enabled, and starts the freeze/compaction thread if needed.
*/
@@ -1094,13 +1146,26 @@
if(wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
// Remove any pending compaction we may have scheduled to happen while screen was off
Slog.e(TAG_AM, "Cancel pending or running compactions as system is awake");
- synchronized(mProcLock) {
- mPendingCompactionProcesses.clear();
- }
- cancelCompaction();
+ cancelAllCompactions();
}
}
+ void cancelAllCompactions() {
+ synchronized (mProcLock) {
+ int size = mPendingCompactionProcesses.size();
+ ProcessRecord record;
+ for (int i=0; i < size; ++i) {
+ record = mPendingCompactionProcesses.get(i);
+ // The process record is kept alive after compactions are cleared,
+ // so make sure to reset the compaction state to avoid skipping any future
+ // compactions due to a stale value here.
+ record.mOptRecord.setHasPendingCompact(false);
+ }
+ mPendingCompactionProcesses.clear();
+ }
+ cancelCompaction();
+ }
+
@GuardedBy({"mService", "mProcLock"})
void onOomAdjustChanged(int oldAdj, int newAdj, ProcessRecord app) {
// Cancel any currently executing compactions
@@ -1114,13 +1179,48 @@
// Perform a major compaction when any app enters cached
if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
&& (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
- compactAppSome(app);
+ compactAppSome(app, false);
} else if (newAdj >= ProcessList.CACHED_APP_MIN_ADJ
&& newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
- compactAppFull(app);
+ compactAppFull(app, false);
}
}
+ /**
+ * This method resolves which compaction method we should use for the proposed compaction.
+ */
+ int resolveCompactionAction(int pendingAction) {
+ int resolvedAction;
+
+ switch (pendingAction) {
+ case COMPACT_PROCESS_SOME:
+ resolvedAction = COMPACT_ACTION_FILE;
+ break;
+ // For the time being, treat these as equivalent.
+ case COMPACT_PROCESS_FULL:
+ case COMPACT_PROCESS_PERSISTENT:
+ case COMPACT_PROCESS_BFGS:
+ resolvedAction = COMPACT_ACTION_FULL;
+ break;
+ default:
+ resolvedAction = COMPACT_ACTION_NONE;
+ break;
+ }
+
+ // Downgrade compaction if facing swap memory pressure
+ if (resolvedAction == COMPACT_ACTION_FULL) {
+ double swapUsagePercent = getFreeSwapPercent();
+ if (swapUsagePercent < COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD) {
+ Slog.d(TAG_AM,
+ "Downgraded compaction to file only due to low swap."
+ + " Swap Free% " + swapUsagePercent);
+ resolvedAction = COMPACT_ACTION_FILE;
+ }
+ }
+
+ return resolvedAction;
+ }
+
@VisibleForTesting
static final class LastCompactionStats {
private final long[] mRssAfterCompaction;
@@ -1139,6 +1239,167 @@
super(mCachedAppOptimizerThread.getLooper());
}
+ private boolean shouldOomAdjThrottleCompaction(ProcessRecord proc, int action) {
+ final String name = proc.processName;
+ if (mAm.mInternal.isPendingTopUid(proc.uid)) {
+ // In case the OOM Adjust has not yet been propagated we see if this is
+ // pending on becoming top app in which case we should not compact.
+ Slog.e(TAG_AM, "Skip compaction since UID is active for " + name);
+ return true;
+ }
+
+ // don't compact if the process has returned to perceptible
+ // and this is only a cached/home/prev compaction
+ if ((action == COMPACT_ACTION_FILE || action == COMPACT_ACTION_FULL)
+ && (proc.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Skipping compaction as process " + name + " is "
+ + "now perceptible.");
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean shouldTimeThrottleCompaction(
+ ProcessRecord proc, long start, int pendingAction) {
+ final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
+ final String name = proc.processName;
+
+ int lastCompactAction = opt.getLastCompactAction();
+ long lastCompactTime = opt.getLastCompactTime();
+
+ // basic throttling
+ // use the Phenotype flag knobs to determine whether current/prevous
+ // compaction combo should be throtted or not
+
+ // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
+ // should very seldom change, and taking the risk of using the wrong action is
+ // preferable to taking the lock for every single compaction action.
+ if (lastCompactTime != 0) {
+ if (pendingAction == COMPACT_PROCESS_SOME) {
+ if ((lastCompactAction == COMPACT_PROCESS_SOME
+ && (start - lastCompactTime < mCompactThrottleSomeSome))
+ || (lastCompactAction == COMPACT_PROCESS_FULL
+ && (start - lastCompactTime < mCompactThrottleSomeFull))) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Skipping some compaction for " + name
+ + ": too soon. throttle=" + mCompactThrottleSomeSome
+ + "/" + mCompactThrottleSomeFull
+ + " last=" + (start - lastCompactTime) + "ms ago");
+ }
+ return true;
+ }
+ } else if (pendingAction == COMPACT_PROCESS_FULL) {
+ if ((lastCompactAction == COMPACT_PROCESS_SOME
+ && (start - lastCompactTime < mCompactThrottleFullSome))
+ || (lastCompactAction == COMPACT_PROCESS_FULL
+ && (start - lastCompactTime < mCompactThrottleFullFull))) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Skipping full compaction for " + name
+ + ": too soon. throttle=" + mCompactThrottleFullSome
+ + "/" + mCompactThrottleFullFull
+ + " last=" + (start - lastCompactTime) + "ms ago");
+ }
+ return true;
+ }
+ } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
+ if (start - lastCompactTime < mCompactThrottlePersistent) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Skipping persistent compaction for " + name
+ + ": too soon. throttle=" + mCompactThrottlePersistent
+ + " last=" + (start - lastCompactTime) + "ms ago");
+ }
+ return true;
+ }
+ } else if (pendingAction == COMPACT_PROCESS_BFGS) {
+ if (start - lastCompactTime < mCompactThrottleBFGS) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Skipping bfgs compaction for " + name
+ + ": too soon. throttle=" + mCompactThrottleBFGS
+ + " last=" + (start - lastCompactTime) + "ms ago");
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean shouldThrottleMiscCompaction(
+ ProcessRecord proc, int procState, int action) {
+ final String name = proc.processName;
+ if (mProcStateThrottle.contains(procState)) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Skipping full compaction for process " + name + "; proc state is "
+ + procState);
+ }
+ return true;
+ }
+
+ if (COMPACT_ACTION_NONE == action) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Skipping compaction for process " + name + "since action is None");
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean shouldRssThrottleCompaction(
+ int action, int pid, String name, long[] rssBefore) {
+ long anonRssBefore = rssBefore[RSS_ANON_INDEX];
+ LastCompactionStats lastCompactionStats = mLastCompactionStats.get(pid);
+
+ if (rssBefore[RSS_TOTAL_INDEX] == 0 && rssBefore[RSS_FILE_INDEX] == 0
+ && rssBefore[RSS_ANON_INDEX] == 0 && rssBefore[RSS_SWAP_INDEX] == 0) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Skipping compaction for"
+ + "process " + pid + " with no memory usage. Dead?");
+ }
+ return true;
+ }
+
+ if (action == COMPACT_ACTION_FULL || action == COMPACT_ACTION_ANON) {
+ if (mFullAnonRssThrottleKb > 0L && anonRssBefore < mFullAnonRssThrottleKb) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Skipping full compaction for process " + name
+ + "; anon RSS is too small: " + anonRssBefore + "KB.");
+ }
+ return true;
+ }
+
+ if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
+ long[] lastRss = lastCompactionStats.getRssAfterCompaction();
+ long absDelta = Math.abs(rssBefore[RSS_FILE_INDEX] - lastRss[RSS_FILE_INDEX])
+ + Math.abs(rssBefore[RSS_ANON_INDEX] - lastRss[RSS_ANON_INDEX])
+ + Math.abs(rssBefore[RSS_SWAP_INDEX] - lastRss[RSS_SWAP_INDEX]);
+ if (absDelta <= mFullDeltaRssThrottleKb) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Skipping full compaction for process " + name
+ + "; abs delta is too small: " + absDelta + "KB.");
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -1149,180 +1410,65 @@
int pid;
String action;
final String name;
- int pendingAction, lastCompactAction;
+ int requestedAction, lastCompactAction;
long lastCompactTime;
- LastCompactionStats lastCompactionStats;
int lastOomAdj = msg.arg1;
int procState = msg.arg2;
+ boolean forceCompaction;
synchronized (mProcLock) {
- if(mPendingCompactionProcesses.isEmpty()) {
+ if (mPendingCompactionProcesses.isEmpty()) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM, "No processes pending compaction, bail out");
+ }
return;
}
proc = mPendingCompactionProcesses.remove(0);
opt = proc.mOptRecord;
+ forceCompaction = opt.isForceCompact();
+ opt.setForceCompact(false); // since this is a one-shot operation
- pendingAction = opt.getReqCompactAction();
+ requestedAction = opt.getReqCompactAction();
pid = proc.getPid();
name = proc.processName;
opt.setHasPendingCompact(false);
-
- if (mAm.mInternal.isPendingTopUid(proc.uid)) {
- // In case the OOM Adjust has not yet been propagated we see if this is
- // pending on becoming top app in which case we should not compact.
- Slog.e(TAG_AM, "Skip compaction since UID is active for " + name);
- return;
- }
-
- // don't compact if the process has returned to perceptible
- // and this is only a cached/home/prev compaction
- if ((pendingAction == COMPACT_PROCESS_SOME
- || pendingAction == COMPACT_PROCESS_FULL)
- && (proc.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM,
- "Skipping compaction as process " + name + " is "
- + "now perceptible.");
- }
- return;
- }
-
lastCompactAction = opt.getLastCompactAction();
lastCompactTime = opt.getLastCompactTime();
- lastCompactionStats = mLastCompactionStats.get(pid);
}
+ int resolvedAction = resolveCompactionAction(requestedAction);
+ long[] rssBefore;
if (pid == 0) {
// not a real process, either one being launched or one being killed
- return;
- }
-
- // basic throttling
- // use the Phenotype flag knobs to determine whether current/prevous
- // compaction combo should be throtted or not
-
- // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
- // should very seldom change, and taking the risk of using the wrong action is
- // preferable to taking the lock for every single compaction action.
- if (lastCompactTime != 0) {
- if (pendingAction == COMPACT_PROCESS_SOME) {
- if ((lastCompactAction == COMPACT_PROCESS_SOME
- && (start - lastCompactTime < mCompactThrottleSomeSome))
- || (lastCompactAction == COMPACT_PROCESS_FULL
- && (start - lastCompactTime
- < mCompactThrottleSomeFull))) {
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, "Skipping some compaction for " + name
- + ": too soon. throttle=" + mCompactThrottleSomeSome
- + "/" + mCompactThrottleSomeFull + " last="
- + (start - lastCompactTime) + "ms ago");
- }
- return;
- }
- } else if (pendingAction == COMPACT_PROCESS_FULL) {
- if ((lastCompactAction == COMPACT_PROCESS_SOME
- && (start - lastCompactTime < mCompactThrottleFullSome))
- || (lastCompactAction == COMPACT_PROCESS_FULL
- && (start - lastCompactTime
- < mCompactThrottleFullFull))) {
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, "Skipping full compaction for " + name
- + ": too soon. throttle=" + mCompactThrottleFullSome
- + "/" + mCompactThrottleFullFull + " last="
- + (start - lastCompactTime) + "ms ago");
- }
- return;
- }
- } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
- if (start - lastCompactTime < mCompactThrottlePersistent) {
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, "Skipping persistent compaction for " + name
- + ": too soon. throttle=" + mCompactThrottlePersistent
- + " last=" + (start - lastCompactTime) + "ms ago");
- }
- return;
- }
- } else if (pendingAction == COMPACT_PROCESS_BFGS) {
- if (start - lastCompactTime < mCompactThrottleBFGS) {
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, "Skipping bfgs compaction for " + name
- + ": too soon. throttle=" + mCompactThrottleBFGS
- + " last=" + (start - lastCompactTime) + "ms ago");
- }
- return;
- }
- }
- }
-
- switch (pendingAction) {
- case COMPACT_PROCESS_SOME:
- action = mCompactActionSome;
- break;
- // For the time being, treat these as equivalent.
- case COMPACT_PROCESS_FULL:
- case COMPACT_PROCESS_PERSISTENT:
- case COMPACT_PROCESS_BFGS:
- action = mCompactActionFull;
- break;
- default:
- action = COMPACT_ACTION_STRING[COMPACT_ACTION_NONE];
- break;
- }
-
- if (COMPACT_ACTION_STRING[COMPACT_ACTION_NONE].equals(action)) {
- return;
- }
-
- if (mProcStateThrottle.contains(procState)) {
if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, "Skipping full compaction for process " + name
- + "; proc state is " + procState);
+ Slog.d(TAG_AM, "Compaction failed, pid is 0");
}
return;
}
- long[] rssBefore = mProcessDependencies.getRss(pid);
- long anonRssBefore = rssBefore[2];
-
- if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
- && rssBefore[3] == 0) {
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, "Skipping compaction for" + "process " + pid
- + " with no memory usage. Dead?");
- }
- return;
- }
-
- if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])
- || action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
- if (mFullAnonRssThrottleKb > 0L
- && anonRssBefore < mFullAnonRssThrottleKb) {
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, "Skipping full compaction for process "
- + name + "; anon RSS is too small: " + anonRssBefore
- + "KB.");
- }
+ if (!forceCompaction) {
+ if (shouldOomAdjThrottleCompaction(proc, resolvedAction)) {
return;
}
-
- if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
- long[] lastRss = lastCompactionStats.getRssAfterCompaction();
- long absDelta = Math.abs(rssBefore[1] - lastRss[1])
- + Math.abs(rssBefore[2] - lastRss[2])
- + Math.abs(rssBefore[3] - lastRss[3]);
- if (absDelta <= mFullDeltaRssThrottleKb) {
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, "Skipping full compaction for process "
- + name + "; abs delta is too small: " + absDelta
- + "KB.");
- }
- return;
- }
+ if (shouldTimeThrottleCompaction(proc, start, requestedAction)) {
+ return;
+ }
+ if (shouldThrottleMiscCompaction(proc, procState, resolvedAction)) {
+ return;
+ }
+ rssBefore = mProcessDependencies.getRss(pid);
+ if (shouldRssThrottleCompaction(resolvedAction, pid, name, rssBefore)) {
+ return;
+ }
+ } else {
+ rssBefore = mProcessDependencies.getRss(pid);
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM, "Forcing compaction for " + name);
}
}
// Now we've passed through all the throttles and are going to compact, update
// bookkeeping.
- switch (pendingAction) {
+ switch (requestedAction) {
case COMPACT_PROCESS_SOME:
mSomeCompactionCount++;
break;
@@ -1338,45 +1484,56 @@
default:
break;
}
+ action = compactActionIntToString(resolvedAction);
+
try {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
- + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
- + ": " + name);
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "Compact " + action + ": " + name);
long zramFreeKbBefore = Debug.getZramFreeKb();
mProcessDependencies.performCompaction(action, pid);
long[] rssAfter = mProcessDependencies.getRss(pid);
long end = SystemClock.uptimeMillis();
long time = end - start;
long zramFreeKbAfter = Debug.getZramFreeKb();
+ long deltaTotalRss = rssAfter[RSS_TOTAL_INDEX] - rssBefore[RSS_TOTAL_INDEX];
+ long deltaFileRss = rssAfter[RSS_FILE_INDEX] - rssBefore[RSS_FILE_INDEX];
+ long deltaAnonRss = rssAfter[RSS_ANON_INDEX] - rssBefore[RSS_ANON_INDEX];
+ long deltaSwapRss = rssAfter[RSS_SWAP_INDEX] - rssBefore[RSS_SWAP_INDEX];
EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
- rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
- rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1],
- rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
- lastCompactAction, lastCompactTime, lastOomAdj, procState,
- zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
+ rssBefore[RSS_TOTAL_INDEX], rssBefore[RSS_FILE_INDEX],
+ rssBefore[RSS_ANON_INDEX], rssBefore[RSS_SWAP_INDEX], deltaTotalRss,
+ deltaFileRss, deltaAnonRss, deltaSwapRss, time, lastCompactAction,
+ lastCompactTime, lastOomAdj, procState, zramFreeKbBefore,
+ zramFreeKbAfter - zramFreeKbBefore);
// Note that as above not taking mPhenoTypeFlagLock here to avoid locking
// on every single compaction for a flag that will seldom change and the
// impact of reading the wrong value here is low.
if (mRandom.nextFloat() < mCompactStatsdSampleRate) {
FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPACTED, pid, name,
- pendingAction, rssBefore[0], rssBefore[1], rssBefore[2],
- rssBefore[3], rssAfter[0], rssAfter[1], rssAfter[2],
- rssAfter[3], time, lastCompactAction, lastCompactTime,
- lastOomAdj, ActivityManager.processStateAmToProto(procState),
+ requestedAction, rssBefore[RSS_TOTAL_INDEX],
+ rssBefore[RSS_FILE_INDEX], rssBefore[RSS_ANON_INDEX],
+ rssBefore[RSS_SWAP_INDEX], rssAfter[RSS_TOTAL_INDEX],
+ rssAfter[RSS_FILE_INDEX], rssAfter[RSS_ANON_INDEX],
+ rssAfter[RSS_SWAP_INDEX], time, lastCompactAction,
+ lastCompactTime, lastOomAdj,
+ ActivityManager.processStateAmToProto(procState),
zramFreeKbBefore, zramFreeKbAfter);
}
synchronized (mProcLock) {
opt.setLastCompactTime(end);
- opt.setLastCompactAction(pendingAction);
+ opt.setLastCompactAction(resolvedAction);
}
- if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])
- || action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
+ if (resolvedAction == COMPACT_ACTION_FULL
+ || resolvedAction == COMPACT_ACTION_ANON) {
// Remove entry and insert again to update insertion order.
mLastCompactionStats.remove(pid);
mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
}
} catch (Exception e) {
// nothing to do, presumably the process died
+ Slog.d(TAG_AM,
+ "Exception occurred while compacting pid: " + name
+ + ". Exception:" + e.getMessage());
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -1580,7 +1737,7 @@
* Default implementation for ProcessDependencies, public vor visibility to OomAdjuster class.
*/
private static final class DefaultProcessDependencies implements ProcessDependencies {
- public static int mPidCompacting = -1;
+ public static volatile int mPidCompacting = -1;
// Get memory RSS from process.
@Override
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index a86ba01..a613729 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -56,6 +56,8 @@
@GuardedBy("mProcLock")
private boolean mPendingCompact;
+ @GuardedBy("mProcLock") private boolean mForceCompact;
+
/**
* True when the process is frozen.
*/
@@ -133,6 +135,16 @@
}
@GuardedBy("mProcLock")
+ boolean isForceCompact() {
+ return mForceCompact;
+ }
+
+ @GuardedBy("mProcLock")
+ void setForceCompact(boolean forceCompact) {
+ mForceCompact = forceCompact;
+ }
+
+ @GuardedBy("mProcLock")
boolean isFrozen() {
return mFrozen;
}
@@ -205,6 +217,9 @@
void dump(PrintWriter pw, String prefix, long nowUptime) {
pw.print(prefix); pw.print("lastCompactTime="); pw.print(mLastCompactTime);
pw.print(" lastCompactAction="); pw.println(mLastCompactAction);
+ pw.print(prefix);
+ pw.print("hasPendingCompaction=");
+ pw.print(mPendingCompact);
pw.print(prefix); pw.print("isFreezeExempt="); pw.print(mFreezeExempt);
pw.print(" isPendingFreeze="); pw.print(mPendingFreeze);
pw.print(" " + IS_FROZEN + "="); pw.println(mFrozen);
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 4822ddb..f21f906 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -739,20 +739,7 @@
synchronized (mGlobalLock) {
final ActivityRecord r = ensureValidPictureInPictureActivityParams(
"setPictureInPictureParams", token, params);
-
- // Only update the saved args from the args that are set.
r.setPictureInPictureParams(params);
- if (r.inPinnedWindowingMode()) {
- // If the activity is already in picture-in-picture, update the pinned task now
- // if it is not already expanding to fullscreen. Otherwise, the arguments will
- // be used the next time the activity enters PiP.
- final Task rootTask = r.getRootTask();
- rootTask.setPictureInPictureAspectRatio(
- r.pictureInPictureArgs.getAspectRatioFloat(),
- r.pictureInPictureArgs.getExpandedAspectRatioFloat());
- rootTask.setPictureInPictureActions(r.pictureInPictureArgs.getActions(),
- r.pictureInPictureArgs.getCloseAction());
- }
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 41b724f..8f689be 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -148,7 +148,6 @@
import android.app.PictureInPictureParams;
import android.app.PictureInPictureUiState;
import android.app.ProfilerInfo;
-import android.app.RemoteAction;
import android.app.WaitResult;
import android.app.admin.DevicePolicyCache;
import android.app.assist.AssistContent;
@@ -3471,19 +3470,10 @@
Slog.e(TAG, "Skip enterPictureInPictureMode, destroyed " + r);
return;
}
- // Only update the saved args from the args that are set
r.setPictureInPictureParams(params);
- final float aspectRatio = r.pictureInPictureArgs.getAspectRatioFloat();
- final float expandedAspectRatio =
- r.pictureInPictureArgs.getExpandedAspectRatioFloat();
- final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
- final RemoteAction closeAction = r.pictureInPictureArgs.getCloseAction();
mRootWindowContainer.moveActivityToPinnedRootTask(r,
null /* launchIntoPipHostActivity */, "enterPictureInPictureMode");
final Task task = r.getTask();
- task.setPictureInPictureAspectRatio(aspectRatio, expandedAspectRatio);
- task.setPictureInPictureActions(actions, closeAction);
-
// Continue the pausing process after entering pip.
if (task.getPausingActivity() == r) {
task.schedulePauseActivity(r, false /* userLeaving */,
diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java
index 43d0776..1ddeee9 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -22,9 +22,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.app.PictureInPictureParams;
-import android.app.RemoteAction;
import android.content.ComponentName;
-import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Matrix;
@@ -40,8 +38,6 @@
import android.window.PictureInPictureSurfaceTransaction;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
/**
* Holds the common state of the pinned task between the system and SystemUI. If SystemUI ever
@@ -90,12 +86,6 @@
private boolean mIsImeShowing;
private int mImeHeight;
- // The set of actions and aspect-ratio for the that are currently allowed on the PiP activity
- private ArrayList<RemoteAction> mActions = new ArrayList<>();
- private RemoteAction mCloseAction;
- private float mAspectRatio = -1f;
- private float mExpandedAspectRatio = 0f;
-
// The aspect ratio bounds of the PIP.
private float mMinAspectRatio;
private float mMaxAspectRatio;
@@ -155,7 +145,6 @@
mPinnedTaskListener = listener;
notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
notifyMovementBoundsChanged(false /* fromImeAdjustment */);
- notifyActionsChanged(mActions, mCloseAction);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register pinned task listener", e);
}
@@ -370,55 +359,6 @@
}
/**
- * Sets the current aspect ratio.
- */
- void setAspectRatio(float aspectRatio) {
- if (Float.compare(mAspectRatio, aspectRatio) != 0) {
- mAspectRatio = aspectRatio;
- notifyAspectRatioChanged(aspectRatio);
- notifyMovementBoundsChanged(false /* fromImeAdjustment */);
- }
- }
-
- /**
- * @return the current aspect ratio.
- */
- float getAspectRatio() {
- return mAspectRatio;
- }
-
- /**
- * Sets the current aspect ratio.
- */
- void setExpandedAspectRatio(float aspectRatio) {
- if (Float.compare(mExpandedAspectRatio, aspectRatio) != 0) {
- mExpandedAspectRatio = aspectRatio;
- notifyExpandedAspectRatioChanged(aspectRatio);
- notifyMovementBoundsChanged(false /* fromImeAdjustment */);
- }
- }
-
- /**
- * @return the current aspect ratio.
- */
- float getExpandedAspectRatio() {
- return mExpandedAspectRatio;
- }
-
-
- /**
- * Sets the current set of actions.
- */
- void setActions(List<RemoteAction> actions, RemoteAction closeAction) {
- mActions.clear();
- if (actions != null) {
- mActions.addAll(actions);
- }
- mCloseAction = closeAction;
- notifyActionsChanged(mActions, closeAction);
- }
-
- /**
* Notifies listeners that the PIP needs to be adjusted for the IME.
*/
private void notifyImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -431,37 +371,6 @@
}
}
- private void notifyAspectRatioChanged(float aspectRatio) {
- if (mPinnedTaskListener == null) return;
- try {
- mPinnedTaskListener.onAspectRatioChanged(aspectRatio);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e);
- }
- }
-
- private void notifyExpandedAspectRatioChanged(float aspectRatio) {
- if (mPinnedTaskListener == null) return;
- try {
- mPinnedTaskListener.onExpandedAspectRatioChanged(aspectRatio);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e);
- }
- }
-
- /**
- * Notifies listeners that the PIP actions have changed.
- */
- private void notifyActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) {
- if (mPinnedTaskListener != null) {
- try {
- mPinnedTaskListener.onActionsChanged(new ParceledListSlice(actions), closeAction);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering actions changed event.", e);
- }
- }
- }
-
/**
* Notifies listeners that the PIP movement bounds have changed.
*/
@@ -490,19 +399,7 @@
}
pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
pw.println(prefix + " mImeHeight=" + mImeHeight);
- pw.println(prefix + " mAspectRatio=" + mAspectRatio);
pw.println(prefix + " mMinAspectRatio=" + mMinAspectRatio);
pw.println(prefix + " mMaxAspectRatio=" + mMaxAspectRatio);
- if (mActions.isEmpty()) {
- pw.println(prefix + " mActions=[]");
- } else {
- pw.println(prefix + " mActions=[");
- for (int i = 0; i < mActions.size(); i++) {
- RemoteAction action = mActions.get(i);
- pw.print(prefix + " Action[" + i + "]: ");
- action.dump("", pw);
- }
- pw.println(prefix + " ]");
- }
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 00e6117..718ce28 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -149,7 +149,6 @@
import android.app.AppGlobals;
import android.app.IActivityController;
import android.app.PictureInPictureParams;
-import android.app.RemoteAction;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -218,7 +217,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -6076,58 +6074,6 @@
}
}
- /**
- * Sets the current picture-in-picture aspect ratios.
- */
- void setPictureInPictureAspectRatio(float aspectRatio, float expandedAspectRatio) {
- if (!mWmService.mAtmService.mSupportsPictureInPicture) {
- return;
- }
-
- final DisplayContent displayContent = getDisplayContent();
- if (displayContent == null) {
- return;
- }
-
- if (!inPinnedWindowingMode()) {
- return;
- }
-
- final PinnedTaskController pinnedTaskController =
- getDisplayContent().getPinnedTaskController();
-
- // Notify the pinned stack controller about aspect ratio change.
- // This would result a callback delivered from SystemUI to WM to start animation,
- // if the bounds are ought to be altered due to aspect ratio change.
- if (Float.compare(aspectRatio, pinnedTaskController.getAspectRatio()) != 0) {
- pinnedTaskController.setAspectRatio(
- pinnedTaskController.isValidPictureInPictureAspectRatio(aspectRatio)
- ? aspectRatio : -1f);
- }
-
- if (mWmService.mAtmService.mSupportsExpandedPictureInPicture && Float.compare(
- expandedAspectRatio, pinnedTaskController.getExpandedAspectRatio()) != 0) {
- pinnedTaskController.setExpandedAspectRatio(pinnedTaskController
- .isValidExpandedPictureInPictureAspectRatio(expandedAspectRatio)
- ? expandedAspectRatio : 0f);
- }
- }
-
- /**
- * Sets the current picture-in-picture actions.
- */
- void setPictureInPictureActions(List<RemoteAction> actions, RemoteAction closeAction) {
- if (!mWmService.mAtmService.mSupportsPictureInPicture) {
- return;
- }
-
- if (!inPinnedWindowingMode()) {
- return;
- }
-
- getDisplayContent().getPinnedTaskController().setActions(actions, closeAction);
- }
-
public DisplayInfo getDisplayInfo() {
return mDisplayContent.getDisplayInfo();
}
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 636ca41..49a4021 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -16,6 +16,8 @@
#define LOG_TAG "CachedAppOptimizer"
//#define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_ACTIVITY_MANAGER
+#define ATRACE_COMPACTION_TRACK "Compaction"
#include <android-base/file.h>
#include <android-base/logging.h>
@@ -37,8 +39,10 @@
#include <sys/pidfd.h>
#include <sys/stat.h>
#include <sys/syscall.h>
+#include <sys/sysinfo.h>
#include <sys/types.h>
#include <unistd.h>
+#include <utils/Trace.h>
#include <algorithm>
@@ -62,18 +66,197 @@
// Defines the maximum amount of VMAs we can send per process_madvise syscall.
// Currently this is set to UIO_MAXIOV which is the maximum segments allowed by
// iovec implementation used by process_madvise syscall
-#define MAX_VMAS_PER_COMPACTION UIO_MAXIOV
+#define MAX_VMAS_PER_BATCH UIO_MAXIOV
// Maximum bytes that we can send per process_madvise syscall once this limit
// is reached we split the remaining VMAs into another syscall. The MAX_RW_COUNT
// limit is imposed by iovec implementation. However, if you want to use a smaller
-// limit, it has to be a page aligned value, otherwise, compaction would fail.
-#define MAX_BYTES_PER_COMPACTION MAX_RW_COUNT
+// limit, it has to be a page aligned value.
+#define MAX_BYTES_PER_BATCH MAX_RW_COUNT
+
+// Selected a high enough number to avoid clashing with linux errno codes
+#define ERROR_COMPACTION_CANCELLED -1000
namespace android {
-static bool cancelRunningCompaction;
-static bool compactionInProgress;
+// Signal happening in separate thread that would bail out compaction
+// before starting next VMA batch
+static std::atomic<bool> cancelRunningCompaction;
+
+// A VmaBatch represents a set of VMAs that can be processed
+// as VMAs are processed by client code it is expected that the
+// VMAs get consumed which means they are discarded as they are
+// processed so that the first element always is the next element
+// to be sent
+struct VmaBatch {
+ struct iovec* vmas;
+ // total amount of VMAs to reach the end of iovec
+ int totalVmas;
+ // total amount of bytes that are remaining within iovec
+ uint64_t totalBytes;
+};
+
+// Advances the iterator by the specified amount of bytes.
+// This is used to remove already processed or no longer
+// needed parts of the batch.
+// Returns total bytes consumed
+int consumeBytes(VmaBatch& batch, uint64_t bytesToConsume) {
+ int index = 0;
+ if (CC_UNLIKELY(bytesToConsume) < 0) {
+ LOG(ERROR) << "Cannot consume negative bytes for VMA batch !";
+ return 0;
+ }
+
+ if (bytesToConsume > batch.totalBytes) {
+ // Avoid consuming more bytes than available
+ bytesToConsume = batch.totalBytes;
+ }
+
+ uint64_t bytesConsumed = 0;
+ while (bytesConsumed < bytesToConsume) {
+ if (CC_UNLIKELY(index >= batch.totalVmas)) {
+ // reach the end of the batch
+ return bytesConsumed;
+ }
+ if (CC_UNLIKELY(bytesConsumed + batch.vmas[index].iov_len > bytesToConsume)) {
+ // this is the whole VMA that will be consumed
+ break;
+ }
+ bytesConsumed += batch.vmas[index].iov_len;
+ batch.totalBytes -= batch.vmas[index].iov_len;
+ --batch.totalVmas;
+ ++index;
+ }
+
+ // Move pointer to consume all the whole VMAs
+ batch.vmas = batch.vmas + index;
+
+ // Consume the rest of the bytes partially at last VMA in batch
+ uint64_t bytesLeftToConsume = bytesToConsume - bytesConsumed;
+ bytesConsumed += bytesLeftToConsume;
+ if (batch.totalVmas > 0) {
+ batch.vmas[0].iov_base = (void*)((uint64_t)batch.vmas[0].iov_base + bytesLeftToConsume);
+ }
+
+ return bytesConsumed;
+}
+
+// given a source of vmas this class will act as a factory
+// of VmaBatch objects and it will allow generating batches
+// until there are no more left in the source vector.
+// Note: the class does not actually modify the given
+// vmas vector, instead it iterates on it until the end.
+class VmaBatchCreator {
+ const std::vector<Vma>* sourceVmas;
+ // This is the destination array where batched VMAs will be stored
+ // it gets encapsulated into a VmaBatch which is the object
+ // meant to be used by client code.
+ struct iovec* destVmas;
+
+ // Parameters to keep track of the iterator on the source vmas
+ int currentIndex_;
+ uint64_t currentOffset_;
+
+public:
+ VmaBatchCreator(const std::vector<Vma>* vmasToBatch, struct iovec* destVmasVec)
+ : sourceVmas(vmasToBatch), destVmas(destVmasVec), currentIndex_(0), currentOffset_(0) {}
+
+ int currentIndex() { return currentIndex_; }
+ uint64_t currentOffset() { return currentOffset_; }
+
+ // Generates a batch and moves the iterator on the source vmas
+ // past the last VMA in the batch.
+ // Returns true on success, false on failure
+ bool createNextBatch(VmaBatch& batch) {
+ if (currentIndex_ >= MAX_VMAS_PER_BATCH && currentIndex_ >= sourceVmas->size()) {
+ return false;
+ }
+
+ const std::vector<Vma>& vmas = *sourceVmas;
+ batch.vmas = destVmas;
+ uint64_t totalBytesInBatch = 0;
+ int indexInBatch = 0;
+
+ // Add VMAs to the batch up until we consumed all the VMAs or
+ // reached any imposed limit of VMAs per batch.
+ while (indexInBatch < MAX_VMAS_PER_BATCH && currentIndex_ < vmas.size()) {
+ uint64_t vmaStart = vmas[currentIndex_].start + currentOffset_;
+ uint64_t vmaSize = vmas[currentIndex_].end - vmaStart;
+ if (CC_UNLIKELY(vmaSize == 0)) {
+ // No more bytes to batch for this VMA, move to next one
+ // this only happens if a batch partially consumed bytes
+ // and offset landed at exactly the end of a vma
+ continue;
+ }
+ batch.vmas[indexInBatch].iov_base = (void*)vmaStart;
+ uint64_t bytesAvailableInBatch = MAX_BYTES_PER_BATCH - totalBytesInBatch;
+
+ if (vmaSize >= bytesAvailableInBatch) {
+ // VMA would exceed the max available bytes in batch
+ // clamp with available bytes and finish batch.
+ vmaSize = bytesAvailableInBatch;
+ currentOffset_ += bytesAvailableInBatch;
+ }
+
+ batch.vmas[indexInBatch].iov_len = vmaSize;
+ totalBytesInBatch += vmaSize;
+
+ ++indexInBatch;
+ if (totalBytesInBatch >= MAX_BYTES_PER_BATCH) {
+ // Reached max bytes quota so this marks
+ // the end of the batch
+ break;
+ }
+
+ // Fully finished current VMA, move to next one
+ currentOffset_ = 0;
+ ++currentIndex_;
+ }
+ // Vmas where fully filled and we are past the last filled index.
+ batch.totalVmas = indexInBatch;
+ batch.totalBytes = totalBytesInBatch;
+ return true;
+ }
+};
+
+// Madvise a set of VMAs given in a batch for a specific process
+// The total number of bytes successfully madvised will be set on
+// outBytesProcessed.
+// Returns 0 on success and standard linux -errno code returned by
+// process_madvise on failure
+int madviseVmasFromBatch(unique_fd& pidfd, VmaBatch& batch, int madviseType,
+ uint64_t* outBytesProcessed) {
+ if (batch.totalVmas == 0) {
+ // No VMAs in Batch, skip.
+ *outBytesProcessed = 0;
+ return 0;
+ }
+
+ ATRACE_BEGIN(StringPrintf("Madvise %d: %d VMAs", madviseType, batch.totalVmas).c_str());
+ uint64_t bytesProcessedInSend =
+ process_madvise(pidfd, batch.vmas, batch.totalVmas, madviseType, 0);
+ ATRACE_END();
+
+ if (CC_UNLIKELY(bytesProcessedInSend == -1)) {
+ bytesProcessedInSend = 0;
+ if (errno != EINVAL) {
+ // Forward irrecoverable errors and bail out compaction
+ *outBytesProcessed = 0;
+ return -errno;
+ }
+ }
+
+ if (bytesProcessedInSend < batch.totalBytes) {
+ // Did not process all the bytes requested
+ // skip last page which likely failed
+ bytesProcessedInSend += PAGE_SIZE;
+ }
+
+ bytesProcessedInSend = consumeBytes(batch, bytesProcessedInSend);
+
+ *outBytesProcessed = bytesProcessedInSend;
+ return 0;
+}
// Legacy method for compacting processes, any new code should
// use compactProcess instead.
@@ -88,8 +271,6 @@
// If any VMA fails compaction due to -EINVAL it will be skipped and continue.
// However, if it fails for any other reason, it will bail out and forward the error
static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
- static struct iovec vmasToKernel[MAX_VMAS_PER_COMPACTION];
-
if (vmas.empty()) {
return 0;
}
@@ -99,73 +280,34 @@
// Skip compaction if failed to open pidfd with any error
return -errno;
}
- compactionInProgress = true;
- cancelRunningCompaction = false;
+
+ struct iovec destVmas[MAX_VMAS_PER_BATCH];
+
+ VmaBatch batch;
+ VmaBatchCreator batcher(&vmas, destVmas);
int64_t totalBytesProcessed = 0;
+ while (batcher.createNextBatch(batch)) {
+ uint64_t bytesProcessedInSend;
- int64_t vmaOffset = 0;
- for (int iVma = 0; iVma < vmas.size();) {
- uint64_t bytesSentToCompact = 0;
- int iVec = 0;
- while (iVec < MAX_VMAS_PER_COMPACTION && iVma < vmas.size()) {
- if (CC_UNLIKELY(cancelRunningCompaction)) {
+ do {
+ if (CC_UNLIKELY(cancelRunningCompaction.load())) {
// There could be a significant delay between when a compaction
// is requested and when it is handled during this time our
// OOM adjust could have improved.
LOG(DEBUG) << "Cancelled running compaction for " << pid;
- break;
+ ATRACE_INSTANT_FOR_TRACK(ATRACE_COMPACTION_TRACK,
+ StringPrintf("Cancelled compaction for %d", pid).c_str());
+ return ERROR_COMPACTION_CANCELLED;
}
-
- uint64_t vmaStart = vmas[iVma].start + vmaOffset;
- uint64_t vmaSize = vmas[iVma].end - vmaStart;
- if (vmaSize == 0) {
- goto next_vma;
+ int error = madviseVmasFromBatch(pidfd, batch, madviseType, &bytesProcessedInSend);
+ if (error < 0) {
+ // Returns standard linux errno code
+ return error;
}
- vmasToKernel[iVec].iov_base = (void*)vmaStart;
- if (vmaSize > MAX_BYTES_PER_COMPACTION - bytesSentToCompact) {
- // Exceeded the max bytes that could be sent, so clamp
- // the end to avoid exceeding limit and issue compaction
- vmaSize = MAX_BYTES_PER_COMPACTION - bytesSentToCompact;
- }
-
- vmasToKernel[iVec].iov_len = vmaSize;
- bytesSentToCompact += vmaSize;
- ++iVec;
- if (bytesSentToCompact >= MAX_BYTES_PER_COMPACTION) {
- // Ran out of bytes within iovec, dispatch compaction.
- vmaOffset += vmaSize;
- break;
- }
-
- next_vma:
- // Finished current VMA, and have more bytes remaining
- vmaOffset = 0;
- ++iVma;
- }
-
- if (cancelRunningCompaction) {
- cancelRunningCompaction = false;
- break;
- }
-
- auto bytesProcessed = process_madvise(pidfd, vmasToKernel, iVec, madviseType, 0);
-
- if (CC_UNLIKELY(bytesProcessed == -1)) {
- if (errno == EINVAL) {
- // This error is somewhat common due to an unevictable VMA if this is
- // the case silently skip the bad VMA and continue compacting the rest.
- continue;
- } else {
- // Forward irrecoverable errors and bail out compaction
- compactionInProgress = false;
- return -errno;
- }
- }
-
- totalBytesProcessed += bytesProcessed;
+ totalBytesProcessed += bytesProcessedInSend;
+ } while (batch.totalBytes > 0);
}
- compactionInProgress = false;
return totalBytesProcessed;
}
@@ -194,9 +336,12 @@
//
// Currently supported behaviors are MADV_COLD and MADV_PAGEOUT.
//
-// Returns the total number of bytes compacted or forwards an
-// process_madvise error.
+// Returns the total number of bytes compacted on success. On error
+// returns process_madvise errno code or if compaction was cancelled
+// it returns ERROR_COMPACTION_CANCELLED.
static int64_t compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) {
+ cancelRunningCompaction.store(false);
+
ProcMemInfo meminfo(pid);
std::vector<Vma> pageoutVmas, coldVmas;
auto vmaCollectorCb = [&coldVmas,&pageoutVmas,&vmaToAdviseFunc](const Vma& vma) {
@@ -215,12 +360,14 @@
int64_t pageoutBytes = compactMemory(pageoutVmas, pid, MADV_PAGEOUT);
if (pageoutBytes < 0) {
// Error, just forward it.
+ cancelRunningCompaction.store(false);
return pageoutBytes;
}
int64_t coldBytes = compactMemory(coldVmas, pid, MADV_COLD);
if (coldBytes < 0) {
// Error, just forward it.
+ cancelRunningCompaction.store(false);
return coldBytes;
}
@@ -300,9 +447,18 @@
}
static void com_android_server_am_CachedAppOptimizer_cancelCompaction(JNIEnv*, jobject) {
- if (compactionInProgress) {
- cancelRunningCompaction = true;
+ cancelRunningCompaction.store(true);
+ ATRACE_INSTANT_FOR_TRACK(ATRACE_COMPACTION_TRACK, "Cancel compaction");
+}
+
+static jdouble com_android_server_am_CachedAppOptimizer_getFreeSwapPercent(JNIEnv*, jobject) {
+ struct sysinfo memoryInfo;
+ int error = sysinfo(&memoryInfo);
+ if(error == -1) {
+ LOG(ERROR) << "Could not check free swap space";
+ return 0;
}
+ return (double)memoryInfo.freeswap / (double)memoryInfo.totalswap;
}
static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, jobject, jint pid,
@@ -358,6 +514,8 @@
/* name, signature, funcPtr */
{"cancelCompaction", "()V",
(void*)com_android_server_am_CachedAppOptimizer_cancelCompaction},
+ {"getFreeSwapPercent", "()D",
+ (void*)com_android_server_am_CachedAppOptimizer_getFreeSwapPercent},
{"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
{"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
{"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 90fd8ed..8aa9f60 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -23,7 +23,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
+// import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -174,6 +174,7 @@
};
private final class Client implements IBinder.DeathRecipient {
+ private static final String TAG = "MidiService.Client";
// Binder token for this client
private final IBinder mToken;
// This client's UID
@@ -215,7 +216,9 @@
}
public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback) {
+ Log.d(TAG, "addDeviceConnection() device:" + device);
if (mDeviceConnections.size() >= MAX_CONNECTIONS_PER_CLIENT) {
+ Log.i(TAG, "too many MIDI connections for UID = " + mUid);
throw new SecurityException(
"too many MIDI connections for UID = " + mUid);
}
@@ -343,6 +346,7 @@
}
private final class Device implements IBinder.DeathRecipient {
+ private static final String TAG = "MidiService.Device";
private IMidiDeviceServer mServer;
private MidiDeviceInfo mDeviceInfo;
private final BluetoothDevice mBluetoothDevice;
@@ -378,6 +382,7 @@
}
private void setDeviceServer(IMidiDeviceServer server) {
+ Log.i(TAG, "setDeviceServer()");
if (server != null) {
if (mServer != null) {
Log.e(TAG, "mServer already set in setDeviceServer");
@@ -459,21 +464,28 @@
}
public void addDeviceConnection(DeviceConnection connection) {
+ Log.d(TAG, "addDeviceConnection() [A] connection:" + connection);
synchronized (mDeviceConnections) {
+ Log.d(TAG, " mServer:" + mServer);
if (mServer != null) {
+ Log.i(TAG, "++++ A");
mDeviceConnections.add(connection);
connection.notifyClient(mServer);
} else if (mServiceConnection == null &&
(mServiceInfo != null || mBluetoothDevice != null)) {
+ Log.i(TAG, "++++ B");
mDeviceConnections.add(connection);
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "++++ onServiceConnected() mBluetoothDevice:"
+ + mBluetoothDevice);
IMidiDeviceServer server = null;
if (mBluetoothDevice != null) {
IBluetoothMidiService mBluetoothMidiService =
IBluetoothMidiService.Stub.asInterface(service);
+ Log.i(TAG, "++++ mBluetoothMidiService:" + mBluetoothMidiService);
if (mBluetoothMidiService != null) {
try {
// We need to explicitly add the device in a separate method
@@ -592,6 +604,7 @@
// Represents a connection between a client and a device
private final class DeviceConnection {
+ private static final String TAG = "MidiService.DeviceConnection";
private final IBinder mToken = new Binder();
private final Device mDevice;
private final Client mClient;
@@ -616,6 +629,8 @@
}
public void notifyClient(IMidiDeviceServer deviceServer) {
+ Log.d(TAG, "notifyClient");
+
if (mCallback != null) {
try {
mCallback.onDeviceOpened(deviceServer, (deviceServer == null ? null : mToken));
@@ -628,7 +643,9 @@
@Override
public String toString() {
- return "DeviceConnection Device ID: " + mDevice.getDeviceInfo().getId();
+// return "DeviceConnection Device ID: " + mDevice.getDeviceInfo().getId();
+ return mDevice != null && mDevice.getDeviceInfo() != null
+ ? ("" + mDevice.getDeviceInfo().getId()) : "null";
}
}
@@ -669,9 +686,10 @@
}
private void dumpUuids(BluetoothDevice btDevice) {
- Log.d(TAG, "UUIDs for " + btDevice);
-
ParcelUuid[] uuidParcels = btDevice.getUuids();
+ Log.d(TAG, "dumpUuids(" + btDevice + ") numParcels:"
+ + (uuidParcels != null ? uuidParcels.length : 0));
+
if (uuidParcels == null) {
Log.d(TAG, "No UUID Parcels");
return;
@@ -707,7 +725,8 @@
}
switch (action) {
- case BluetoothDevice.ACTION_ACL_CONNECTED: {
+ case BluetoothDevice.ACTION_ACL_CONNECTED:
+ {
Log.d(TAG, "ACTION_ACL_CONNECTED");
dumpIntentExtras(intent);
// BLE-MIDI controllers are by definition BLE, so if this device
@@ -734,7 +753,8 @@
}
break;
- case BluetoothDevice.ACTION_ACL_DISCONNECTED: {
+ case BluetoothDevice.ACTION_ACL_DISCONNECTED:
+ {
Log.d(TAG, "ACTION_ACL_DISCONNECTED");
BluetoothDevice btDevice =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
@@ -745,6 +765,35 @@
}
}
break;
+
+ case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
+// {
+// Log.d(TAG, "ACTION_BOND_STATE_CHANGED");
+// int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+// Log.d(TAG, " bondState:" + bondState);
+// BluetoothDevice btDevice =
+// intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+// Log.d(TAG, " btDevice:" + btDevice);
+// dumpUuids(btDevice);
+// if (isBLEMIDIDevice(btDevice)) {
+// Log.d(TAG, "BT MIDI DEVICE");
+// openBluetoothDevice(btDevice);
+// }
+// }
+// break;
+
+ case BluetoothDevice.ACTION_UUID:
+ {
+ Log.d(TAG, "ACTION_UUID");
+ BluetoothDevice btDevice =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ dumpUuids(btDevice);
+ if (isBLEMIDIDevice(btDevice)) {
+ Log.d(TAG, "BT MIDI DEVICE");
+ openBluetoothDevice(btDevice);
+ }
+ }
+ break;
}
}
};
@@ -753,11 +802,15 @@
mContext = context;
mPackageManager = context.getPackageManager();
+ // TEMPORARY - Disable BTL-MIDI
+ //FIXME - b/25689266
// Setup broadcast receivers
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
- filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
- context.registerReceiver(mBleMidiReceiver, filter);
+// IntentFilter filter = new IntentFilter();
+// filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+// filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+// filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+// filter.addAction(BluetoothDevice.ACTION_UUID);
+// context.registerReceiver(mBleMidiReceiver, filter);
mBluetoothServiceUid = -1;
@@ -771,6 +824,8 @@
mNonMidiUUIDs.add(BluetoothUuid.LE_AUDIO);
mNonMidiUUIDs.add(BluetoothUuid.HOGP);
mNonMidiUUIDs.add(BluetoothUuid.HEARING_AID);
+ // This one is coming up
+ // mNonMidiUUIDs.add(BluetoothUuid.BATTERY);
}
private void onUnlockUser() {
@@ -876,11 +931,13 @@
public void openDevice(IBinder token, MidiDeviceInfo deviceInfo,
IMidiDeviceOpenCallback callback) {
Client client = getClient(token);
+ Log.d(TAG, "openDevice() client:" + client);
if (client == null) return;
Device device;
synchronized (mDevicesByInfo) {
device = mDevicesByInfo.get(deviceInfo);
+ Log.d(TAG, " device:" + device);
if (device == null) {
throw new IllegalArgumentException("device does not exist: " + deviceInfo);
}
@@ -901,6 +958,7 @@
// clear calling identity so bindService does not fail
final long identity = Binder.clearCallingIdentity();
try {
+ Log.i(TAG, "addDeviceConnection() [B] device:" + device);
client.addDeviceConnection(device, callback);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -916,6 +974,7 @@
@Override
public void onDeviceOpened(MidiDevice device) {
synchronized (mBleMidiDeviceMap) {
+ Log.i(TAG, "onDeviceOpened() device:" + device);
mBleMidiDeviceMap.put(bluetoothDevice, device);
}
}
@@ -949,6 +1008,7 @@
// Bluetooth devices are created on demand
Device device;
+ Log.i(TAG, "alloc device...");
synchronized (mDevicesByInfo) {
device = mBluetoothDevices.get(bluetoothDevice);
if (device == null) {
@@ -956,10 +1016,11 @@
mBluetoothDevices.put(bluetoothDevice, device);
}
}
-
+ Log.i(TAG, "device: " + device);
// clear calling identity so bindService does not fail
final long identity = Binder.clearCallingIdentity();
try {
+ Log.i(TAG, "addDeviceConnection() [C] device:" + device);
client.addDeviceConnection(device, callback);
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp
index 89b204b..f454ac7 100644
--- a/services/tests/mockingservicestests/jni/Android.bp
+++ b/services/tests/mockingservicestests/jni/Android.bp
@@ -44,6 +44,7 @@
"libnativehelper",
"libprocessgroup",
"libutils",
+ "libcutils",
"android.hardware.graphics.bufferqueue@1.0",
"android.hardware.graphics.bufferqueue@2.0",
"android.hardware.graphics.common@1.2",
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index bf46f55..2baa1ec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -857,6 +857,7 @@
.containsExactlyElementsIn(expected);
}
+ @SuppressWarnings("GuardedBy")
@Test
public void processWithDeltaRSSTooSmall_notFullCompacted() throws Exception {
// Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS
@@ -892,7 +893,7 @@
mProcessDependencies.setRss(rssBefore1);
mProcessDependencies.setRssAfterCompaction(rssAfter1); //
// WHEN we try to run compaction
- mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
waitForHandler();
// THEN process IS compacted.
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
@@ -907,7 +908,7 @@
processRecord.mOptRecord.setLastCompactTime(
processRecord.mOptRecord.getLastCompactTime() - 10_000);
// WHEN we try to run compaction.
- mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
waitForHandler();
// THEN process IS NOT compacted - values after compaction for process 1 should remain the
// same as from the last compaction.
@@ -923,7 +924,7 @@
processRecord.mOptRecord.setLastCompactTime(
processRecord.mOptRecord.getLastCompactTime() - 10_000);
// WHEN we try to run compaction
- mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
waitForHandler();
// THEN process IS compacted - values after compaction for process 1 should be updated.
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
@@ -932,6 +933,7 @@
assertThat(valuesAfter).isEqualTo(rssAfter3);
}
+ @SuppressWarnings("GuardedBy")
@Test
public void processWithAnonRSSTooSmall_notFullCompacted() throws Exception {
// Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS
@@ -963,7 +965,7 @@
mProcessDependencies.setRss(rssBelowThreshold);
mProcessDependencies.setRssAfterCompaction(rssBelowThresholdAfter);
// WHEN we try to run compaction
- mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
waitForHandler();
// THEN process IS NOT compacted.
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
@@ -972,7 +974,7 @@
mProcessDependencies.setRss(rssAboveThreshold);
mProcessDependencies.setRssAfterCompaction(rssAboveThresholdAfter);
// WHEN we try to run compaction
- mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
waitForHandler();
// THEN process IS compacted.
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
@@ -981,6 +983,7 @@
assertThat(valuesAfter).isEqualTo(rssAboveThresholdAfter);
}
+ @SuppressWarnings("GuardedBy")
@Test
public void processWithOomAdjTooSmall_notFullCompacted() throws Exception {
// Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set Min and
@@ -993,10 +996,11 @@
// Simulate RSS memory for which compaction should occur.
long[] rssBefore =
- new long[]{/*Total RSS*/ 15000, /*File RSS*/ 15000, /*Anon RSS*/ 15000,
- /*Swap*/ 10000};
+ new long[]{/*Total RSS*/ 15000, /*File RSS*/ 15000, /*Anon RSS*/ 15000,
+ /*Swap*/ 10000};
long[] rssAfter =
- new long[]{/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/5000};
+ new long[]{/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/
+ 5000};
// Process that passes properties.
int pid = 1;
ProcessRecord processRecord =
@@ -1010,7 +1014,7 @@
processRecord.mState.setSetAdj(899);
processRecord.mState.setCurAdj(970);
// WHEN we try to run compaction
- mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
waitForHandler();
// THEN process IS NOT compacted.
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
@@ -1019,16 +1023,71 @@
processRecord.mState.setSetAdj(910);
processRecord.mState.setCurAdj(930);
// WHEN we try to run compaction
- mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
waitForHandler();
// THEN process IS compacted.
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
long[] valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats
- .get(pid)
- .getRssAfterCompaction();
+ .get(pid)
+ .getRssAfterCompaction();
assertThat(valuesAfter).isEqualTo(rssAfter);
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void process_forceCompacted() throws Exception {
+ mCachedAppOptimizerUnderTest.init();
+ setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true);
+ setFlag(CachedAppOptimizer.KEY_COMPACT_THROTTLE_MIN_OOM_ADJ, Long.toString(920), true);
+ setFlag(CachedAppOptimizer.KEY_COMPACT_THROTTLE_MAX_OOM_ADJ, Long.toString(950), true);
+ initActivityManagerService();
+
+ long[] rssBefore = new long[] {/*Total RSS*/ 15000, /*File RSS*/ 15000, /*Anon RSS*/ 15000,
+ /*Swap*/ 10000};
+ long[] rssAfter = new long[] {
+ /*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/ 5000};
+ // Process that passes properties.
+ int pid = 1;
+ ProcessRecord processRecord = makeProcessRecord(pid, 2, 3, "p1", "app1");
+ mProcessDependencies.setRss(rssBefore);
+ mProcessDependencies.setRssAfterCompaction(rssAfter);
+
+ // Use an OOM Adjust value that usually avoids compaction
+ processRecord.mState.setSetAdj(100);
+ processRecord.mState.setCurAdj(100);
+
+ // Compact process full
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord, false);
+ waitForHandler();
+ // the process is not compacted
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
+
+ // Compact process some
+ mCachedAppOptimizerUnderTest.compactAppSome(processRecord, false);
+ waitForHandler();
+ // the process is not compacted
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
+
+ processRecord.mState.setSetAdj(100);
+ processRecord.mState.setCurAdj(100);
+
+ // We force a full compaction
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord, true);
+ waitForHandler();
+ // then process is compacted.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
+
+ mCachedAppOptimizerUnderTest.mLastCompactionStats.clear();
+
+ // We force a some compaction
+ mCachedAppOptimizerUnderTest.compactAppSome(processRecord, true);
+ waitForHandler();
+ // then process is compacted.
+ String executedCompactAction =
+ compactActionIntToString(processRecord.mOptRecord.getLastCompactAction());
+ assertThat(executedCompactAction)
+ .isEqualTo(mCachedAppOptimizerUnderTest.mCompactActionSome);
+ }
private void setFlag(String key, String value, boolean defaultValue) throws Exception {
mCountDown = new CountDownLatch(1);