diff options
101 files changed, 2872 insertions, 1319 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index cac13743461d..4681d4943256 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -116,8 +116,8 @@ package android.app { 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 458dd5d804d1..abd60177f884 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3710,10 +3710,16 @@ public class ActivityManager { /** * 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 @@ public class ActivityManager { /** * 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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 6e395be215b5..c7c654a0b071 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2537,8 +2537,8 @@ public class AppOpsManager { * restriction} for a certain app-op. */ private static RestrictionBypass[] sOpAllowSystemRestrictionBypass = new RestrictionBypass[] { - new RestrictionBypass(true, false), //COARSE_LOCATION - new RestrictionBypass(true, false), //FINE_LOCATION + null, //COARSE_LOCATION + null, //FINE_LOCATION null, //GPS null, //VIBRATE null, //READ_CONTACTS diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 49a61580ab3b..4efe9dfe7185 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -100,6 +100,8 @@ interface IActivityManager { 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 @@ interface IActivityManager { /** 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/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index add891d40d95..b49e571f74e1 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -374,10 +374,6 @@ public class PropertyInvalidatedCache<Query, Result> { private static final String TAG = "PropertyInvalidatedCache"; private static final boolean DEBUG = false; private static final boolean VERIFY = false; - // If this is true, dumpsys will dump the cache entries along with cache statistics. - // Most of the time this causes dumpsys to fail because the output stream is too - // large. Only set it to true in development images. - private static final boolean DETAILED = false; // Per-Cache performance counters. As some cache instances are declared static, @GuardedBy("mLock") @@ -1358,7 +1354,69 @@ public class PropertyInvalidatedCache<Query, Result> { } } - private void dumpContents(PrintWriter pw) { + /** + * Switches that can be used to control the detail emitted by a cache dump. The + * "CONTAINS" switches match if the cache (property) name contains the switch + * argument. The "LIKE" switches match if the cache (property) name matches the + * switch argument as a regex. The regular expression must match the entire name, + * which generally means it may need leading/trailing "." expressions. + */ + final static String NAME_CONTAINS = "-name-has="; + final static String NAME_LIKE = "-name-like="; + final static String PROPERTY_CONTAINS = "-property-has="; + final static String PROPERTY_LIKE = "-property-like="; + + /** + * Return true if any argument is a detailed specification switch. + */ + private static boolean anyDetailed(String[] args) { + for (String a : args) { + if (a.startsWith(NAME_CONTAINS) || a.startsWith(NAME_LIKE) + || a.startsWith(PROPERTY_CONTAINS) || a.startsWith(PROPERTY_LIKE)) { + return true; + } + } + return false; + } + + /** + * A helper method to determine if a string matches a switch. + */ + private static boolean chooses(String arg, String key, String reference, boolean contains) { + if (arg.startsWith(key)) { + final String value = arg.substring(key.length()); + if (contains) { + return reference.contains(value); + } else { + return reference.matches(value); + } + } + return false; + } + + /** + * Return true if this cache should be dumped in detail. This method is not called + * unless it has already been determined that there is at least one match requested. + */ + private boolean showDetailed(String[] args) { + for (String a : args) { + if (chooses(a, NAME_CONTAINS, cacheName(), true) + || chooses(a, NAME_LIKE, cacheName(), false) + || chooses(a, PROPERTY_CONTAINS, mPropertyName, true) + || chooses(a, PROPERTY_LIKE, mPropertyName, false)) { + return true; + } + } + return false; + } + + private void dumpContents(PrintWriter pw, boolean detailed, String[] args) { + // If the user has requested specific caches and this is not one of them, return + // immediately. + if (detailed && !showDetailed(args)) { + return; + } + long invalidateCount; long corkedInvalidates; synchronized (sCorkLock) { @@ -1386,9 +1444,15 @@ public class PropertyInvalidatedCache<Query, Result> { mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow)); pw.println(TextUtils.formatSimple(" Enabled: %s", mDisabled ? "false" : "true")); pw.println(""); + pw.flush(); + // No specific cache was requested. This is the default, and no details + // should be dumped. + if (!detailed) { + return; + } Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet(); - if (!DETAILED || cacheEntries.size() == 0) { + if (cacheEntries.size() == 0) { return; } @@ -1399,17 +1463,34 @@ public class PropertyInvalidatedCache<Query, Result> { pw.println(TextUtils.formatSimple(" Key: %s\n Value: %s\n", key, value)); } + pw.flush(); + } + } + + /** + * Dump the corking status. + */ + @GuardedBy("sCorkLock") + private static void dumpCorkInfo(PrintWriter pw) { + ArrayList<Map.Entry<String, Integer>> activeCorks = getActiveCorks(); + if (activeCorks.size() > 0) { + pw.println(" Corking Status:"); + for (int i = 0; i < activeCorks.size(); i++) { + Map.Entry<String, Integer> entry = activeCorks.get(i); + pw.println(TextUtils.formatSimple(" Property Name: %s Count: %d", + entry.getKey(), entry.getValue())); + } } } /** - * Dumps contents of every cache in the process to the provided ParcelFileDescriptor. + * Without arguments, this dumps statistics from every cache in the process to the + * provided ParcelFileDescriptor. Optional switches allow the caller to choose + * specific caches (selection is by cache name or property name); if these switches + * are used then the output includes both cache statistics and cache entries. * @hide */ public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) { - ArrayList<PropertyInvalidatedCache> activeCaches; - ArrayList<Map.Entry<String, Integer>> activeCorks; - try ( FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor()); PrintWriter pw = new FastPrintWriter(fout); @@ -1419,24 +1500,21 @@ public class PropertyInvalidatedCache<Query, Result> { return; } + // See if detailed is requested for any cache. If there is a specific detailed request, + // then only that cache is reported. + boolean detail = anyDetailed(args); + + ArrayList<PropertyInvalidatedCache> activeCaches; synchronized (sCorkLock) { activeCaches = getActiveCaches(); - activeCorks = getActiveCorks(); - - if (activeCorks.size() > 0) { - pw.println(" Corking Status:"); - for (int i = 0; i < activeCorks.size(); i++) { - Map.Entry<String, Integer> entry = activeCorks.get(i); - pw.println(TextUtils.formatSimple(" Property Name: %s Count: %d", - entry.getKey(), entry.getValue())); - } + if (!detail) { + dumpCorkInfo(pw); } } for (int i = 0; i < activeCaches.size(); i++) { PropertyInvalidatedCache currentCache = activeCaches.get(i); - currentCache.dumpContents(pw); - pw.flush(); + currentCache.dumpContents(pw, detail, args); } } catch (IOException e) { Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances"); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index a6ed42348af6..efd4f0681838 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -482,8 +482,9 @@ public class InputMethodService extends AbstractInputMethodService { /** * Timeout after which hidden IME surface will be removed from memory + * TODO(b/230762351): reset timeout to 5000ms and invalidate cache when IME insets change. */ - private static final long TIMEOUT_SURFACE_REMOVAL_MILLIS = 5000; + private static final long TIMEOUT_SURFACE_REMOVAL_MILLIS = 500; InputMethodManager mImm; private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations(); diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java index c5ab82dbf0c5..11481209284a 100644 --- a/core/java/android/util/PackageUtils.java +++ b/core/java/android/util/PackageUtils.java @@ -171,39 +171,53 @@ public final class PackageUtils { } /** - * @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. + * @see #computeSha256DigestForLargeFile(String, byte[], String) + */ + public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, + @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 digest or null if an error occurs. + * @return The SHA256 digest or null if an error occurs. */ public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, - @Nullable String separator) { + @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 595a846e069a..e4e2d6f30aab 100644 --- a/core/java/android/view/IPinnedTaskListener.aidl +++ b/core/java/android/view/IPinnedTaskListener.aidl @@ -44,26 +44,10 @@ oneway interface IPinnedTaskListener { 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 cba0e970d389..0f27989642a9 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.CancellationSignal; 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 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple 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 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple private volatile boolean mActive; private volatile boolean mConnected; + private int mTraceId; /** * Constructs a ScrollCaptureConnection. @@ -86,6 +96,9 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple @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 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple 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 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple } 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 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple 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 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple 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 6c4933e07b90..a1ce39e974e3 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3883,8 +3883,8 @@ public final class SurfaceControl implements Parcelable { @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 b7a2aa0b0174..6665ab62703b 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -321,6 +321,11 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, 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 4ad232ad25eb..8bd0f7b2fb57 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.stats.devicepolicy.DevicePolicyEnums; 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 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { 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 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } } - 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, - @DrawableRes int iconRes, String title, String subtitle) { - showEmptyState(activeListAdapter, iconRes, title, subtitle, /* 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, @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 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { 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 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } 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 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } 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 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } 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 916408984674..d35d5ca6fca7 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -193,7 +193,6 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter, View.OnClickListener listener) { showEmptyState(activeListAdapter, - R.drawable.ic_work_apps_off, getWorkAppPausedTitle(), /* subtitle = */ null, listener); @@ -203,12 +202,10 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd 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 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd 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 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd @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 17c2dc09140e..3d93b2a231c2 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -673,7 +673,6 @@ public class ResolverActivity extends Activity implements 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 f4e568b6676a..0b33501fd875 100644 --- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java @@ -26,7 +26,6 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK 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 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA 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 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter, View.OnClickListener listener) { showEmptyState(activeListAdapter, - R.drawable.ic_work_apps_off, getWorkAppPausedTitle(), /* subtitle = */ null, listener); @@ -212,7 +192,6 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA @Override protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) { showEmptyState(activeListAdapter, - R.drawable.ic_sharing_disabled, getCrossProfileBlockedTitle(), getCantAccessWorkMessage()); } @@ -220,7 +199,6 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA @Override protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) { showEmptyState(activeListAdapter, - R.drawable.ic_sharing_disabled, getCrossProfileBlockedTitle(), getCantAccessPersonalMessage()); } @@ -228,7 +206,6 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA @Override protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, - R.drawable.ic_no_apps, getNoPersonalAppsAvailableMessage(), /* subtitle = */ null); } @@ -236,7 +213,6 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA @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 4d296bddd813..000000000000 --- 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 d488cdbb8b7b..000000000000 --- 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 f62eb2769bef..000000000000 --- 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 8594c33c3082..d5174f8b4cd9 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 d9d1a082ed4a..b73f96e3696c 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 8226ec435533..55c1cb9af013 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 bfa6ce5d36c5..74cad1aaa057 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -881,7 +881,7 @@ public class RippleDrawable extends LayerDrawable { 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 @@ public class RippleDrawable extends LayerDrawable { 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 @@ public class RippleDrawable extends LayerDrawable { @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 @@ public class RippleDrawable extends LayerDrawable { 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 @@ public class RippleDrawable extends LayerDrawable { 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 @@ public class RippleDrawable extends LayerDrawable { } @UnsupportedAppUsage - Paint getRipplePaint() { + Paint updateRipplePaint() { if (mRipplePaint == null) { mRipplePaint = new Paint(); mRipplePaint.setAntiAlias(true); @@ -1215,6 +1210,12 @@ public class RippleDrawable extends LayerDrawable { 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 0f376957c8ff..1655fba93878 100644 --- a/graphics/java/android/graphics/drawable/RippleForeground.java +++ b/graphics/java/android/graphics/drawable/RippleForeground.java @@ -252,7 +252,7 @@ class RippleForeground extends RippleComponent { 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 @@ class RippleForeground extends RippleComponent { 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 e50b9a1cd469..81caf7786cf5 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 @@ import java.util.concurrent.Executor; 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 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * @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 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { 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 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { 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 2328f76a7130..b370e59ac7c8 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen 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 ee5a322eed4f..d8423499730f 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { 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 be793018d969..3c0762d81494 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 @@ class TaskContainer { /** 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 @@ class TaskContainer { return !mTaskBounds.isEmpty(); } - @Nullable - Configuration getConfiguration() { - return mConfiguration; + void setWindowingMode(int windowingMode) { + mWindowingMode = windowingMode; + } + + /** 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; } - void setConfiguration(@Nullable Configuration configuration) { - mConfiguration = configuration; + 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 b06ce4c19d5c..1f12c4484159 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.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; + /** * Test class for {@link JetpackTaskFragmentOrganizer}. * @@ -48,6 +58,8 @@ public class JetpackTaskFragmentOrganizerTest { private static final int TASK_ID = 10; @Mock + private WindowContainerTransaction mTransaction; + @Mock private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback; private JetpackTaskFragmentOrganizer mOrganizer; @@ -91,4 +103,24 @@ public class JetpackTaskFragmentOrganizerTest { 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 9fb08dffbab8..c7feb7e59de3 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 @@ public class TaskContainerTest { } @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 000000000000..7475abac4695 --- /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 dbd5a9b370ab..7a3ee23d8cdc 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 a86a14525022..db96d8de4094 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 776b18ecc01b..02e726fbc3bf 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 1dd5ebcd993e..72c8141c8f2a 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.common.annotations.ShellMainThread; 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.TvPipBoundsState; 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 @@ public abstract class TvPipModule { PipTransitionController pipTransitionController, TvPipNotificationController tvPipNotificationController, TaskStackListenerImpl taskStackListener, + PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper windowManagerShellWrapper, @ShellMainThread ShellExecutor mainExecutor, @@ -82,6 +85,7 @@ public abstract class TvPipModule { pipMediaController, tvPipNotificationController, taskStackListener, + pipParamsChangedForwarder, displayController, windowManagerShellWrapper, mainExecutor, @@ -163,15 +167,22 @@ public abstract class TvPipModule { 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 e43f4fc34adf..7513e5129ade 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.PipAnimationController; 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 @@ public class WMShellModule { 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 @@ public class WMShellModule { PipAnimationController pipAnimationController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, PipTransitionController pipTransitionController, + PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenControllerOptional, DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @@ -304,7 +306,7 @@ public class WMShellModule { return new PipTaskOrganizer(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm, menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper, - pipTransitionController, splitScreenControllerOptional, + pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } @@ -391,4 +393,10 @@ public class WMShellModule { 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 87eca74acd0b..ce98458c0575 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 @@ public class PinnedStackListenerForwarder { } } - 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 @@ public class PinnedStackListenerForwarder { } @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 class PinnedStackListenerForwarder { 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 f6ff294b4328..16f1d1c2944c 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 static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 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 @@ public interface PipMenuController { /** * 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 000000000000..21ba85459c48 --- /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 fbdf6f0b539f..4690e16bc385 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.content.res.Configuration; 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipBoundsAlgorithm = boundsHandler; mPipMenuController = pipMenuController; mPipTransitionController = pipTransitionController; + mPipParamsChangedForwarder = pipParamsChangedForwarder; mEnterAnimationDuration = context.getResources() .getInteger(R.integer.config_pipEnterAnimationDuration); mExitAnimationDuration = context.getResources() @@ -559,6 +561,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** - * @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()); - } - return aspectRatioChanged; + protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) { + if (PipUtils.aspectRatioChanged(params.getAspectRatioFloat(), + mPictureInPictureParams.getAspectRatioFloat())) { + mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat()); + } + 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 d7b69adf1241..c6cf8b8b0566 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 static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 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 android.util.Pair; 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 @@ public class PipUtils { } 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 bbec4eccce3c..4942987742a0 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.annotation.Nullable; 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 @@ public class PhonePipMenuController implements PipMenuController { 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 @@ public class PhonePipMenuController implements PipMenuController { 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 @@ public class PhonePipMenuController implements PipMenuController { 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 @@ public class PhonePipMenuController implements PipMenuController { * 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 @@ public class PhonePipMenuController implements PipMenuController { /** * @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 @@ public class PhonePipMenuController implements PipMenuController { */ 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 272331b7cd3f..2e8b5b7979d0 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.app.RemoteAction; 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.PipAnimationController; 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.protolog.ShellProtoLogGroup; 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, TaskStackListenerImpl taskStackListener, + PipParamsChangedForwarder pipParamsChangedForwarder, Optional<OneHandedController> oneHandedController, ShellExecutor mainExecutor ) { @@ -360,6 +353,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb } }); + 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 a3048bd8fabe..ca22882187d8 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 class TvPipBoundsState extends PipBoundsState { 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 0e1f5a24f4a7..8326588bbbad 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.RemoteAction; 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.Pip; 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 com.android.wm.shell.protolog.ShellProtoLogGroup; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; import java.util.Set; /** @@ -66,7 +67,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal 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 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipMediaController pipMediaController, TvPipNotificationController pipNotificationController, TaskStackListenerImpl taskStackListener, + PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper wmShell, ShellExecutor mainExecutor, @@ -141,6 +142,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal pipMediaController, pipNotificationController, taskStackListener, + pipParamsChangedForwarder, displayController, wmShell, mainExecutor, @@ -157,6 +159,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipMediaController pipMediaController, TvPipNotificationController pipNotificationController, TaskStackListenerImpl taskStackListener, + PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper wmShell, ShellExecutor mainExecutor, @@ -183,6 +186,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal loadConfigurations(); + registerPipParamsChangedListener(pipParamsChangedForwarder); registerTaskStackListenerCallback(taskStackListener); registerWmShellPinnedStackListener(wmShell); displayController.addDisplayWindowListener(this); @@ -380,6 +384,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal animationDuration, rect -> { mTvPipMenuController.updateExpansionState(); }); + mTvPipMenuController.onPipTransitionStarted(bounds); } /** @@ -540,6 +545,73 @@ public class TvPipController implements PipTransitionController.PipTransitionCal }); } + 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 @@ public class TvPipController implements PipTransitionController.PipTransitionCal 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 abbc614b4b4f..a09aab666a31 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 @@ import com.android.wm.shell.R; */ 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 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic 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 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic 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 2d67254f3610..132c04481bce 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.BroadcastReceiver; 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 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis mPipMenuView.setListener(this); setUpViewSurfaceZOrder(mPipMenuView, 1); addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE); + maybeUpdateMenuViewActions(); } private void attachPipBackgroundView() { @@ -199,6 +199,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis void notifyPipAnimating(boolean animating) { mPipMenuView.setEduTextActive(!animating); + if (!animating) { + mPipMenuView.onPipTransitionFinished(); + } } void showMovementMenuOnly() { @@ -235,6 +238,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } else { mPipMenuView.showButtonsMenu(); } + mPipMenuView.updateBounds(mTvPipBoundsState.getBounds()); + } + + void onPipTransitionStarted(Rect finishBounds) { + if (mPipMenuView != null) { + mPipMenuView.onPipTransitionStarted(finishBounds); + } } private void maybeCloseEduText() { @@ -336,12 +346,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } @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 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis 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 5b0db8c86529..868e45655ba3 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.View; 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 androidx.annotation.Nullable; 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 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { 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 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { 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 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { 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 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { 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(); + } + + private void changeButtonScrollOrientation(Rect bounds) { + final boolean vertical = bounds.height() > bounds.width(); - if (previouslyVertical == vertical) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: no update for menu layout", TAG); + 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 { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: change menu layout to vertical: %b", TAG, vertical); - } + 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) { - mHorizontalScrollView.removeView(mActionButtonsContainer); - mScrollView.addView(mActionButtonsContainer); + mScrollView.getDrawingRect(scrollBounds); } else { - mScrollView.removeView(mActionButtonsContainer); - mHorizontalScrollView.addView(mActionButtonsContainer); + mHorizontalScrollView.getDrawingRect(scrollBounds); } - mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL - : LinearLayout.HORIZONTAL); + mFocusedButton.getHitRect(buttonBounds); - mScrollView.setVisibility(vertical ? VISIBLE : GONE); - mHorizontalScrollView.setVisibility(vertical ? GONE : VISIBLE); + 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) { + mScrollView.smoothScrollTo((int) mFocusedButton.getX(), + (int) mFocusedButton.getY()); + } else { + mHorizontalScrollView.smoothScrollTo((int) mFocusedButton.getX(), + (int) mFocusedButton.getY()); + } } Rect getPipMenuContainerBounds(Rect pipBounds) { @@ -300,6 +449,8 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { 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 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { 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 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { }); } + /** + * 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 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { 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 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { 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 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { @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 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { 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 000000000000..42fd1aab44f8 --- /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 def9ad20a632..4b85496f2a7f 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 @@ public class PipTaskOrganizerTest extends ShellTestCase { private PipBoundsState mPipBoundsState; private PipTransitionState mPipTransitionState; private PipBoundsAlgorithm mPipBoundsAlgorithm; + private PipParamsChangedForwarder mPipParamsChangedForwarder; private ComponentName mComponent1; private ComponentName mComponent2; @@ -97,11 +98,10 @@ public class PipTaskOrganizerTest extends ShellTestCase { 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 deb955b30842..5368b7db3dc1 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.onehanded.OneHandedController; 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 @@ public class PipControllerTest extends ShellTestCase { @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 @@ public class PipControllerTest extends ShellTestCase { 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 @@ public class PipControllerTest extends ShellTestCase { 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 0e6249047980..2fe7b1668d08 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 @@ public final class BluetoothMidiDevice { 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 aa0ef91be46b..ee7b7d6b180f 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.bluetooth.LocalBluetoothManager; 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 @@ public class InfoMediaManagerTest { 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 @@ public class InfoMediaManagerTest { } @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 @@ public class InfoMediaManagerTest { 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 ccfd3a3d79f0..b230438f66fd 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"> - <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: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" - 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: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/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 0e9700fe6b1e..dc86afaf6497 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 ef030baa967b..4d5bf53eb64a 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 6d52a30be7b4..18386637e8f8 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 2b1c47f60070..630fb360cc14 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 class QuickStepContract { 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 @@ public class QuickStepContract { 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 @@ public class QuickStepContract { 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 @@ public class QuickStepContract { * 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 e63e675bfd14..e1913657b7cc 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -56,6 +56,7 @@ public class LockIconView extends FrameLayout implements Dumpable { @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 @@ public class LockIconView extends FrameLayout implements Dumpable { * 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 @@ public class LockIconView extends FrameLayout implements Dumpable { 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 95cda8b9dfca..2b217f02e834 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.PointF; 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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private float mHeightPixels; private float mWidthPixels; private int mBottomPaddingPx; + private int mScaledPaddingPx; private boolean mShowUnlockIcon; private boolean mShowLockIcon; @@ -341,6 +340,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme 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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme 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 d4dad73a49f2..b6b9065f335e 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 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } /** + * @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 59c658fa43d2..49e378e4a76f 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.Canvas; 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 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { 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 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { @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 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { @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 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { 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 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { } 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 937b81337cbb..9139699af26a 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 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { 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 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { 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 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { 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 416b8ee60fd9..8b0f36fe1245 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 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud updateGenericBouncerVisibility(); mConfigurationController.addCallback(mConfigurationListener); getPanelExpansionStateManager().addExpansionListener(mPanelExpansionListener); + updateScaleFactor(); + mView.updatePadding(); updateAlpha(); updatePauseAuth(); @@ -367,6 +369,15 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } } + /** + * 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 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @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 eef9d5bebd75..bd67a7ff1fa0 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 @@ public class ClipboardOverlayController { 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 @@ public class ClipboardOverlayController { 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 class ClipboardOverlayController { 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 @@ public class ClipboardOverlayController { attachWindow(); withWindowAttached(() -> { - mWindow.setContentView(mContainer); + mWindow.setContentView(mView); updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets()); mView.requestLayout(); }); @@ -308,7 +299,7 @@ public class ClipboardOverlayController { } else { mRemoteCopyChip.setVisibility(View.GONE); } - withWindowAttached(() -> mContainer.post(this::animateIn)); + withWindowAttached(() -> mView.post(this::animateIn)); mTimeoutHandler.resetTimeout(); } @@ -508,7 +499,7 @@ public class ClipboardOverlayController { 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 @@ public class ClipboardOverlayController { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - mContainer.setAlpha(1); + mView.setAlpha(1); mTimeoutHandler.resetTimeout(); } }); @@ -568,9 +559,7 @@ public class ClipboardOverlayController { 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 @@ public class ClipboardOverlayController { private void reset() { mView.setTranslationX(0); - mContainer.setAlpha(0); + mView.setAlpha(0); mActionContainerBackground.setVisibility(View.GONE); resetActionChips(); mTimeoutHandler.cancelTimeout(); @@ -706,8 +695,9 @@ public class ClipboardOverlayController { } 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 @@ public class ClipboardOverlayController { 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 b21a886b037d..fb09132684eb 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.dagger.SysUISingleton 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 d9ee8f3f06b4..20417aff024c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -18,6 +18,8 @@ package com.android.systemui.media; import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS; +import static com.android.systemui.media.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS; + import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorSet; @@ -48,7 +50,6 @@ import android.util.Log; 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; @@ -102,7 +103,6 @@ public class MediaControlPanel { + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity"; private static final String EXTRAS_SMARTSPACE_INTENT = "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT"; - private static final int MEDIA_RECOMMENDATION_MAX_NUM = 3; private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name"; private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND"; private static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"; @@ -552,30 +552,6 @@ public class MediaControlPanel { // 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; }, () -> { @@ -949,16 +925,14 @@ public class MediaControlPanel { return; } + if (!data.isValid()) { + Log.e(TAG, "Received an invalid recommendation list; returning"); + return; + } + mSmartspaceId = SmallHash.hash(data.getTargetId()); mPackageName = data.getPackageName(); mInstanceId = data.getInstanceId(); - TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations(); - - List<SmartspaceAction> mediaRecommendationList = data.getRecommendations(); - if (mediaRecommendationList == null || mediaRecommendationList.isEmpty()) { - Log.w(TAG, "Empty media recommendations"); - return; - } // Set up recommendation card's header. ApplicationInfo applicationInfo; @@ -994,6 +968,7 @@ public class MediaControlPanel { } // Set up media rec card's tap action if applicable. + TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations(); setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(), /* interactedSubcardRank */ -1); // Set up media rec card's accessibility label. @@ -1002,29 +977,20 @@ public class MediaControlPanel { List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems(); List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers(); - int mediaRecommendationNum = Math.min(mediaRecommendationList.size(), - MEDIA_RECOMMENDATION_MAX_NUM); + List<SmartspaceAction> recommendations = data.getValidRecommendations(); boolean hasTitle = false; boolean hasSubtitle = false; - int uiComponentIndex = 0; - for (int itemIndex = 0; - itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum; - itemIndex++) { - SmartspaceAction recommendation = mediaRecommendationList.get(itemIndex); - if (recommendation.getIcon() == null) { - Log.w(TAG, "No media cover is provided. Skipping this item..."); - continue; - } + for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) { + SmartspaceAction recommendation = recommendations.get(itemIndex); // Set up media item cover. - ImageView mediaCoverImageView = mediaCoverItems.get(uiComponentIndex); + ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex); mediaCoverImageView.setImageIcon(recommendation.getIcon()); // Set up the media item's click listener if applicable. - ViewGroup mediaCoverContainer = mediaCoverContainers.get(uiComponentIndex); - setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation, - uiComponentIndex); + ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex); + setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation, itemIndex); // Bubble up the long-click event to the card. mediaCoverContainer.setOnLongClickListener(v -> { View parent = (View) v.getParent(); @@ -1053,8 +1019,7 @@ public class MediaControlPanel { // Set up title CharSequence title = recommendation.getTitle(); hasTitle |= !TextUtils.isEmpty(title); - TextView titleView = - mRecommendationViewHolder.getMediaTitles().get(uiComponentIndex); + TextView titleView = mRecommendationViewHolder.getMediaTitles().get(itemIndex); titleView.setText(title); // Set up subtitle @@ -1062,13 +1027,10 @@ public class MediaControlPanel { boolean shouldShowSubtitleText = !TextUtils.isEmpty(title); CharSequence subtitle = shouldShowSubtitleText ? recommendation.getSubtitle() : ""; hasSubtitle |= !TextUtils.isEmpty(subtitle); - TextView subtitleView = - mRecommendationViewHolder.getMediaSubtitles().get(uiComponentIndex); + TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); subtitleView.setText(subtitle); - - uiComponentIndex++; } - mSmartspaceMediaItemsCount = uiComponentIndex; + mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS; // If there's no subtitles and/or titles for any of the albums, hide those views. ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); @@ -1301,7 +1263,7 @@ public class MediaControlPanel { } logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT, interactedSubcardRank, - getSmartspaceSubCardCardinality()); + mSmartspaceMediaItemsCount); if (shouldSmartspaceRecItemOpenInForeground(action)) { // Request to unlock the device if the activity needs to be opened in foreground. @@ -1386,13 +1348,4 @@ public class MediaControlPanel { interactedSubcardRank, interactedSubcardCardinality); } - - private int getSmartspaceSubCardCardinality() { - if (!mMediaCarouselController.getMediaCarouselScrollHandler().getQsExpanded() - && mSmartspaceMediaItemsCount > 3) { - return 3; - } - - return mSmartspaceMediaItemsCount; - } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index 647d3efa5916..81efdf591b41 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -166,7 +166,7 @@ class MediaDataFilter @Inject constructor( shouldPrioritizeMutable = true } - if (!data.isValid) { + if (!data.isValid()) { Log.d(TAG, "Invalid recommendation data. Skip showing the rec card") return } @@ -203,7 +203,6 @@ class MediaDataFilter @Inject constructor( if (smartspaceMediaData.isActive) { smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( targetId = smartspaceMediaData.targetId, - isValid = smartspaceMediaData.isValid, instanceId = smartspaceMediaData.instanceId) } listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) } @@ -260,7 +259,6 @@ class MediaDataFilter @Inject constructor( } smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( targetId = smartspaceMediaData.targetId, - isValid = smartspaceMediaData.isValid, instanceId = smartspaceMediaData.instanceId) mediaDataManager.dismissSmartspaceRecommendation(smartspaceMediaData.targetId, delay = 0L) @@ -272,13 +270,13 @@ class MediaDataFilter @Inject constructor( */ fun hasActiveMediaOrRecommendation() = userEntries.any { it.value.active } || - (smartspaceMediaData.isActive && smartspaceMediaData.isValid) + (smartspaceMediaData.isActive && smartspaceMediaData.isValid()) /** * Are there any media entries we should display? */ fun hasAnyMediaOrRecommendation() = userEntries.isNotEmpty() || - (smartspaceMediaData.isActive && smartspaceMediaData.isValid) + (smartspaceMediaData.isActive && smartspaceMediaData.isValid()) /** * Are there any media notifications active (excluding the recommendation)? diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 426b45a31550..0a4455658b6b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -105,7 +105,6 @@ private val LOADING = MediaData( internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData( targetId = "INVALID", isActive = false, - isValid = false, packageName = "INVALID", cardAction = null, recommendations = emptyList(), @@ -551,7 +550,7 @@ class MediaDataManager( * connection session. */ fun dismissSmartspaceRecommendation(key: String, delay: Long) { - if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid) { + if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) { // If this doesn't match, or we've already invalidated the data, no action needed return } @@ -1240,7 +1239,6 @@ class MediaDataManager( return SmartspaceMediaData( targetId = target.smartspaceTargetId, isActive = isActive, - isValid = true, packageName = it, cardAction = target.baseAction, recommendations = target.iconGrid, diff --git a/packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt index 9a1a6d35e3e3..48f4a16cc538 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 @@ package com.android.systemui.media 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 @@ internal open class MetadataAnimationHandler( 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 @@ internal open class MetadataAnimationHandler( 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/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt index 930c5a8de125..50a96f601443 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt @@ -31,10 +31,6 @@ data class SmartspaceMediaData( */ val isActive: Boolean, /** - * Indicates if all the required data field is valid. - */ - val isValid: Boolean, - /** * Package name of the media recommendations' provider-app. */ val packageName: String, @@ -58,4 +54,19 @@ data class SmartspaceMediaData( * Instance ID for [MediaUiEventLogger] */ val instanceId: InstanceId -) +) { + /** + * Indicates if all the data is valid. + * + * TODO(b/230333302): Make MediaControlPanel more flexible so that we can display fewer than + * [NUM_REQUIRED_RECOMMENDATIONS]. + */ + fun isValid() = getValidRecommendations().size >= NUM_REQUIRED_RECOMMENDATIONS + + /** + * Returns the list of [recommendations] that have valid data. + */ + fun getValidRecommendations() = recommendations.filter { it.icon != null } +} + +const val NUM_REQUIRED_RECOMMENDATIONS = 3 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 ddcba3ae65ae..07001ee138b8 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } } - 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/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 9ef90ecf50a7..311ee56477de 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -17,6 +17,7 @@ import com.android.systemui.R; import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanelControllerBase.TileRecord; import com.android.systemui.qs.tileimpl.HeightOverrideable; +import com.android.systemui.qs.tileimpl.QSTileViewImplKt; import java.util.ArrayList; @@ -242,7 +243,12 @@ public class TileLayout extends ViewGroup implements QSTileLayout { record.tileView.setLeftTopRightBottom(left, top, right, bottom); } record.tileView.setPosition(i); - mLastTileBottom = bottom; + if (forLayout) { + mLastTileBottom = record.tileView.getBottom(); + } else { + float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction); + mLastTileBottom = top + (int) (record.tileView.getMeasuredHeight() * scale); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 72dad0608a3a..59164dea9c45 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -262,7 +262,7 @@ open class QSTileViewImpl @JvmOverloads constructor( } // Limit how much we affect the height, so we don't have rounding artifacts when the tile // is too short. - val constrainedSquishiness = 0.1f + squishinessFraction * 0.9f + val constrainedSquishiness = constrainSquishiness(squishinessFraction) bottom = top + (actualHeight * constrainedSquishiness).toInt() scrollY = (actualHeight - height) / 2 } @@ -678,6 +678,10 @@ internal object SubtitleArrayMapping { } } +fun constrainSquishiness(squish: Float): Float { + return 0.1f + squish * 0.9f +} + private fun colorValuesHolder(name: String, vararg values: Int): PropertyValuesHolder { return PropertyValuesHolder.ofInt(name, *values).apply { setEvaluator(ArgbEvaluator.getInstance()) diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index c723fbb9a6e1..9768e706764f 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_D 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 androidx.annotation.NonNull; 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 @@ public class OverviewProxyService extends CurrentUserTracker implements 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 @@ public class OverviewProxyService extends CurrentUserTracker implements ScreenLifecycle screenLifecycle, UiEventLogger uiEventLogger, KeyguardUnlockAnimationController sysuiUnlockAnimationController, + AssistUtils assistUtils, DumpManager dumpManager) { super(broadcastDispatcher); mContext = context; @@ -640,6 +668,9 @@ public class OverviewProxyService extends CurrentUserTracker implements startConnectionToCurrentUser(); mStartingSurface = startingSurface; mSysuiUnlockAnimationController = sysuiUnlockAnimationController; + + // Listen for assistant changes + assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener); } @Override @@ -648,6 +679,11 @@ public class OverviewProxyService extends CurrentUserTracker implements 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/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index dea429f6c617..d49e1e6acc23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -33,7 +33,6 @@ import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkScoreManager; import android.net.wifi.ScanResult; import android.net.wifi.WifiManager; import android.os.AsyncTask; @@ -225,10 +224,10 @@ public class NetworkControllerImpl extends BroadcastReceiver TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, @Nullable WifiManager wifiManager, - NetworkScoreManager networkScoreManager, AccessPointControllerImpl accessPointController, DemoModeController demoModeController, CarrierConfigTracker carrierConfigTracker, + WifiStatusTrackerFactory trackerFactory, @Main Handler handler, InternetDialogFactory internetDialogFactory, FeatureFlags featureFlags, @@ -237,7 +236,6 @@ public class NetworkControllerImpl extends BroadcastReceiver telephonyManager, telephonyListenerManager, wifiManager, - networkScoreManager, subscriptionManager, Config.readConfig(context), bgLooper, @@ -250,6 +248,7 @@ public class NetworkControllerImpl extends BroadcastReceiver broadcastDispatcher, demoModeController, carrierConfigTracker, + trackerFactory, handler, featureFlags, dumpManager); @@ -262,8 +261,9 @@ public class NetworkControllerImpl extends BroadcastReceiver TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, WifiManager wifiManager, - NetworkScoreManager networkScoreManager, - SubscriptionManager subManager, Config config, Looper bgLooper, + SubscriptionManager subManager, + Config config, + Looper bgLooper, Executor bgExecutor, CallbackHandler callbackHandler, AccessPointControllerImpl accessPointController, @@ -273,6 +273,7 @@ public class NetworkControllerImpl extends BroadcastReceiver BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController, CarrierConfigTracker carrierConfigTracker, + WifiStatusTrackerFactory trackerFactory, @Main Handler handler, FeatureFlags featureFlags, DumpManager dumpManager @@ -315,9 +316,10 @@ public class NetworkControllerImpl extends BroadcastReceiver notifyControllersMobileDataChanged(); } }); + mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature, - mCallbackHandler, this, mWifiManager, mConnectivityManager, networkScoreManager, - mMainHandler, mReceiverHandler); + mCallbackHandler, this, mWifiManager, trackerFactory, + mReceiverHandler); mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java index e2806a39130f..7e8f04e4bc23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java @@ -50,7 +50,7 @@ public abstract class SignalController<T extends ConnectivityState, I extends Ic protected final T mLastState; protected final int mTransportType; protected final Context mContext; - // The owner of the SignalController (i.e. NetworkController will maintain the following + // The owner of the SignalController (i.e. NetworkController) will maintain the following // lists and call notifyListeners whenever the list has changed to ensure everyone // is aware of current state. protected final NetworkControllerImpl mNetworkController; @@ -103,7 +103,7 @@ public abstract class SignalController<T extends ConnectivityState, I extends Ic * Determines if the state of this signal controller has changed and * needs to trigger callbacks related to it. */ - public boolean isDirty() { + boolean isDirty() { if (!mLastState.equals(mCurrentState)) { if (DEBUG) { Log.d(mTag, "Change in state from: " + mLastState + "\n" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java index a4589c8dd6d3..87cdb17245f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java @@ -21,14 +21,13 @@ import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OU import android.content.Context; import android.content.Intent; -import android.net.ConnectivityManager; import android.net.NetworkCapabilities; -import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.Handler; import android.text.Html; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import com.android.settingslib.SignalIcon.IconGroup; import com.android.settingslib.SignalIcon.MobileIconGroup; import com.android.settingslib.graph.SignalDrawable; @@ -36,8 +35,6 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.wifi.WifiStatusTracker; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.Assert; import java.io.PrintWriter; @@ -49,21 +46,21 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> private final MobileIconGroup mCarrierMergedWifiIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI; private final WifiManager mWifiManager; + private final Handler mBgHandler; + public WifiSignalController( Context context, boolean hasMobileDataFeature, CallbackHandler callbackHandler, NetworkControllerImpl networkController, WifiManager wifiManager, - ConnectivityManager connectivityManager, - NetworkScoreManager networkScoreManager, - @Main Handler handler, - @Background Handler backgroundHandler) { + WifiStatusTrackerFactory trackerFactory, + @Background Handler bgHandler) { super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, callbackHandler, networkController); + mBgHandler = bgHandler; mWifiManager = wifiManager; - mWifiTracker = new WifiStatusTracker(mContext, wifiManager, networkScoreManager, - connectivityManager, this::handleStatusUpdated, handler, backgroundHandler); + mWifiTracker = trackerFactory.createTracker(this::handleStatusUpdated, bgHandler); mWifiTracker.setListening(true); mHasMobileDataFeature = hasMobileDataFeature; if (wifiManager != null) { @@ -181,33 +178,51 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> * Fetches wifi initial state replacing the initial sticky broadcast. */ public void fetchInitialState() { - mWifiTracker.fetchInitialState(); - copyWifiStates(); - notifyListenersIfNecessary(); + doInBackground(() -> { + mWifiTracker.fetchInitialState(); + copyWifiStates(); + notifyListenersIfNecessary(); + }); } /** * Extract wifi state directly from broadcasts about changes in wifi state. */ - public void handleBroadcast(Intent intent) { - mWifiTracker.handleBroadcast(intent); - copyWifiStates(); - notifyListenersIfNecessary(); + void handleBroadcast(Intent intent) { + doInBackground(() -> { + mWifiTracker.handleBroadcast(intent); + copyWifiStates(); + notifyListenersIfNecessary(); + }); } private void handleStatusUpdated() { - Assert.isMainThread(); - copyWifiStates(); - notifyListenersIfNecessary(); + // The WifiStatusTracker callback comes in on the main thread, but the rest of our data + // access happens on the bgHandler + doInBackground(() -> { + copyWifiStates(); + notifyListenersIfNecessary(); + }); + } + + private void doInBackground(Runnable action) { + if (Thread.currentThread() != mBgHandler.getLooper().getThread()) { + mBgHandler.post(action); + } else { + action.run(); + } } private void copyWifiStates() { + // Data access should only happen on our bg thread + Preconditions.checkState(mBgHandler.getLooper().isCurrentThread()); + mCurrentState.enabled = mWifiTracker.enabled; mCurrentState.isDefault = mWifiTracker.isDefaultNetwork; mCurrentState.connected = mWifiTracker.connected; mCurrentState.ssid = mWifiTracker.ssid; mCurrentState.rssi = mWifiTracker.rssi; - notifyWifiLevelChangeIfNecessary(mWifiTracker.level); + boolean levelChanged = mCurrentState.level != mWifiTracker.level; mCurrentState.level = mWifiTracker.level; mCurrentState.statusLabel = mWifiTracker.statusLabel; mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged; @@ -215,11 +230,9 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> mCurrentState.iconGroup = mCurrentState.isCarrierMerged ? mCarrierMergedWifiIconGroup : mUnmergedWifiIconGroup; - } - void notifyWifiLevelChangeIfNecessary(int level) { - if (level != mCurrentState.level) { - mNetworkController.notifyWifiLevelChange(level); + if (levelChanged) { + mNetworkController.notifyWifiLevelChange(mCurrentState.level); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt new file mode 100644 index 000000000000..9dc17d0f50de --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.connectivity + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkScoreManager +import android.net.wifi.WifiManager +import android.os.Handler + +import com.android.settingslib.wifi.WifiStatusTracker +import com.android.systemui.dagger.qualifiers.Main + +import javax.inject.Inject + +/** + * Factory class for [WifiStatusTracker] which lives in SettingsLib (and thus doesn't use Dagger). + * This enables the constructors for NetworkControllerImpl and WifiSignalController to be slightly + * nicer. + */ +internal class WifiStatusTrackerFactory @Inject constructor( + private val mContext: Context, + private val mWifiManager: WifiManager?, + private val mNetworkScoreManager: NetworkScoreManager, + private val mConnectivityManager: ConnectivityManager, + @Main private val mMainHandler: Handler +) { + fun createTracker(callback: Runnable?, bgHandler: Handler?): WifiStatusTracker { + return WifiStatusTracker(mContext, + mWifiManager, + mNetworkScoreManager, + mConnectivityManager, + callback, + mMainHandler, + bgHandler) + } +} 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 4d0feffa1d24..be923a68391c 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.dx.mockito.inline.extended.ExtendedMockito.mockitoSess 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 @@ import java.util.List; @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 @@ public class LockIconViewControllerTest extends SysuiTestCase { 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 @@ public class LockIconViewControllerTest extends SysuiTestCase { @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 @@ public class LockIconViewControllerTest extends SysuiTestCase { 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 @@ public class LockIconViewControllerTest extends SysuiTestCase { 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 b8c85bb41726..94254da0d7de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -124,7 +124,6 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var collapsedSet: ConstraintSet @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory @Mock private lateinit var mediaCarouselController: MediaCarouselController - @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var transitionParent: ViewGroup private lateinit var appIcon: ImageView @@ -270,7 +269,7 @@ public class MediaControlPanelTest : SysuiTestCase() { smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( packageName = PACKAGE, instanceId = instanceId, - recommendations = listOf(smartspaceAction), + recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction), cardAction = smartspaceAction ) } @@ -294,9 +293,6 @@ public class MediaControlPanelTest : SysuiTestCase() { */ private fun initMediaViewHolderMocks() { whenever(seekBarViewModel.progress).thenReturn(seekBarData) - whenever(mediaCarouselController.mediaCarouselScrollHandler) - .thenReturn(mediaCarouselScrollHandler) - whenever(mediaCarouselScrollHandler.qsExpanded).thenReturn(false) // Set up mock views for the players appIcon = ImageView(context) @@ -951,7 +947,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Rebinding should not trigger animation player.bindPlayer(mediaData, PACKAGE) - verify(mockAnimator, times(1)).start() + verify(mockAnimator, times(2)).start() } @Test @@ -973,7 +969,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // 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 @@ -1448,6 +1444,66 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test + fun bindRecommendation_listHasTooFewRecs_notDisplayed() { + player.attachRecommendation(recommendationViewHolder) + val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata) + val data = smartspaceData.copy( + recommendations = listOf( + SmartspaceAction.Builder("id1", "title1") + .setSubtitle("subtitle1") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("subtitle2") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + ) + ) + + player.bindRecommendation(data) + + assertThat(recTitle1.text).isEqualTo("") + verify(mediaViewController, never()).refreshState() + } + + @Test + fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() { + player.attachRecommendation(recommendationViewHolder) + val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata) + val data = smartspaceData.copy( + recommendations = listOf( + SmartspaceAction.Builder("id1", "title1") + .setSubtitle("subtitle1") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("subtitle2") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "empty icon 1") + .setSubtitle("subtitle2") + .setIcon(null) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "empty icon 2") + .setSubtitle("subtitle2") + .setIcon(null) + .setExtras(Bundle.EMPTY) + .build(), + ) + ) + + player.bindRecommendation(data) + + assertThat(recTitle1.text).isEqualTo("") + verify(mediaViewController, never()).refreshState() + } + + @Test fun bindRecommendation_hasTitlesAndSubtitles() { player.attachRecommendation(recommendationViewHolder) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt index 1f9490ab3851..6a532d74967f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt @@ -110,7 +110,7 @@ class MediaDataFilterTest : SysuiTestCase() { `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY) `when`(smartspaceData.isActive).thenReturn(true) - `when`(smartspaceData.isValid).thenReturn(true) + `when`(smartspaceData.isValid()).thenReturn(true) `when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE) `when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem)) `when`(smartspaceData.headphoneConnectionTimeMillis).thenReturn( @@ -196,22 +196,108 @@ class MediaDataFilterTest : SysuiTestCase() { } @Test - fun testHasAnyMediaOrRecommendation() { + fun hasAnyMedia_noMediaSet_returnsFalse() { + assertThat(mediaDataFilter.hasAnyMedia()).isFalse() + } + + @Test + fun hasAnyMedia_mediaSet_returnsTrue() { + mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) + + assertThat(mediaDataFilter.hasAnyMedia()).isTrue() + } + + @Test + fun hasAnyMedia_recommendationSet_returnsFalse() { + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + assertThat(mediaDataFilter.hasAnyMedia()).isFalse() + } + + @Test + fun hasAnyMediaOrRecommendation_noMediaSet_returnsFalse() { assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse() + } + @Test + fun hasAnyMediaOrRecommendation_mediaSet_returnsTrue() { mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) + assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue() - assertThat(mediaDataFilter.hasAnyMedia()).isTrue() } @Test - fun testHasActiveMediaOrRecommendation() { - assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + fun hasAnyMediaOrRecommendation_recommendationSet_returnsTrue() { + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue() + } + + @Test + fun hasActiveMedia_noMediaSet_returnsFalse() { + assertThat(mediaDataFilter.hasActiveMedia()).isFalse() + } + + @Test + fun hasActiveMedia_inactiveMediaSet_returnsFalse() { + val data = dataMain.copy(active = false) + mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) + + assertThat(mediaDataFilter.hasActiveMedia()).isFalse() + } + + @Test + fun hasActiveMedia_activeMediaSet_returnsTrue() { val data = dataMain.copy(active = true) + mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) + assertThat(mediaDataFilter.hasActiveMedia()).isTrue() + } + + @Test + fun hasActiveMediaOrRecommendation_nothingSet_returnsFalse() { + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + } + + @Test + fun hasActiveMediaOrRecommendation_inactiveMediaSet_returnsFalse() { + val data = dataMain.copy(active = false) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) + + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + } + + @Test + fun hasActiveMediaOrRecommendation_activeMediaSet_returnsTrue() { + val data = dataMain.copy(active = true) + mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) + + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() + } + + @Test + fun hasActiveMediaOrRecommendation_inactiveRecommendationSet_returnsFalse() { + `when`(smartspaceData.isActive).thenReturn(false) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + } + + @Test + fun hasActiveMediaOrRecommendation_invalidRecommendationSet_returnsFalse() { + `when`(smartspaceData.isValid()).thenReturn(false) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + } + + @Test + fun hasActiveMediaOrRecommendation_activeAndValidRecommendationSet_returnsTrue() { + `when`(smartspaceData.isActive).thenReturn(true) + `when`(smartspaceData.isValid()).thenReturn(true) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() - assertThat(mediaDataFilter.hasActiveMedia()).isTrue() } @Test @@ -332,7 +418,7 @@ class MediaDataFilterTest : SysuiTestCase() { @Test fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() { - `when`(smartspaceData.isValid).thenReturn(false) + `when`(smartspaceData.isValid()).thenReturn(false) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 333e148475df..e42ae1c2f878 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -7,6 +7,7 @@ import android.app.smartspace.SmartspaceAction import android.app.smartspace.SmartspaceTarget import android.content.Intent import android.graphics.Bitmap +import android.graphics.drawable.Icon import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaController @@ -97,6 +98,7 @@ class MediaDataManagerTest : SysuiTestCase() { lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget @Mock private lateinit var mediaRecommendationItem: SmartspaceAction + lateinit var validRecommendationList: List<SmartspaceAction> @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction @Mock private lateinit var mediaFlags: MediaFlags @Mock private lateinit var logger: MediaUiEventLogger @@ -172,12 +174,17 @@ class MediaDataManagerTest : SysuiTestCase() { putString("package_name", PACKAGE_NAME) putParcelable("dismiss_intent", DISMISS_INTENT) } + val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras) whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction) whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras) + whenever(mediaRecommendationItem.icon).thenReturn(icon) + validRecommendationList = listOf( + mediaRecommendationItem, mediaRecommendationItem, mediaRecommendationItem + ) whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE) whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA) - whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem)) + whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList) whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false) whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId()) @@ -507,10 +514,9 @@ class MediaDataManagerTest : SysuiTestCase() { eq(SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, - isValid = true, packageName = PACKAGE_NAME, cardAction = mediaSmartspaceBaseAction, - recommendations = listOf(mediaRecommendationItem), + recommendations = validRecommendationList, dismissIntent = DISMISS_INTENT, headphoneConnectionTimeMillis = 1234L, instanceId = InstanceId.fakeInstanceId(instanceId))), @@ -529,7 +535,6 @@ class MediaDataManagerTest : SysuiTestCase() { eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, - isValid = false, dismissIntent = DISMISS_INTENT, headphoneConnectionTimeMillis = 1234L, instanceId = InstanceId.fakeInstanceId(instanceId))), @@ -555,7 +560,6 @@ class MediaDataManagerTest : SysuiTestCase() { eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, - isValid = false, dismissIntent = null, headphoneConnectionTimeMillis = 1234L, instanceId = InstanceId.fakeInstanceId(instanceId))), 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 52cb902a4f38..311aa9649911 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 @@ package com.android.systemui.media 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 @@ class MetadataAnimationHandlerTest : SysuiTestCase() { 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 @@ class MetadataAnimationHandlerTest : SysuiTestCase() { @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 @@ class MetadataAnimationHandlerTest : SysuiTestCase() { 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 @@ class MetadataAnimationHandlerTest : SysuiTestCase() { 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 @@ class MetadataAnimationHandlerTest : SysuiTestCase() { fun enterAnimatorEndsWithoutCallback_noAnimatiorStart() { handler.onAnimationEnd(enterAnimator) - verify(animatorSet, never()).start() + verify(exitAnimator, never()).start() + verify(enterAnimator, never()).start() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt new file mode 100644 index 000000000000..b5078bc37b84 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt @@ -0,0 +1,108 @@ +package com.android.systemui.media + +import android.app.smartspace.SmartspaceAction +import android.graphics.drawable.Icon +import androidx.test.filters.SmallTest +import com.android.internal.logging.InstanceId +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +@SmallTest +class SmartspaceMediaDataTest : SysuiTestCase() { + + private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play) + + @Test + fun getValidRecommendations_onlyReturnsRecsWithIcons() { + val withIcon1 = SmartspaceAction.Builder("id", "title").setIcon(icon).build() + val withIcon2 = SmartspaceAction.Builder("id", "title").setIcon(icon).build() + val withoutIcon1 = SmartspaceAction.Builder("id", "title").setIcon(null).build() + val withoutIcon2 = SmartspaceAction.Builder("id", "title").setIcon(null).build() + val recommendations = listOf(withIcon1, withoutIcon1, withIcon2, withoutIcon2) + + val data = DEFAULT_DATA.copy(recommendations = recommendations) + + assertThat(data.getValidRecommendations()).isEqualTo(listOf(withIcon1, withIcon2)) + } + + @Test + fun isValid_emptyList_returnsFalse() { + val data = DEFAULT_DATA.copy(recommendations = listOf()) + + assertThat(data.isValid()).isFalse() + } + + @Test + fun isValid_tooFewRecs_returnsFalse() { + val data = DEFAULT_DATA.copy( + recommendations = listOf( + SmartspaceAction.Builder("id", "title").setIcon(icon).build() + ) + ) + + assertThat(data.isValid()).isFalse() + } + + @Test + fun isValid_tooFewRecsWithIcons_returnsFalse() { + val recommendations = mutableListOf<SmartspaceAction>() + // Add one fewer recommendation w/ icon than the number required + for (i in 1 until NUM_REQUIRED_RECOMMENDATIONS) { + recommendations.add( + SmartspaceAction.Builder("id", "title").setIcon(icon).build() + ) + } + for (i in 1 until 3) { + recommendations.add( + SmartspaceAction.Builder("id", "title").setIcon(null).build() + ) + } + + val data = DEFAULT_DATA.copy(recommendations = recommendations) + + assertThat(data.isValid()).isFalse() + } + + @Test + fun isValid_enoughRecsWithIcons_returnsTrue() { + val recommendations = mutableListOf<SmartspaceAction>() + // Add the number of required recommendations + for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS) { + recommendations.add( + SmartspaceAction.Builder("id", "title").setIcon(icon).build() + ) + } + + val data = DEFAULT_DATA.copy(recommendations = recommendations) + + assertThat(data.isValid()).isTrue() + } + + @Test + fun isValid_manyRecsWithIcons_returnsTrue() { + val recommendations = mutableListOf<SmartspaceAction>() + // Add more than enough recommendations + for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS + 3) { + recommendations.add( + SmartspaceAction.Builder("id", "title").setIcon(icon).build() + ) + } + + val data = DEFAULT_DATA.copy(recommendations = recommendations) + + assertThat(data.isValid()).isTrue() + } +} + +private val DEFAULT_DATA = SmartspaceMediaData( + targetId = "INVALID", + isActive = false, + packageName = "INVALID", + cardAction = null, + recommendations = emptyList(), + dismissIntent = null, + headphoneConnectionTimeMillis = 0, + instanceId = InstanceId.fakeInstanceId(-1) +) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index 2fe7c075bc18..e01ebbdda374 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -127,6 +127,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); protected Handler mMainHandler; protected FeatureFlags mFeatureFlags; + protected WifiStatusTrackerFactory mWifiStatusTrackerFactory; protected int mSubId; @@ -220,12 +221,14 @@ public class NetworkControllerBaseTest extends SysuiTestCase { return null; }).when(mMockProvisionController).addCallback(any()); + mWifiStatusTrackerFactory = new WifiStatusTrackerFactory( + mContext, mMockWm, mMockNsm, mMockCm, mMainHandler); + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mTelephonyListenerManager, mMockWm, - mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), @@ -238,6 +241,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mMockBd, mDemoModeController, mCarrierConfigTracker, + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java index ccfa1b31b799..3a0c203f76e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java @@ -127,11 +127,13 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { mConfig.show4gForLte = true; mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mTelephonyListenerManager, mMockWm, - mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler, + mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController, - mock(CarrierConfigTracker.class), new Handler(TestableLooper.get(this).getLooper()), + mock(CarrierConfigTracker.class), + mWifiStatusTrackerFactory, + new Handler(TestableLooper.get(this).getLooper()), mFeatureFlags, mock(DumpManager.class)); setupNetworkController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java index b84750aa7ea5..ae1b3d1e1f42 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java @@ -71,7 +71,6 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockTm, mTelephonyListenerManager, mMockWm, - mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), @@ -84,6 +83,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockBd, mDemoModeController, mCarrierConfigTracker, + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class) @@ -105,7 +105,6 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockTm, mTelephonyListenerManager, mMockWm, - mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), @@ -118,6 +117,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockBd, mDemoModeController, mCarrierConfigTracker, + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class) @@ -134,11 +134,12 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { when(mMockTm.isDataCapable()).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, - mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + mTelephonyListenerManager, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController, mock(CarrierConfigTracker.class), + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class)); setupNetworkController(); @@ -156,11 +157,12 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { when(mMockSm.getCompleteActiveSubscriptionInfoList()).thenReturn(Collections.emptyList()); mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, - mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + mTelephonyListenerManager, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController, mock(CarrierConfigTracker.class), + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class)); mNetworkController.registerListeners(); @@ -225,11 +227,12 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { when(mMockTm.isDataCapable()).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, - mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + mTelephonyListenerManager, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController, mock(CarrierConfigTracker.class), + mWifiStatusTrackerFactory, mMainHandler, mFeatureFlags, mock(DumpManager.class)); setupNetworkController(); diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 877ee8218ea6..fa52ac905a43 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -500,6 +500,7 @@ public class BinaryTransparencyService extends SystemService { // 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 @@ public class BinaryTransparencyService extends SystemService { // 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 @@ public class BinaryTransparencyService extends SystemService { // 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 @@ public class BinaryTransparencyService extends SystemService { // 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 91f6eeb875f6..7c6ccc95664e 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 3e5786eaa333..402491d8fe80 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 @@ final class ActivityManagerShellCommand extends ShellCommand { 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 @@ final class ActivityManagerShellCommand extends ShellCommand { 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 @@ final class ActivityManagerShellCommand extends ShellCommand { 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 ff569a681a4e..a172018ab291 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 @@ public final class CachedAppOptimizer { @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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { } @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 @@ public final class CachedAppOptimizer { } @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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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,11 +1146,24 @@ public final class CachedAppOptimizer { 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(); + 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); } - cancelCompaction(); + mPendingCompactionProcesses.clear(); } + cancelCompaction(); } @GuardedBy({"mService", "mProcLock"}) @@ -1114,13 +1179,48 @@ public final class CachedAppOptimizer { // 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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?"); + if (!forceCompaction) { + if (shouldOomAdjThrottleCompaction(proc, resolvedAction)) { + return; } - 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 (shouldTimeThrottleCompaction(proc, start, requestedAction)) { 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 (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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { * 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 a86ba016eeff..a613729b441f 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 @@ final class ProcessCachedOptimizerRecord { @GuardedBy("mProcLock") private boolean mPendingCompact; + @GuardedBy("mProcLock") private boolean mForceCompact; + /** * True when the process is frozen. */ @@ -133,6 +135,16 @@ final class ProcessCachedOptimizerRecord { } @GuardedBy("mProcLock") + boolean isForceCompact() { + return mForceCompact; + } + + @GuardedBy("mProcLock") + void setForceCompact(boolean forceCompact) { + mForceCompact = forceCompact; + } + + @GuardedBy("mProcLock") boolean isFrozen() { return mFrozen; } @@ -205,6 +217,9 @@ final class ProcessCachedOptimizerRecord { 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/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 04fcda7835fa..122a950e4fd3 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -242,15 +242,19 @@ public class SpatializerHelper { mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i)); } } + // for both transaural / binaural, we are not forcing enablement as the init() method + // could have been called another time after boot in case of audioserver restart if (mTransauralSupported) { // TODO deal with persisted values - mSADevices.add( - new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null)); + addCompatibleAudioDevice( + new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""), + false /*forceEnable*/); } if (mBinauralSupported) { // TODO deal with persisted values - mSADevices.add( - new SADeviceState(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, null)); + addCompatibleAudioDevice( + new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""), + false /*forceEnable*/); } // TODO read persisted states } catch (RemoteException e) { @@ -461,6 +465,20 @@ public class SpatializerHelper { } synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { + addCompatibleAudioDevice(ada, true /*forceEnable*/); + } + + /** + * Add the given device to the list of devices for which spatial audio will be available + * (== possible). + * @param ada the compatible device + * @param forceEnable if true, spatial audio is enabled for this device, regardless of whether + * this device was already in the list. If false, the enabled field is only + * set to true if the device is added to the list, otherwise, if already + * present, the setting is left untouched. + */ + private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada, + boolean forceEnable) { loglogi("addCompatibleAudioDevice: dev=" + ada); final int deviceType = ada.getType(); final boolean wireless = isWireless(deviceType); @@ -471,7 +489,9 @@ public class SpatializerHelper { && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) || !wireless) { isInList = true; - deviceState.mEnabled = true; + if (forceEnable) { + deviceState.mEnabled = true; + } break; } } @@ -508,7 +528,7 @@ public class SpatializerHelper { private synchronized Pair<Boolean, Boolean> evaluateState(AudioDeviceAttributes ada) { // if not a wireless device, this value will be overwritten to map the type // to TYPE_BUILTIN_SPEAKER or TYPE_WIRED_HEADPHONES - int deviceType = ada.getType(); + @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType(); final boolean wireless = isWireless(deviceType); // if not a wireless device: find if media device is in the speaker, wired headphones @@ -1468,13 +1488,13 @@ public class SpatializerHelper { } private static final class SADeviceState { - final int mDeviceType; + final @AudioDeviceInfo.AudioDeviceType int mDeviceType; final @Nullable String mDeviceAddress; // non-null for wireless devices boolean mEnabled = true; // by default, SA is enabled on any device boolean mHasHeadTracker = false; boolean mHeadTrackerEnabled = true; // by default, if head tracker is present, use it - SADeviceState(int deviceType, @Nullable String address) { + SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) { mDeviceType = deviceType; mDeviceAddress = address; } @@ -1505,7 +1525,7 @@ public class SpatializerHelper { } } - private static boolean isWireless(int deviceType) { + private static boolean isWireless(@AudioDeviceInfo.AudioDeviceType int deviceType) { for (int type : WIRELESS_TYPES) { if (type == deviceType) { return true; @@ -1514,7 +1534,7 @@ public class SpatializerHelper { return false; } - private static boolean isWirelessSpeaker(int deviceType) { + private static boolean isWirelessSpeaker(@AudioDeviceInfo.AudioDeviceType int deviceType) { for (int type : WIRELESS_SPEAKER_TYPES) { if (type == deviceType) { return true; diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 4822ddbc0ebb..f21f90631792 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 @@ class ActivityClientController extends IActivityClientController.Stub { 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 41b724f2596f..8f689be48c21 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.PendingIntent; 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 43d077664fd5..1ddeee9f46d9 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_WITH_CLASS_NAME 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.view.SurfaceControl; 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 @@ class PinnedTaskController { 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 @@ class PinnedTaskController { 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 @@ class PinnedTaskController { } /** - * 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 @@ class PinnedTaskController { } } - 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 @@ class PinnedTaskController { } 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 00e61171cb68..718ce2870f10 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.ActivityTaskManager; 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.io.PrintWriter; 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 @@ class Task extends TaskFragment { } } - /** - * 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 636ca4143a33..49a4021b1a84 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 @@ using android::base::unique_fd; // 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 @@ static inline void compactProcessProcfs(int pid, const std::string& compactionTy // 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 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT // 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; - } - - uint64_t vmaStart = vmas[iVma].start + vmaOffset; - uint64_t vmaSize = vmas[iVma].end - vmaStart; - if (vmaSize == 0) { - goto next_vma; - } - 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; + ATRACE_INSTANT_FOR_TRACK(ATRACE_COMPACTION_TRACK, + StringPrintf("Cancelled compaction for %d", pid).c_str()); + return ERROR_COMPACTION_CANCELLED; } - - 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; + int error = madviseVmasFromBatch(pidfd, batch, madviseType, &bytesProcessedInSend); + if (error < 0) { + // Returns standard linux errno code + return error; } - - 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 @@ static int getAnyPageAdvice(const Vma& vma) { // // 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 @@ static int64_t compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) { 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_compactSystem(JNIEnv *, job } 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 @@ static const JNINativeMethod sMethods[] = { /* 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 90fd8edacce3..8aa9f60e922d 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.BroadcastReceiver; 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 @@ public class MidiService extends IMidiManager.Stub { }; 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 class MidiService extends IMidiManager.Stub { } 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 @@ public class MidiService extends IMidiManager.Stub { } 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 @@ public class MidiService extends IMidiManager.Stub { } 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 class MidiService extends IMidiManager.Stub { } 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 @@ public class MidiService extends IMidiManager.Stub { // 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 class MidiService extends IMidiManager.Stub { } public void notifyClient(IMidiDeviceServer deviceServer) { + Log.d(TAG, "notifyClient"); + if (mCallback != null) { try { mCallback.onDeviceOpened(deviceServer, (deviceServer == null ? null : mToken)); @@ -628,7 +643,9 @@ public class MidiService extends IMidiManager.Stub { @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 @@ public class MidiService extends IMidiManager.Stub { } 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 @@ public class MidiService extends IMidiManager.Stub { } 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 @@ public class MidiService extends IMidiManager.Stub { } 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 @@ public class MidiService extends IMidiManager.Stub { } } 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 @@ public class MidiService extends IMidiManager.Stub { 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 @@ public class MidiService extends IMidiManager.Stub { 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 class MidiService extends IMidiManager.Stub { 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 @@ public class MidiService extends IMidiManager.Stub { // 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 @@ public class MidiService extends IMidiManager.Stub { @Override public void onDeviceOpened(MidiDevice device) { synchronized (mBleMidiDeviceMap) { + Log.i(TAG, "onDeviceOpened() device:" + device); mBleMidiDeviceMap.put(bluetoothDevice, device); } } @@ -949,6 +1008,7 @@ public class MidiService extends IMidiManager.Stub { // 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 @@ public class MidiService extends IMidiManager.Stub { 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 89b204b9c999..f454ac7e9e4b 100644 --- a/services/tests/mockingservicestests/jni/Android.bp +++ b/services/tests/mockingservicestests/jni/Android.bp @@ -44,6 +44,7 @@ cc_library_shared { "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 bf46f555004c..2baa1ec6cdc2 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 @@ public final class CachedAppOptimizerTest { .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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { // 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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); |