diff options
106 files changed, 3830 insertions, 490 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 49011b4285fb..9cbf12f7d511 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1185,6 +1185,7 @@ package android { field public static final int right = 16843183; // 0x10101af field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093 field public static final int ringtoneType = 16843257; // 0x10101f9 + field public static final int rippleStyle = 16844337; // 0x1010631 field public static final int rollbackDataPolicy = 16844311; // 0x1010617 field public static final int rotation = 16843558; // 0x1010326 field public static final int rotationAnimation = 16844090; // 0x101053a @@ -8308,6 +8309,7 @@ package android.appwidget { public class AppWidgetHostView extends android.widget.FrameLayout { ctor public AppWidgetHostView(android.content.Context); ctor public AppWidgetHostView(android.content.Context, int, int); + method public void clearCurrentSize(); method public int getAppWidgetId(); method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(); method public static android.graphics.Rect getDefaultPaddingForWidget(android.content.Context, android.content.ComponentName, android.graphics.Rect); @@ -8315,11 +8317,13 @@ package android.appwidget { method protected android.view.View getErrorView(); method protected void prepareView(android.view.View); method public void setAppWidget(int, android.appwidget.AppWidgetProviderInfo); + method public void setCurrentSize(@NonNull android.graphics.PointF); method public void setExecutor(java.util.concurrent.Executor); method public void setOnLightBackground(boolean); method public void updateAppWidget(android.widget.RemoteViews); method public void updateAppWidgetOptions(android.os.Bundle); - method public void updateAppWidgetSize(android.os.Bundle, int, int, int, int); + method @Deprecated public void updateAppWidgetSize(android.os.Bundle, int, int, int, int); + method public void updateAppWidgetSize(@NonNull android.os.Bundle, @NonNull java.util.List<android.graphics.PointF>); } public class AppWidgetManager { @@ -8372,6 +8376,7 @@ package android.appwidget { field public static final String OPTION_APPWIDGET_MIN_HEIGHT = "appWidgetMinHeight"; field public static final String OPTION_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth"; field public static final String OPTION_APPWIDGET_RESTORE_COMPLETED = "appWidgetRestoreCompleted"; + field public static final String OPTION_APPWIDGET_SIZES = "appWidgetSizes"; } public class AppWidgetProvider extends android.content.BroadcastReceiver { @@ -16533,9 +16538,13 @@ package android.graphics.drawable { public class RippleDrawable extends android.graphics.drawable.LayerDrawable { ctor public RippleDrawable(@NonNull android.content.res.ColorStateList, @Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable); method public int getRadius(); + method public int getRippleStyle(); method public void setColor(android.content.res.ColorStateList); method public void setRadius(int); + method public void setRippleStyle(int) throws java.lang.IllegalArgumentException; field public static final int RADIUS_AUTO = -1; // 0xffffffff + field public static final int STYLE_PATTERNED = 1; // 0x1 + field public static final int STYLE_SOLID = 0; // 0x0 } public class RotateDrawable extends android.graphics.drawable.DrawableWrapper { @@ -54644,6 +54653,7 @@ package android.widget { public class RemoteViews implements android.view.LayoutInflater.Filter android.os.Parcelable { ctor public RemoteViews(String, int); ctor public RemoteViews(android.widget.RemoteViews, android.widget.RemoteViews); + ctor public RemoteViews(@NonNull java.util.Map<android.graphics.PointF,android.widget.RemoteViews>); ctor public RemoteViews(android.widget.RemoteViews); ctor public RemoteViews(android.os.Parcel); method public void addView(@IdRes int, android.widget.RemoteViews); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f9c89626661a..f330a9355cfb 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -27,6 +27,7 @@ package android { field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; field public static final String BACKUP = "android.permission.BACKUP"; + field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION"; field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE"; field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"; field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; @@ -343,6 +344,7 @@ package android { field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028 field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029 field public static final int config_systemGallery = 17039399; // 0x1040027 + field public static final int config_systemShell = 17039402; // 0x104002a } public static final class R.style { @@ -1852,6 +1854,7 @@ package android.bluetooth { } public final class BluetoothDevice implements android.os.Parcelable { + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean canBondWithoutDialog(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean cancelBondProcess(); method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission(); @@ -2849,7 +2852,8 @@ package android.graphics.fonts { public class FontManager { method @Nullable public android.text.FontConfig getFontConfig(); method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int); - method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int); + method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.graphics.fonts.FontFileUpdateRequest, @IntRange(from=0) int); + method @Deprecated @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int); field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa @@ -8563,7 +8567,7 @@ package android.os { method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig); - method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.BATTERY_PREDICTION, android.Manifest.permission.DEVICE_POWER}) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean); method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setFullPowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2a9b31833087..86fe8c30ca22 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -838,7 +838,8 @@ package android.graphics.fonts { public class FontManager { method @Nullable public android.text.FontConfig getFontConfig(); method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int); - method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int); + method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.graphics.fonts.FontFileUpdateRequest, @IntRange(from=0) int); + method @Deprecated @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int); field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/app/ILocalWallpaperColorConsumer.aidl new file mode 100644 index 000000000000..28b11ec04d96 --- /dev/null +++ b/core/java/android/app/ILocalWallpaperColorConsumer.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.app.WallpaperColors; +import android.graphics.RectF; + +/** + * @hide + */ +oneway interface ILocalWallpaperColorConsumer { + void onColorsChanged(in RectF area, in WallpaperColors colors); +}
\ No newline at end of file diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index 101917bc2e07..5402381b7207 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -17,9 +17,11 @@ package android.app; import android.graphics.Rect; +import android.graphics.RectF; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.app.IWallpaperManagerCallback; +import android.app.ILocalWallpaperColorConsumer; import android.app.WallpaperInfo; import android.content.ComponentName; import android.app.WallpaperColors; @@ -162,6 +164,18 @@ interface IWallpaperManager { WallpaperColors getWallpaperColors(int which, int userId, int displayId); /** + * @hide + */ + void removeOnLocalColorsChangedListener( + in ILocalWallpaperColorConsumer callback, int which, int userId, int displayId); + + /** + * @hide + */ + void addOnLocalColorsChangedListener(in ILocalWallpaperColorConsumer callback, + in List<RectF> regions, int which, int userId, int displayId); + + /** * Register a callback to receive color updates from a display */ void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId, int displayId); diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index ac2f22357013..d7587bdce997 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -66,6 +66,7 @@ import android.os.RemoteException; import android.os.StrictMode; import android.os.SystemProperties; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.view.Display; @@ -107,6 +108,8 @@ public class WallpaperManager { private static boolean DEBUG = false; private float mWallpaperXStep = -1; private float mWallpaperYStep = -1; + private static final @NonNull RectF LOCAL_COLOR_BOUNDS = + new RectF(0, 0, 1, 1); /** {@hide} */ private static final String PROP_WALLPAPER = "ro.config.wallpaper"; @@ -309,6 +312,8 @@ public class WallpaperManager { private int mCachedWallpaperUserId; private Bitmap mDefaultWallpaper; private Handler mMainLooperHandler; + private ArrayMap<LocalWallpaperColorConsumer, ILocalWallpaperColorConsumer> + mLocalColorCallbacks = new ArrayMap<>(); Globals(IWallpaperManager service, Looper looper) { mService = service; @@ -350,6 +355,40 @@ public class WallpaperManager { } } + private ILocalWallpaperColorConsumer wrap(LocalWallpaperColorConsumer callback) { + ILocalWallpaperColorConsumer callback2 = new ILocalWallpaperColorConsumer.Stub() { + @Override + public void onColorsChanged(RectF area, WallpaperColors colors) { + callback.onColorsChanged(area, colors); + } + }; + mLocalColorCallbacks.put(callback, callback2); + return callback2; + } + + public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback, + @NonNull List<RectF> regions, int which, int userId, int displayId) { + try { + mService.addOnLocalColorsChangedListener(wrap(callback) , regions, which, + userId, displayId); + } catch (RemoteException e) { + // Can't get colors, connection lost. + } + } + + public void removeOnColorsChangedListener( + @NonNull LocalWallpaperColorConsumer callback, int which, int userId, + int displayId) { + ILocalWallpaperColorConsumer callback2 = mLocalColorCallbacks.remove(callback); + if (callback2 == null) return; + try { + mService.removeOnLocalColorsChangedListener( + callback2, which, userId, displayId); + } catch (RemoteException e) { + // Can't get colors, connection lost. + } + } + /** * Stop listening to wallpaper color events. * @@ -1057,6 +1096,29 @@ public class WallpaperManager { } /** + * @hide + */ + public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback, + List<RectF> regions) throws IllegalArgumentException { + for (RectF region : regions) { + if (!LOCAL_COLOR_BOUNDS.contains(region)) { + throw new IllegalArgumentException("Regions must be within bounds " + + LOCAL_COLOR_BOUNDS); + } + } + sGlobals.addOnColorsChangedListener(callback, regions, FLAG_SYSTEM, + mContext.getUserId(), mContext.getDisplayId()); + } + + /** + * @hide + */ + public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) { + sGlobals.removeOnColorsChangedListener(callback, FLAG_SYSTEM, mContext.getUserId(), + mContext.getDisplayId()); + } + + /** * Version of {@link #getWallpaperFile(int)} that can access the wallpaper data * for a given user. The caller must hold the INTERACT_ACROSS_USERS_FULL * permission to access another user's wallpaper data. @@ -2202,4 +2264,18 @@ public class WallpaperManager { onColorsChanged(colors, which); } } + + /** + * Callback to update a consumer with a local color change + * @hide + */ + public interface LocalWallpaperColorConsumer { + + /** + * Gets called when a color of an area gets updated + * @param area + * @param colors + */ + void onColorsChanged(RectF area, WallpaperColors colors); + } } diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index a3c3a0e106a3..42d90a794e74 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -16,6 +16,7 @@ package android.appwidget; +import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityOptions; import android.compat.annotation.UnsupportedAppUsage; @@ -29,6 +30,7 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.PointF; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; @@ -53,6 +55,7 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; /** @@ -89,6 +92,7 @@ public class AppWidgetHostView extends FrameLayout { int mLayoutId = -1; private OnClickHandler mOnClickHandler; private boolean mOnLightBackground; + PointF mCurrentSize = null; private Executor mAsyncExecutor; private CancellationSignal mLastExecutionSignal; @@ -268,7 +272,8 @@ public class AppWidgetHostView extends FrameLayout { * Provide guidance about the size of this widget to the AppWidgetManager. The widths and * heights should correspond to the full area the AppWidgetHostView is given. Padding added by * the framework will be accounted for automatically. This information gets embedded into the - * AppWidget options and causes a callback to the AppWidgetProvider. + * AppWidget options and causes a callback to the AppWidgetProvider. In addition, the list of + * sizes is explicitly set to an empty list. * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) * * @param newOptions The bundle of options, in addition to the size information, @@ -277,14 +282,97 @@ public class AppWidgetHostView extends FrameLayout { * @param minHeight The maximum height in dips that the widget will be displayed at. * @param maxWidth The maximum width in dips that the widget will be displayed at. * @param maxHeight The maximum height in dips that the widget will be displayed at. - * + * @deprecated use {@link AppWidgetHostView#updateAppWidgetSize(Bundle, List)} instead. */ + @Deprecated public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight) { updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false); } /** + * Provide guidance about the size of this widget to the AppWidgetManager. The sizes should + * correspond to the full area the AppWidgetHostView is given. Padding added by the framework + * will be accounted for automatically. + * + * This method will update the option bundle with the list of sizes and the min/max bounds for + * width and height. + * + * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) + * + * @param newOptions The bundle of options, in addition to the size information. + * @param sizes Sizes, in dips, the widget may be displayed at without calling the provider + * again. Typically, this will be size of the widget in landscape and portrait. + * On some foldables, this might include the size on the outer and inner screens. + */ + public void updateAppWidgetSize(@NonNull Bundle newOptions, @NonNull List<PointF> sizes) { + AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext); + + Rect padding = getDefaultPadding(); + float density = getResources().getDisplayMetrics().density; + + float xPaddingDips = (padding.left + padding.right) / density; + float yPaddingDips = (padding.top + padding.bottom) / density; + + ArrayList<PointF> paddedSizes = new ArrayList<>(sizes.size()); + float minWidth = Float.MAX_VALUE; + float maxWidth = 0; + float minHeight = Float.MAX_VALUE; + float maxHeight = 0; + for (int i = 0; i < sizes.size(); i++) { + PointF size = sizes.get(i); + PointF paddedPoint = new PointF(Math.max(0.f, size.x - xPaddingDips), + Math.max(0.f, size.y - yPaddingDips)); + paddedSizes.add(paddedPoint); + minWidth = Math.min(minWidth, paddedPoint.x); + maxWidth = Math.max(maxWidth, paddedPoint.x); + minHeight = Math.min(minHeight, paddedPoint.y); + maxHeight = Math.max(maxHeight, paddedPoint.y); + } + if (paddedSizes.equals( + widgetManager.getAppWidgetOptions(mAppWidgetId).<PointF>getParcelableArrayList( + AppWidgetManager.OPTION_APPWIDGET_SIZES))) { + return; + } + Bundle options = newOptions.deepCopy(); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, (int) minWidth); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, (int) minHeight); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, (int) maxWidth); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, (int) maxHeight); + options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes); + updateAppWidgetOptions(options); + } + + /** + * Set the current size of the widget. This should be the full area the AppWidgetHostView is + * given. Padding added by the framework will be accounted for automatically. + * + * This size will be used to choose the appropriate layout the next time the {@link RemoteViews} + * is re-inflated, if it was created with {@link RemoteViews#RemoteViews(Map)} . + */ + public void setCurrentSize(@NonNull PointF size) { + Rect padding = getDefaultPadding(); + float density = getResources().getDisplayMetrics().density; + float xPaddingDips = (padding.left + padding.right) / density; + float yPaddingDips = (padding.top + padding.bottom) / density; + PointF newSize = new PointF(size.x - xPaddingDips, size.y - yPaddingDips); + if (!newSize.equals(mCurrentSize)) { + mCurrentSize = newSize; + mLayoutId = -1; // Prevents recycling the view. + } + } + + /** + * Clear the current size, indicating it is not currently known. + */ + public void clearCurrentSize() { + if (mCurrentSize != null) { + mCurrentSize = null; + mLayoutId = -1; + } + } + + /** * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -322,6 +410,8 @@ public class AppWidgetHostView extends FrameLayout { newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight); newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth); newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight); + newOptions.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, + new ArrayList<PointF>()); updateAppWidgetOptions(newOptions); } } @@ -440,7 +530,7 @@ public class AppWidgetHostView extends FrameLayout { // Try normal RemoteView inflation if (content == null) { try { - content = remoteViews.apply(mContext, this, mOnClickHandler); + content = remoteViews.apply(mContext, this, mOnClickHandler, mCurrentSize); if (LOGD) Log.d(TAG, "had to inflate new layout"); } catch (RuntimeException e) { exception = e; @@ -492,7 +582,8 @@ public class AppWidgetHostView extends FrameLayout { mView, mAsyncExecutor, new ViewApplyListener(remoteViews, layoutId, true), - mOnClickHandler); + mOnClickHandler, + mCurrentSize); } catch (Exception e) { // Reapply failed. Try apply } @@ -502,7 +593,8 @@ public class AppWidgetHostView extends FrameLayout { this, mAsyncExecutor, new ViewApplyListener(remoteViews, layoutId, false), - mOnClickHandler); + mOnClickHandler, + mCurrentSize); } } @@ -533,7 +625,8 @@ public class AppWidgetHostView extends FrameLayout { AppWidgetHostView.this, mAsyncExecutor, new ViewApplyListener(mViews, mLayoutId, false), - mOnClickHandler); + mOnClickHandler, + mCurrentSize); } else { applyContent(null, false, e); } diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 37093a10b2f0..aac8710e8691 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -217,6 +217,12 @@ public class AppWidgetManager { public static final String OPTION_APPWIDGET_MAX_HEIGHT = "appWidgetMaxHeight"; /** + * A bundle extra ({@code List<PointF>}) that contains the list of possible sizes, in dips, a + * widget instance can take. + */ + public static final String OPTION_APPWIDGET_SIZES = "appWidgetSizes"; + + /** * A bundle extra that hints to the AppWidgetProvider the category of host that owns this * this widget. Can have the value {@link * AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN} or {@link diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index ea7e5ea7c802..ec46da0dcf0e 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1654,7 +1654,7 @@ public final class BluetoothAdapter { mContext = context; } - private String getOpPackageName() { + String getOpPackageName() { // Workaround for legacy API for getting a BluetoothAdapter not // passing a context if (mContext != null) { diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 3b8dec7bf955..e7661dbad749 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -1236,7 +1236,8 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.createBond(this, transport, oobData); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + return service.createBond(this, transport, oobData, adapter.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1394,6 +1395,31 @@ public final class BluetoothDevice implements Parcelable { } /** + * Checks whether this bluetooth device is associated with CDM and meets the criteria to skip + * the bluetooth pairing dialog because it has been already consented by the CDM prompt. + * + * @return true if we can bond without the dialog, false otherwise + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean canBondWithoutDialog() { + final IBluetooth service = sService; + if (service == null) { + Log.e(TAG, "BT not enabled. Cannot check if we can skip pairing dialog"); + return false; + } + try { + if (DBG) Log.d(TAG, "canBondWithoutDialog, device: " + this); + return service.canBondWithoutDialog(this); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return false; + } + + /** * Returns whether there is an open connection to this device. * * @return True if there is at least one open connection to this device. diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java index f087dd075816..e512cf1bbb1f 100644 --- a/core/java/android/graphics/fonts/FontManager.java +++ b/core/java/android/graphics/fonts/FontManager.java @@ -208,42 +208,40 @@ public class FontManager { } /** - * Update system installed font file. + * Update a system installed font file. * * <p> - * To protect devices, system font updater relies on the Linux Kernel feature called fs-verity. - * If the device is not ready for fs-verity, {@link #RESULT_ERROR_FONT_UPDATER_DISABLED} will be + * To protect devices, system font updater relies on a Linux Kernel feature called fs-verity. + * If the device does not support fs-verity, {@link #RESULT_ERROR_FONT_UPDATER_DISABLED} will be * returned. * - * Android only accepts OpenType compliant font files. If other font files are provided, + * <p>Android only accepts OpenType compliant font files. If other font files are provided, * {@link #RESULT_ERROR_INVALID_FONT_FILE} will be returned. * - * The font file to be updated is identified by PostScript name stored in name table. If the - * font file doesn't have PostScript name entry, {@link #RESULT_ERROR_INVALID_FONT_NAME} will be - * returned. + * <p>The font file to be updated is identified by PostScript name stored in the name table. If + * the font file doesn't have PostScript name entry, {@link #RESULT_ERROR_INVALID_FONT_NAME} + * will be returned. * - * The entire font file is verified with the given signature for the system installed - * certificate. If the system cannot verify the font contents, + * <p>The entire font file is verified with the given signature using system installed + * certificates. If the system cannot verify the font file contents, * {@link #RESULT_ERROR_VERIFICATION_FAILURE} will be returned. * - * The font file must have newer or equal revision number in the head table. In other words, the - * downgrading font file is not allowed. If the older font file is provided, + * <p>The font file must have a newer revision number in the head table. In other words, it is + * not allowed to downgrade a font file. If an older font file is provided, * {@link #RESULT_ERROR_DOWNGRADING} will be returned. * - * The caller must specify the base config version for keeping consist system configuration. If - * the system configuration is updated for some reason between you get config with - * {@link #getFontConfig()} and calling this method, {@link #RESULT_ERROR_VERSION_MISMATCH} will - * be returned. Get the latest font configuration by calling {@link #getFontConfig()} again and - * try with the latest config version again. - * - * @param pfd A file descriptor of the font file. - * @param signature A PKCS#7 detached signature for verifying entire font files. - * @param baseVersion A base config version to be updated. You can get latest config version by - * {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If the - * system has newer config version, the update will fail with - * {@link #RESULT_ERROR_VERSION_MISMATCH}. Try to get the latest config and - * try update again. - * @return result code. + * <p>The caller must specify the base config version for keeping the font configuration + * consistent. If the font configuration is updated for some reason between the time you get + * a configuration with {@link #getFontConfig()} and the time when you call this method, + * {@link #RESULT_ERROR_VERSION_MISMATCH} will be returned. Get the latest font configuration by + * calling {@link #getFontConfig()} and call this method again with the latest config version. + * + * @param request A {@link FontFileUpdateRequest} to execute. + * @param baseVersion A base config version to be updated. You can get the latest config version + * by {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If + * the system has a newer config version, the update will fail with + * {@link #RESULT_ERROR_VERSION_MISMATCH}. + * @return A result code. * * @see FontConfig#getConfigVersion() * @see #getFontConfig() @@ -258,18 +256,30 @@ public class FontManager { * @see #RESULT_ERROR_FONT_UPDATER_DISABLED */ @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile( - @NonNull ParcelFileDescriptor pfd, - @NonNull byte[] signature, - @IntRange(from = 0) int baseVersion - ) { + @NonNull FontFileUpdateRequest request, @IntRange(from = 0) int baseVersion) { try { - return mIFontManager.updateFontFile(new FontUpdateRequest(pfd, signature), baseVersion); + return mIFontManager.updateFontFile(new FontUpdateRequest( + request.getParcelFileDescriptor(), request.getSignature()), baseVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** + * @deprecated Use {@link #updateFontFile(FontFileUpdateRequest, int)} + */ + // TODO: Remove this API before Developer Preview 3. + @Deprecated + @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile( + @NonNull ParcelFileDescriptor pfd, + @NonNull byte[] signature, + @IntRange(from = 0) int baseVersion + ) { + return updateFontFile(new FontFileUpdateRequest(pfd, signature), baseVersion); + } + + + /** * Update or add system wide font families. * * <p>This method will update existing font families or add new font families. The updated diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java index 3bc0f9ca4e6a..b172ccc4e370 100644 --- a/core/java/android/net/UidRange.java +++ b/core/java/android/net/UidRange.java @@ -21,6 +21,7 @@ import static android.os.UserHandle.PER_USER_RANGE; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import java.util.Collection; @@ -45,6 +46,14 @@ public final class UidRange implements Parcelable { return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); } + /** Creates a UidRange for the specified user. */ + public static UidRange createForUser(UserHandle user) { + final UserHandle nextUser = UserHandle.of(user.getIdentifier() + 1); + final int start = UserHandle.getUid(user, 0 /* appId */); + final int end = UserHandle.getUid(nextUser, 0) - 1; + return new UidRange(start, end); + } + /** Returns the smallest user Id which is contained in this UidRange */ public int getStartUser() { return start / PER_USER_RANGE; diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 059e932a9da9..3774fb595680 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1898,7 +1898,8 @@ public final class PowerManager { * These estimates will be displayed on system UI surfaces in place of the system computed * value. * - * Calling this requires the {@link android.Manifest.permission#DEVICE_POWER} permission. + * Calling this requires either the {@link android.Manifest.permission#DEVICE_POWER} or the + * {@link android.Manifest.permission#BATTERY_PREDICTION} permissions. * * @param timeRemaining The time remaining as a {@link Duration}. * @param isPersonalized true if personalized based on device usage history, false otherwise. @@ -1906,7 +1907,10 @@ public final class PowerManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + @RequiresPermission(anyOf = { + android.Manifest.permission.BATTERY_PREDICTION, + android.Manifest.permission.DEVICE_POWER + }) public void setBatteryDischargePrediction(@NonNull Duration timeRemaining, boolean isPersonalized) { if (timeRemaining == null) { diff --git a/core/java/android/service/wallpaper/EngineWindowPage.java b/core/java/android/service/wallpaper/EngineWindowPage.java new file mode 100644 index 000000000000..9d2e5b9441a3 --- /dev/null +++ b/core/java/android/service/wallpaper/EngineWindowPage.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.wallpaper; + +import android.app.WallpaperColors; +import android.graphics.Bitmap; +import android.graphics.RectF; +import android.util.ArrayMap; +import android.util.ArraySet; + +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +/** + * This class represents a page of a launcher page used by the wallpaper + * @hide + */ +public class EngineWindowPage { + private Bitmap mScreenShot; + private volatile long mLastUpdateTime; + private Set<RectF> mCallbackAreas = new ArraySet<>(); + private Map<RectF, WallpaperColors> mRectFColors = new ArrayMap<>(); + + /** should be locked extrnally */ + public void addArea(RectF area) { + mCallbackAreas.add(area); + } + + /** should be locked extrnally */ + public void addWallpaperColors(RectF area, WallpaperColors colors) { + mCallbackAreas.add(area); + mRectFColors.put(area, colors); + } + + /** get screenshot bitmap */ + public Bitmap getBitmap() { + if (mScreenShot == null || mScreenShot.isRecycled()) return null; + return mScreenShot; + } + + /** remove callbacks for an area */ + public void removeArea(RectF area) { + mCallbackAreas.remove(area); + mRectFColors.remove(area); + } + + /** set the last time the screenshot was updated */ + public void setLastUpdateTime(long lastUpdateTime) { + mLastUpdateTime = lastUpdateTime; + } + + /** get last screenshot time */ + public long getLastUpdateTime() { + return mLastUpdateTime; + } + + /** get colors for an area */ + public WallpaperColors getColors(RectF rect) { + return mRectFColors.get(rect); + } + + /** set the new bitmap version */ + public void setBitmap(Bitmap screenShot) { + mScreenShot = screenShot; + } + + /** get areas of interest */ + public Set<RectF> getAreas() { + return mCallbackAreas; + } + + /** run operations on this page */ + public synchronized void execSync(Consumer<EngineWindowPage> run) { + run.accept(this); + } + + /** nullify the area color */ + public void removeColor(RectF colorArea) { + mRectFColors.remove(colorArea); + } +} diff --git a/core/java/android/service/wallpaper/IWallpaperConnection.aidl b/core/java/android/service/wallpaper/IWallpaperConnection.aidl index f334d9d3e874..f81ed3410d58 100644 --- a/core/java/android/service/wallpaper/IWallpaperConnection.aidl +++ b/core/java/android/service/wallpaper/IWallpaperConnection.aidl @@ -16,6 +16,7 @@ package android.service.wallpaper; +import android.graphics.RectF; import android.os.ParcelFileDescriptor; import android.service.wallpaper.IWallpaperEngine; import android.app.WallpaperColors; @@ -28,4 +29,5 @@ interface IWallpaperConnection { void engineShown(IWallpaperEngine engine); ParcelFileDescriptor setWallpaper(String name); void onWallpaperColorsChanged(in WallpaperColors colors, int displayId); + void onLocalWallpaperColorsChanged(in RectF area, in WallpaperColors colors, int displayId); } diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index 90392e65794a..fbb449d3875e 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -16,7 +16,10 @@ package android.service.wallpaper; +import android.app.ILocalWallpaperColorConsumer; +import android.app.WallpaperColors; import android.graphics.Rect; +import android.graphics.RectF; import android.view.MotionEvent; import android.os.Bundle; @@ -39,4 +42,6 @@ oneway interface IWallpaperEngine { void destroy(); void setZoomOut(float scale); void scalePreview(in Rect positionInWindow); + void removeLocalColorsAreas(in List<RectF> regions); + void addLocalColorsAreas(in List<RectF> regions); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 82e0b4a1aecc..0b447d5bcf8a 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -23,6 +23,7 @@ import static android.graphics.Matrix.MSKEW_Y; import static android.view.View.SYSTEM_UI_FLAG_VISIBLE; import android.annotation.FloatRange; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -42,6 +43,7 @@ import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; @@ -53,6 +55,8 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; +import android.os.Trace; +import android.util.ArraySet; import android.util.Log; import android.util.MergedConfiguration; import android.view.Display; @@ -66,6 +70,8 @@ import android.view.InputEventReceiver; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.MotionEvent; +import android.view.PixelCopy; +import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.View; @@ -83,7 +89,10 @@ import com.android.internal.view.BaseSurfaceHolder; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; /** @@ -115,7 +124,12 @@ public abstract class WallpaperService extends Service { public static final String SERVICE_META_DATA = "android.service.wallpaper"; static final String TAG = "WallpaperService"; - static final boolean DEBUG = false; + static final boolean DEBUG = true; + static final float MIN_PAGE_ALLOWED_MARGIN = .05f; + private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64; + private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute + private static final @NonNull RectF LOCAL_COLOR_BOUNDS = + new RectF(0, 0, 1, 1); private static final int DO_ATTACH = 10; private static final int DO_DETACH = 20; @@ -134,6 +148,8 @@ public abstract class WallpaperService extends Service { private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050; private static final int MSG_ZOOM = 10100; private static final int MSG_SCALE_PREVIEW = 10110; + private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY, + Float.NEGATIVE_INFINITY); private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000; @@ -158,6 +174,14 @@ public abstract class WallpaperService extends Service { */ public class Engine { IWallpaperEngineWrapper mIWallpaperEngine; + final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4); + final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4); + + // 2D matrix [x][y] to represent a page of a portion of a window + EngineWindowPage[] mWindowPages = new EngineWindowPage[1]; + Bitmap mLastScreenshot; + int mLastWindowPage = -1; + float mLastPageOffset = 0; // Copies from mIWallpaperEngine. HandlerCaller mCaller; @@ -409,8 +433,8 @@ public abstract class WallpaperService extends Service { */ @VisibleForTesting public Engine(Supplier<Long> clockFunction, Handler handler) { - mClockFunction = clockFunction; - mHandler = handler; + mClockFunction = clockFunction; + mHandler = handler; } /** @@ -448,6 +472,19 @@ public abstract class WallpaperService extends Service { } /** + * Return whether the wallpaper is capable of extracting local colors in a rectangle area, + * Must implement without calling super: + * {@link #addLocalColorsAreas(List)} + * {@link #removeLocalColorsAreas(List)} + * When local colors change, call {@link #notifyLocalColorsChanged(List, List)} + * See {@link com.android.systemui.ImageWallpaper} for an example + * @hide + */ + public boolean supportsLocalColorExtraction() { + return false; + } + + /** * Returns true if this engine is running in preview mode -- that is, * it is being shown to the user before they select it as the actual * wallpaper. @@ -691,6 +728,8 @@ public abstract class WallpaperService extends Service { Log.w(TAG, "Can't notify system because wallpaper connection " + "was not established."); } + resetWindowPages(); + processLocalColors(mPendingXOffset, mPendingXOffsetStep); } catch (RemoteException e) { Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e); } @@ -714,6 +753,28 @@ public abstract class WallpaperService extends Service { } /** + * Send the changed local color areas for the connection + * @param regions + * @param colors + * @hide + */ + public void notifyLocalColorsChanged(@NonNull List<RectF> regions, + @NonNull List<WallpaperColors> colors) + throws RuntimeException { + for (int i = 0; i < regions.size() && i < colors.size() && colors.get(i) != null; i++) { + try { + mConnection.onLocalWallpaperColorsChanged( + regions.get(i), + colors.get(i), + mDisplayContext.getDisplayId() + ); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + + /** * Sets internal engine state. Only for testing. * @param created {@code true} or {@code false}. * @hide @@ -1068,7 +1129,9 @@ public abstract class WallpaperService extends Service { mIsCreating = false; mSurfaceCreated = true; if (redrawNeeded) { + resetWindowPages(); mSession.finishDrawing(mWindow, null /* postDrawTransaction */); + processLocalColors(mPendingXOffset, mPendingXOffsetStep); } reposition(); mIWallpaperEngine.reportShown(); @@ -1209,6 +1272,7 @@ public abstract class WallpaperService extends Service { if (!mDestroyed) { mVisible = visible; reportVisibility(); + if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); } } @@ -1278,6 +1342,405 @@ public abstract class WallpaperService extends Service { } catch (RemoteException e) { } } + + // setup local color extraction data + processLocalColors(xOffset, xOffsetStep); + } + + private void processLocalColors(float xOffset, float xOffsetStep) { + // implemented by the wallpaper + if (supportsLocalColorExtraction()) return; + if (DEBUG) { + Log.d(TAG, "processLocalColors " + xOffset + " of step " + + xOffsetStep); + } + //below is the default implementation + if (!validStep(xOffsetStep)) { + if (DEBUG) { + Log.w(TAG, "invalid offset step " + xOffsetStep); + } + return; + } + + int xPage = Math.round(xOffset / xOffsetStep); + if (!shouldProcessPage(xPage, xOffset, xOffsetStep)) return; + mLastWindowPage = xPage; + mLastPageOffset = xOffset; + int xPages = Math.round(1 / xOffsetStep); + if (DEBUG) { + Log.d(TAG, "xPages " + xPages + " xPage " + xPage); + Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset); + } + EngineWindowPage current; + synchronized (mLock) { + if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) { + mWindowPages = new EngineWindowPage[xPages]; + initWindowPages(mWindowPages, xOffsetStep); + } + if (mLocalColorsToAdd.size() != 0) { + for (RectF colorArea : mLocalColorsToAdd) { + if (!isValid(colorArea)) continue; + mLocalColorAreas.add(colorArea); + int colorPage = getRectFPage(colorArea, xOffsetStep); + EngineWindowPage currentPage = mWindowPages[colorPage]; + if (currentPage == null) { + currentPage = new EngineWindowPage(); + currentPage.addArea(colorArea); + mWindowPages[colorPage] = currentPage; + } else { + currentPage.setLastUpdateTime(0); + currentPage.removeColor(colorArea); + } + } + mLocalColorsToAdd.clear(); + } + if (xPage >= mWindowPages.length) { + if (DEBUG) { + Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage); + Log.e(TAG, "error on page " + xPage + " out of " + xPages); + Log.e(TAG, + "error on xOffsetStep " + xOffsetStep + " xOffset " + xOffset); + } + xPage = mWindowPages.length - 1; + } + current = mWindowPages[xPage]; + if (current == null) { + if (DEBUG) Log.d(TAG, "making page " + xPage + " out of " + xPages); + if (DEBUG) { + Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset); + } + current = new EngineWindowPage(); + mWindowPages[xPage] = current; + } + } + updatePage(current, xPage, xPages, xOffsetStep); + } + + private boolean shouldProcessPage(int xPage, float xOffset, float xOffsetStep) { + float pageCenter = xOffsetStep * xPage + xOffsetStep / 2; + + return (pageCenter > xOffset && pageCenter < mLastPageOffset) + || (pageCenter < xOffset && pageCenter > mLastPageOffset); + } + + private void initWindowPages(EngineWindowPage[] windowPages, float step) { + for (int i = 0; i < windowPages.length; i++) { + windowPages[i] = new EngineWindowPage(); + } + mLocalColorAreas.addAll(mLocalColorsToAdd); + mLocalColorsToAdd.clear(); + for (RectF area: mLocalColorAreas) { + if (!isValid(area)) { + mLocalColorAreas.remove(area); + continue; + } + int pageNum = getRectFPage(area, step); + windowPages[pageNum].addArea(area); + } + } + + void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages, + float xOffsetStep) { + // to save creating a runnable, check twice + long current = System.nanoTime() / 1_000_000; + AtomicLong lapsed = new AtomicLong(current - currentPage.getLastUpdateTime()); + if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return; + currentPage.execSync((page) -> { + // just in case of race double check + long currentInner = System.nanoTime() / 1_000_000; + lapsed.set(currentInner - page.getLastUpdateTime()); + page.setLastUpdateTime(currentInner); + }); + long lastUpdate = currentPage.getLastUpdateTime(); + if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return; + Surface surface = mSurfaceHolder.getSurface(); + boolean widthIsLarger = + mSurfaceControl.getWidth() > mSurfaceControl.getHeight(); + int smaller = widthIsLarger ? mSurfaceControl.getWidth() + : mSurfaceControl.getHeight(); + float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller; + int width = (int) (ratio * mSurfaceControl.getWidth()); + int height = (int) (ratio * mSurfaceControl.getHeight()); + if (width <= 0 || height <= 0) { + return; + } + Bitmap screenShot = Bitmap.createBitmap(width, height, + Bitmap.Config.ARGB_8888); + final Bitmap finalScreenShot = screenShot; + Trace.beginSection("WallpaperService#pixelCopy"); + PixelCopy.request(surface, screenShot, (res) -> { + Trace.endSection(); + if (DEBUG) Log.d(TAG, "result of pixel copy is " + res); + if (res != PixelCopy.SUCCESS) { + currentPage.execSync((p) -> { + // reset the time + p.setLastUpdateTime(lastUpdate); + // assign the last bitmap taken for now + p.setBitmap(mLastScreenshot); + }); + return; + } + mLastScreenshot = finalScreenShot; + // going to hold this lock for a while + currentPage.execSync((p) -> { + p.setBitmap(finalScreenShot); + }); + updatePageColors(currentPage, pageIndx, numPages, xOffsetStep); + }, mHandler); + + } + // locked by the passed page + private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages, + float xOffsetStep) { + if (page.getBitmap() == null) return; + if (DEBUG) { + Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas " + + page.getAreas().size() + " and bitmap size of " + + page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight()); + } + for (RectF area: page.getAreas()) { + RectF subArea = generateSubRect(area, pageIndx, numPages); + Bitmap b = page.getBitmap(); + int x = Math.round(b.getWidth() * subArea.left); + int y = Math.round(b.getHeight() * subArea.top); + int width = Math.round(b.getWidth() * subArea.width()); + int height = Math.round(b.getHeight() * subArea.height()); + Bitmap target; + try { + target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height); + } catch (Exception e) { + Log.e(TAG, "Error creating page local color bitmap", e); + continue; + } + WallpaperColors color = WallpaperColors.fromBitmap(target); + target.recycle(); + WallpaperColors currentColor = page.getColors(area); + + if (DEBUG) { + Log.d(TAG, "getting local bitmap area x " + x + " y " + y + + " width " + width + " height " + height + " for sub area " + subArea + + " and with page " + pageIndx + " of " + numPages); + + } + if (currentColor == null || !color.equals(currentColor)) { + page.addWallpaperColors(area, color); + if (DEBUG) { + Log.d(TAG, "onLocalWallpaperColorsChanged" + + " local color callback for area" + area + " for page " + pageIndx + + " of " + numPages); + } + try { + mConnection.onLocalWallpaperColorsChanged(area, color, + mDisplayContext.getDisplayId()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e); + } + } + } + } + + private RectF generateSubRect(RectF in, int pageInx, int numPages) { + float minLeft = (float) (pageInx) / (float) (numPages); + float maxRight = (float) (pageInx + 1) / (float) (numPages); + float left = in.left; + float right = in.right; + + // bound rect + if (left < minLeft) left = minLeft; + if (right > maxRight) right = maxRight; + + // scale up the sub area then trim + left = (left * (float) numPages) % 1f; + right = (right * (float) numPages) % 1f; + if (right == 0f) { + right = 1f; + } + + return new RectF(left, in.top, right, in.bottom); + } + + private void resetWindowPages() { + if (supportsLocalColorExtraction()) return; + mLastWindowPage = -1; + synchronized (mLock) { + for (int i = 0; i < mWindowPages.length; i++) { + EngineWindowPage page = mWindowPages[i]; + if (page != null) { + page.execSync((p) -> { + p.setLastUpdateTime(0L); + }); + } + } + } + } + + private int getRectFPage(RectF area, float step) { + if (!isValid(area)) return 0; + if (!validStep(step)) return 0; + int pages = Math.round(1 / step); + int page = Math.round(area.centerX() * pages); + if (page == pages) return pages - 1; + if (page == mWindowPages.length) page = mWindowPages.length - 1; + return page; + } + + /** + * Add local colors areas of interest + * @param regions list of areas + * @hide + */ + public void addLocalColorsAreas(@NonNull List<RectF> regions) { + if (supportsLocalColorExtraction()) return; + if (DEBUG) { + Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions); + } + float step = mPendingXOffsetStep; + + List<WallpaperColors> colors = getLocalWallpaperColors(regions); + synchronized (mLock) { + if (!validStep(step)) { + mLocalColorsToAdd.addAll(regions); + return; + } + for (int i = 0; i < regions.size(); i++) { + RectF area = regions.get(i); + if (!isValid(area)) continue; + int pageInx = getRectFPage(area, step); + // no page should be null + EngineWindowPage page = mWindowPages[pageInx]; + + if (page != null) { + mLocalColorAreas.add(area); + page.addArea(area); + WallpaperColors color = colors.get(i); + if (color != null && !color.equals(page.getColors(area))) { + page.execSync(p -> { + p.addWallpaperColors(area, color); + }); + } + } else { + mLocalColorsToAdd.add(area); + } + } + } + + for (int i = 0; i < colors.size() && colors.get(i) != null; i++) { + try { + mConnection.onLocalWallpaperColorsChanged(regions.get(i), colors.get(i), + mDisplayContext.getDisplayId()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e); + return; + } + } + } + + /** + * Remove local colors areas of interest if they exist + * @param regions list of areas + * @hide + */ + public void removeLocalColorsAreas(@NonNull List<RectF> regions) { + if (supportsLocalColorExtraction()) return; + synchronized (mLock) { + float step = mPendingXOffsetStep; + mLocalColorsToAdd.removeAll(regions); + mLocalColorAreas.removeAll(regions); + if (!validStep(step)) { + return; + } + for (int i = 0; i < regions.size(); i++) { + RectF area = regions.get(i); + if (!isValid(area)) continue; + int pageInx = getRectFPage(area, step); + // no page should be null + EngineWindowPage page = mWindowPages[pageInx]; + if (page != null) { + page.execSync(p -> { + p.removeArea(area); + }); + } + } + } + } + + private @NonNull List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas) { + ArrayList<WallpaperColors> colors = new ArrayList<>(areas.size()); + float step = mPendingXOffsetStep; + if (!validStep(step)) { + if (DEBUG) Log.d(TAG, "invalid step size " + step); + step = 1.0f; + } + for (int i = 0; i < areas.size(); i++) { + RectF currentArea = areas.get(i); + EngineWindowPage page; + RectF area; + int pageIndx; + synchronized (mLock) { + pageIndx = getRectFPage(currentArea, step); + if (mWindowPages.length == 0 || pageIndx < 0 + || pageIndx > mWindowPages.length || !isValid(currentArea)) { + colors.add(null); + continue; + } + area = generateSubRect(currentArea, pageIndx, mWindowPages.length); + page = mWindowPages[pageIndx]; + } + if (page == null) { + colors.add(null); + continue; + } + float finalStep = step; + int finalPageIndx = pageIndx; + Bitmap screenShot = page.getBitmap(); + if (screenShot == null || screenShot.isRecycled()) { + if (DEBUG) { + Log.d(TAG, "invalid bitmap " + screenShot + + " for page " + finalPageIndx); + } + page.setLastUpdateTime(0); + colors.add(null); + continue; + } + Bitmap b = screenShot; + Rect subImage = new Rect( + Math.round(area.left * b.getWidth() / finalStep), + Math.round(area.top * b.getHeight()), + Math.round(area.right * b.getWidth() / finalStep), + Math.round(area.bottom * b.getHeight()) + ); + subImage = fixRect(b, subImage); + if (DEBUG) { + Log.d(TAG, "getting subbitmap of " + subImage.toString() + + " for RectF " + area.toString() + + " screenshot width " + screenShot.getWidth() + " height " + + screenShot.getHeight()); + } + Bitmap colorImg = Bitmap.createBitmap(screenShot, + subImage.left, subImage.top, subImage.width(), subImage.height()); + if (DEBUG) { + Log.d(TAG, "created bitmap " + colorImg.getWidth() + ", " + + colorImg.getHeight()); + } + WallpaperColors color = WallpaperColors.fromBitmap(colorImg); + colors.add(color); + } + return colors; + } + + // fix the rect to be included within the bounds of the bitmap + private Rect fixRect(Bitmap b, Rect r) { + r.left = r.left >= r.right || r.left >= b.getWidth() || r.left > 0 + ? 0 + : r.left; + r.right = r.left >= r.right || r.right > b.getWidth() + ? b.getWidth() + : r.right; + return r; + } + + private boolean validStep(float step) { + return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.; } void doCommand(WallpaperCommand cmd) { @@ -1371,6 +1834,16 @@ public abstract class WallpaperService extends Service { }; } + private boolean isValid(RectF area) { + boolean valid = area.bottom > area.top && area.left < area.right + && LOCAL_COLOR_BOUNDS.contains(area); + return valid; + } + + private boolean inRectFRange(float number) { + return number >= 0f && number <= 1f; + } + class IWallpaperEngineWrapper extends IWallpaperEngine.Stub implements HandlerCaller.Callback { private final HandlerCaller mCaller; @@ -1477,6 +1950,14 @@ public abstract class WallpaperService extends Service { mCaller.sendMessage(msg); } + public void addLocalColorsAreas(List<RectF> regions) { + mEngine.addLocalColorsAreas(regions); + } + + public void removeLocalColorsAreas(List<RectF> regions) { + mEngine.removeLocalColorsAreas(regions); + } + public void destroy() { Message msg = mCaller.obtainMessage(DO_DETACH); mCaller.sendMessage(msg); @@ -1516,14 +1997,15 @@ public abstract class WallpaperService extends Service { } switch (message.what) { case DO_ATTACH: { + Engine engine = onCreateEngine(); + mEngine = engine; try { mConnection.attachEngine(this, mDisplayId); } catch (RemoteException e) { + engine.detach(); Log.w(TAG, "Wallpaper host disappeared", e); return; } - Engine engine = onCreateEngine(); - mEngine = engine; mActiveEngines.add(engine); engine.attach(this); return; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1273b491d0e1..ba78f966b218 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -22164,6 +22164,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * and hardware acceleration. */ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { + // Clear the overscroll effect: + // TODO: Use internal API instead of overriding the existing RenderEffect + setRenderEffect(null); + final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated(); /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList. * diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 97dbb1542717..6dedd12a2730 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -89,7 +89,7 @@ public class HorizontalScrollView extends FrameLayout { */ @NonNull @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124053130) - private EdgeEffect mEdgeGlowLeft = new EdgeEffect(getContext()); + private EdgeEffect mEdgeGlowLeft; /** * Tracks the state of the bottom edge glow. @@ -98,7 +98,7 @@ public class HorizontalScrollView extends FrameLayout { * setting it via reflection and they need to keep working until they target Q. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124052619) - private EdgeEffect mEdgeGlowRight = new EdgeEffect(getContext()); + private EdgeEffect mEdgeGlowRight; /** * Position of the last motion event. @@ -186,6 +186,8 @@ public class HorizontalScrollView extends FrameLayout { public HorizontalScrollView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mEdgeGlowLeft = new EdgeEffect(context, attrs); + mEdgeGlowRight = new EdgeEffect(context, attrs); initScrollView(); final TypedArray a = context.obtainStyledAttributes( @@ -631,7 +633,15 @@ public class HorizontalScrollView extends FrameLayout { * otherwise don't. mScroller.isFinished should be false when * being flinged. */ - mIsBeingDragged = !mScroller.isFinished(); + mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowLeft.isFinished() + || !mEdgeGlowRight.isFinished(); + // Catch the edge effect if it is active. + if (!mEdgeGlowLeft.isFinished()) { + mEdgeGlowLeft.onPullDistance(0f, 1f - ev.getY() / getHeight()); + } + if (!mEdgeGlowRight.isFinished()) { + mEdgeGlowRight.onPullDistance(0f, ev.getY() / getHeight()); + } break; } @@ -675,7 +685,8 @@ public class HorizontalScrollView extends FrameLayout { if (getChildCount() == 0) { return false; } - if ((mIsBeingDragged = !mScroller.isFinished())) { + if ((mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowRight.isFinished() + || !mEdgeGlowLeft.isFinished())) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); @@ -721,12 +732,26 @@ public class HorizontalScrollView extends FrameLayout { mLastMotionX = x; final int oldX = mScrollX; - final int oldY = mScrollY; final int range = getScrollRange(); final int overscrollMode = getOverScrollMode(); final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); + final float displacement = ev.getY(activePointerIndex) / getHeight(); + if (canOverscroll) { + int consumed = 0; + if (deltaX < 0 && mEdgeGlowRight.getDistance() != 0f) { + consumed = Math.round(getHeight() + * mEdgeGlowRight.onPullDistance((float) deltaX / getWidth(), + displacement)); + } else if (deltaX > 0 && mEdgeGlowLeft.getDistance() != 0f) { + consumed = Math.round(-getHeight() + * mEdgeGlowLeft.onPullDistance((float) -deltaX / getWidth(), + 1 - displacement)); + } + deltaX -= consumed; + } + // Calling overScrollBy will call onOverScrolled, which // calls onScrollChanged if applicable. if (overScrollBy(deltaX, 0, mScrollX, 0, range, 0, @@ -735,17 +760,17 @@ public class HorizontalScrollView extends FrameLayout { mVelocityTracker.clear(); } - if (canOverscroll) { + if (canOverscroll && deltaX != 0f) { final int pulledToX = oldX + deltaX; if (pulledToX < 0) { - mEdgeGlowLeft.onPull((float) deltaX / getWidth(), - 1.f - ev.getY(activePointerIndex) / getHeight()); + mEdgeGlowLeft.onPullDistance((float) -deltaX / getWidth(), + 1.f - displacement); if (!mEdgeGlowRight.isFinished()) { mEdgeGlowRight.onRelease(); } } else if (pulledToX > range) { - mEdgeGlowRight.onPull((float) deltaX / getWidth(), - ev.getY(activePointerIndex) / getHeight()); + mEdgeGlowRight.onPullDistance((float) deltaX / getWidth(), + displacement); if (!mEdgeGlowLeft.isFinished()) { mEdgeGlowLeft.onRelease(); } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 6cb4b81827b9..4e3d99b43cf6 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -48,6 +48,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Outline; +import android.graphics.PointF; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -99,6 +100,8 @@ import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Stack; @@ -171,6 +174,11 @@ public class RemoteViews implements Parcelable, Filter { */ private static final int MAX_NESTED_VIEWS = 10; + /** + * Maximum number of RemoteViews that can be specified in constructor. + */ + private static final int MAX_INIT_VIEW_COUNT = 16; + // The unique identifiers for each custom {@link Action}. private static final int SET_ON_CLICK_RESPONSE_TAG = 1; private static final int REFLECTION_ACTION_TAG = 2; @@ -290,7 +298,7 @@ public class RemoteViews implements Parcelable, Filter { * The resource ID of the layout file. (Added to the parcel) */ @UnsupportedAppUsage - private final int mLayoutId; + private int mLayoutId; /** * The resource ID of the layout file in dark text mode. (Added to the parcel) @@ -322,6 +330,7 @@ public class RemoteViews implements Parcelable, Filter { */ private static final int MODE_NORMAL = 0; private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1; + private static final int MODE_HAS_SIZED_REMOTEVIEWS = 2; /** * Used in conjunction with the special constructor @@ -331,12 +340,26 @@ public class RemoteViews implements Parcelable, Filter { private RemoteViews mLandscape = null; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private RemoteViews mPortrait = null; + /** + * List of RemoteViews with their ideal size. There must be at least two if the map is not null. + * + * The smallest remote view is always the last element in the list. + */ + private List<RemoteViews> mSizedRemoteViews = null; + + /** + * Ideal size for this RemoteViews. + * + * Only to be used on children views used in a {@link RemoteViews} with + * {@link RemoteViews#hasSizedRemoteViews()}. + */ + private PointF mIdealSize = null; @ApplyFlags private int mApplyFlags = 0; /** Class cookies of the Parcel this instance was read from. */ - private final Map<Class, Object> mClassCookies; + private Map<Class, Object> mClassCookies; private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = (view, pendingIntent, response) -> startPendingIntent(view, pendingIntent, response.getLaunchOptions(view)); @@ -2768,23 +2791,50 @@ public class RemoteViews implements Parcelable, Filter { mClassCookies = null; } + private boolean hasMultipleLayouts() { + return hasLandscapeAndPortraitLayouts() || hasSizedRemoteViews(); + } + private boolean hasLandscapeAndPortraitLayouts() { return (mLandscape != null) && (mPortrait != null); } + private boolean hasSizedRemoteViews() { + return mSizedRemoteViews != null; + } + + private @Nullable PointF getIdealSize() { + return mIdealSize; + } + + private void setIdealSize(@Nullable PointF size) { + mIdealSize = size; + } + + /** + * Finds the smallest view in {@code mSizedRemoteViews}. + * This method must not be called if {@code mSizedRemoteViews} is null. + */ + private RemoteViews findSmallestRemoteView() { + return mSizedRemoteViews.get(mSizedRemoteViews.size() - 1); + } + /** * Create a new RemoteViews object that will inflate as the specified * landspace or portrait RemoteViews, depending on the current configuration. * * @param landscape The RemoteViews to inflate in landscape configuration * @param portrait The RemoteViews to inflate in portrait configuration + * @throws IllegalArgumentException if either landscape or portrait are null or if they are + * not from the same application */ public RemoteViews(RemoteViews landscape, RemoteViews portrait) { if (landscape == null || portrait == null) { - throw new RuntimeException("Both RemoteViews must be non-null"); + throw new IllegalArgumentException("Both RemoteViews must be non-null"); } if (!landscape.hasSameAppInfo(portrait.mApplication)) { - throw new RuntimeException("Both RemoteViews must share the same package and user"); + throw new IllegalArgumentException( + "Both RemoteViews must share the same package and user"); } mApplication = portrait.mApplication; mLayoutId = portrait.mLayoutId; @@ -2802,9 +2852,84 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Create a new RemoteViews object that will inflate the layout with the closest size + * specification. + * + * The default remote views in that case is always the smallest one provided. + * + * @param remoteViews Mapping of size to layout. + * @throws IllegalArgumentException if the map is empty, there are more than + * MAX_INIT_VIEW_COUNT layouts or the remote views are not all from the same application. + */ + public RemoteViews(@NonNull Map<PointF, RemoteViews> remoteViews) { + if (remoteViews.isEmpty()) { + throw new IllegalArgumentException("The set of RemoteViews cannot be empty"); + } + if (remoteViews.size() > MAX_INIT_VIEW_COUNT) { + throw new IllegalArgumentException("Too many RemoteViews in constructor"); + } + if (remoteViews.size() == 1) { + initializeFrom(remoteViews.values().iterator().next()); + return; + } + mBitmapCache = new BitmapCache(); + mClassCookies = initializeSizedRemoteViews( + remoteViews.entrySet().stream().map( + entry -> { + entry.getValue().setIdealSize(entry.getKey()); + return entry.getValue(); + } + ).iterator() + ); + + RemoteViews smallestView = findSmallestRemoteView(); + mApplication = smallestView.mApplication; + mLayoutId = smallestView.mLayoutId; + mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId; + } + + // Initialize mSizedRemoteViews and return the class cookies. + private Map<Class, Object> initializeSizedRemoteViews(Iterator<RemoteViews> remoteViews) { + List<RemoteViews> sizedRemoteViews = new ArrayList<>(); + Map<Class, Object> classCookies = null; + float viewArea = Float.MAX_VALUE; + RemoteViews smallestView = null; + while (remoteViews.hasNext()) { + RemoteViews view = remoteViews.next(); + PointF size = view.getIdealSize(); + float newViewArea = size.x * size.y; + if (smallestView != null && !view.hasSameAppInfo(smallestView.mApplication)) { + throw new IllegalArgumentException( + "All RemoteViews must share the same package and user"); + } + if (smallestView == null || newViewArea < viewArea) { + if (smallestView != null) { + sizedRemoteViews.add(smallestView); + } + viewArea = newViewArea; + smallestView = view; + } else { + sizedRemoteViews.add(view); + } + configureRemoteViewsAsChild(view); + view.setIdealSize(size); + if (classCookies == null) { + classCookies = view.mClassCookies; + } + } + sizedRemoteViews.add(smallestView); + mSizedRemoteViews = sizedRemoteViews; + return classCookies; + } + + /** * Creates a copy of another RemoteViews. */ public RemoteViews(RemoteViews src) { + initializeFrom(src); + } + + private void initializeFrom(RemoteViews src) { mBitmapCache = src.mBitmapCache; mApplication = src.mApplication; mIsRoot = src.mIsRoot; @@ -2812,12 +2937,20 @@ public class RemoteViews implements Parcelable, Filter { mLightBackgroundLayoutId = src.mLightBackgroundLayoutId; mApplyFlags = src.mApplyFlags; mClassCookies = src.mClassCookies; + mIdealSize = src.mIdealSize; if (src.hasLandscapeAndPortraitLayouts()) { mLandscape = new RemoteViews(src.mLandscape); mPortrait = new RemoteViews(src.mPortrait); } + if (src.hasSizedRemoteViews()) { + mSizedRemoteViews = new ArrayList<>(src.mSizedRemoteViews.size()); + for (RemoteViews srcView : src.mSizedRemoteViews) { + mSizedRemoteViews.add(new RemoteViews(srcView)); + } + } + if (src.mActions != null) { Parcel p = Parcel.obtain(); p.putClassCookies(mClassCookies); @@ -2867,10 +3000,29 @@ public class RemoteViews implements Parcelable, Filter { if (mode == MODE_NORMAL) { mApplication = parcel.readInt() == 0 ? info : ApplicationInfo.CREATOR.createFromParcel(parcel); + mIdealSize = parcel.readInt() == 0 ? null : PointF.CREATOR.createFromParcel(parcel); mLayoutId = parcel.readInt(); mLightBackgroundLayoutId = parcel.readInt(); readActionsFromParcel(parcel, depth); + } else if (mode == MODE_HAS_SIZED_REMOTEVIEWS) { + int numViews = parcel.readInt(); + if (numViews > MAX_INIT_VIEW_COUNT) { + throw new IllegalArgumentException( + "Too many views in mapping from size to RemoteViews."); + } + List<RemoteViews> remoteViews = new ArrayList<>(numViews); + for (int i = 0; i < numViews; i++) { + RemoteViews view = new RemoteViews(parcel, mBitmapCache, info, depth, + mClassCookies); + info = view.mApplication; + remoteViews.add(view); + } + initializeSizedRemoteViews(remoteViews.iterator()); + RemoteViews smallestView = findSmallestRemoteView(); + mApplication = smallestView.mApplication; + mLayoutId = smallestView.mLayoutId; + mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId; } else { // MODE_HAS_LANDSCAPE_AND_PORTRAIT mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies); @@ -2990,16 +3142,20 @@ public class RemoteViews implements Parcelable, Filter { */ private void setBitmapCache(BitmapCache bitmapCache) { mBitmapCache = bitmapCache; - if (!hasLandscapeAndPortraitLayouts()) { + if (hasSizedRemoteViews()) { + for (RemoteViews remoteView : mSizedRemoteViews) { + remoteView.setBitmapCache(bitmapCache); + } + } else if (hasLandscapeAndPortraitLayouts()) { + mLandscape.setBitmapCache(bitmapCache); + mPortrait.setBitmapCache(bitmapCache); + } else { if (mActions != null) { final int count = mActions.size(); - for (int i= 0; i < count; ++i) { + for (int i = 0; i < count; ++i) { mActions.get(i).setBitmapCache(bitmapCache); } } - } else { - mLandscape.setBitmapCache(bitmapCache); - mPortrait.setBitmapCache(bitmapCache); } } @@ -3018,10 +3174,10 @@ public class RemoteViews implements Parcelable, Filter { * @param a The action to add */ private void addAction(Action a) { - if (hasLandscapeAndPortraitLayouts()) { - throw new RuntimeException("RemoteViews specifying separate landscape and portrait" + - " layouts cannot be modified. Instead, fully configure the landscape and" + - " portrait layouts individually before constructing the combined layout."); + if (hasMultipleLayouts()) { + throw new RuntimeException("RemoteViews specifying separate layouts for orientation" + + " or size cannot be modified. Instead, fully configure each layouts" + + " individually before constructing the combined layout."); } if (mActions == null) { mActions = new ArrayList<>(); @@ -4100,14 +4256,79 @@ public class RemoteViews implements Parcelable, Filter { int orientation = context.getResources().getConfiguration().orientation; if (orientation == Configuration.ORIENTATION_LANDSCAPE) { return mLandscape; - } else { - return mPortrait; } + return mPortrait; + } + if (hasSizedRemoteViews()) { + return findSmallestRemoteView(); } return this; } /** + * Returns the square distance between two points. + * + * This is particularly useful when we only care about the ordering of the distances. + */ + private static float squareDistance(PointF p1, PointF p2) { + float dx = p1.x - p2.x; + float dy = p1.y - p2.y; + return dx * dx + dy * dy; + } + + /** + * Returns whether the layout fits in the space available to the widget. + * + * A layout fits on a widget if the widget size is known (i.e. not null) and both dimensions + * are smaller than the ones of the widget, adding some padding to account for rounding errors. + */ + private static boolean fitsIn(PointF sizeLayout, @Nullable PointF sizeWidget) { + return sizeWidget != null && (Math.ceil(sizeWidget.x) + 1 > sizeLayout.x) + && (Math.ceil(sizeWidget.y) + 1 > sizeLayout.y); + } + + /** + * Returns the most appropriate {@link RemoteViews} given the context and, if not null, the + * size of the widget. + * + * If {@link RemoteViews#hasSizedRemoteViews()} returns true, the most appropriate view is + * the one that fits in the widget (according to {@link RemoteViews#fitsIn}) and has the + * diagonal the most similar to the widget. If no layout fits or the size of the widget is + * not specified, the one with the smallest area will be chosen. + */ + private RemoteViews getRemoteViewsToApply(@NonNull Context context, + @Nullable PointF widgetSize) { + if (!hasSizedRemoteViews()) { + // If there isn't multiple remote views, fall back on the previous methods. + return getRemoteViewsToApply(context); + } + // Find the better remote view + RemoteViews bestFit = null; + float bestSqDist = Float.MAX_VALUE; + for (RemoteViews layout : mSizedRemoteViews) { + PointF layoutSize = layout.getIdealSize(); + if (fitsIn(layoutSize, widgetSize)) { + if (bestFit == null) { + bestFit = layout; + bestSqDist = squareDistance(layoutSize, widgetSize); + } else { + float newSqDist = squareDistance(layoutSize, widgetSize); + if (newSqDist < bestSqDist) { + bestFit = layout; + bestSqDist = newSqDist; + } + } + } + } + if (bestFit == null) { + Log.w(LOG_TAG, "Could not find a RemoteViews fitting the current size: " + widgetSize); + return findSmallestRemoteView(); + } + return bestFit; + } + + + /** * Inflates the view hierarchy represented by this object and applies * all of the actions. * @@ -4124,7 +4345,13 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public View apply(Context context, ViewGroup parent, OnClickHandler handler) { - RemoteViews rvToApply = getRemoteViewsToApply(context); + return apply(context, parent, handler, null); + } + + /** @hide */ + public View apply(@NonNull Context context, @NonNull ViewGroup parent, + @Nullable OnClickHandler handler, @Nullable PointF size) { + RemoteViews rvToApply = getRemoteViewsToApply(context, size); View result = inflateView(context, rvToApply, parent); rvToApply.performApply(result, parent, handler); @@ -4132,9 +4359,17 @@ public class RemoteViews implements Parcelable, Filter { } /** @hide */ - public View applyWithTheme(Context context, ViewGroup parent, OnClickHandler handler, + public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent, + @Nullable OnClickHandler handler, @StyleRes int applyThemeResId) { - RemoteViews rvToApply = getRemoteViewsToApply(context); + return applyWithTheme(context, parent, handler, applyThemeResId, null); + } + + /** @hide */ + public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent, + @Nullable OnClickHandler handler, + @StyleRes int applyThemeResId, @Nullable PointF size) { + RemoteViews rvToApply = getRemoteViewsToApply(context, size); View result = inflateView(context, rvToApply, parent, applyThemeResId); rvToApply.performApply(result, parent, handler); @@ -4219,12 +4454,26 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener, OnClickHandler handler) { - return getAsyncApplyTask(context, parent, listener, handler).startTaskOnExecutor(executor); + return applyAsync(context, parent, executor, listener, handler, null); + } + + /** @hide */ + public CancellationSignal applyAsync(Context context, ViewGroup parent, + Executor executor, OnViewAppliedListener listener, OnClickHandler handler, + PointF size) { + return getAsyncApplyTask(context, parent, listener, handler, size).startTaskOnExecutor( + executor); } private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent, OnViewAppliedListener listener, OnClickHandler handler) { - return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener, + return getAsyncApplyTask(context, parent, listener, handler, null); + } + + private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent, + OnViewAppliedListener listener, OnClickHandler handler, PointF size) { + return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, + listener, handler, null); } @@ -4341,12 +4590,18 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public void reapply(Context context, View v, OnClickHandler handler) { - RemoteViews rvToApply = getRemoteViewsToApply(context); + reapply(context, v, handler, null); + } - // In the case that a view has this RemoteViews applied in one orientation, is persisted - // across orientation change, and has the RemoteViews re-applied in the new orientation, - // we throw an exception, since the layouts may be completely unrelated. - if (hasLandscapeAndPortraitLayouts()) { + /** @hide */ + public void reapply(Context context, View v, OnClickHandler handler, PointF size) { + RemoteViews rvToApply = getRemoteViewsToApply(context, size); + + // In the case that a view has this RemoteViews applied in one orientation or size, is + // persisted across change, and has the RemoteViews re-applied in a different situation + // (orientation or size), we throw an exception, since the layouts may be completely + // unrelated. + if (hasMultipleLayouts()) { if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) { throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + " that does not share the same root layout id."); @@ -4377,12 +4632,18 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public CancellationSignal reapplyAsync(Context context, View v, Executor executor, OnViewAppliedListener listener, OnClickHandler handler) { - RemoteViews rvToApply = getRemoteViewsToApply(context); + return reapplyAsync(context, v, executor, listener, handler, null); + } + + /** @hide */ + public CancellationSignal reapplyAsync(Context context, View v, Executor executor, + OnViewAppliedListener listener, OnClickHandler handler, PointF size) { + RemoteViews rvToApply = getRemoteViewsToApply(context, size); // In the case that a view has this RemoteViews applied in one orientation, is persisted // across orientation change, and has the RemoteViews re-applied in the new orientation, // we throw an exception, since the layouts may be completely unrelated. - if (hasLandscapeAndPortraitLayouts()) { + if (hasMultipleLayouts()) { if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) { throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + " that does not share the same root layout id."); @@ -4466,7 +4727,7 @@ public class RemoteViews implements Parcelable, Filter { } public void writeToParcel(Parcel dest, int flags) { - if (!hasLandscapeAndPortraitLayouts()) { + if (!hasMultipleLayouts()) { dest.writeInt(MODE_NORMAL); // We only write the bitmap cache if we are the root RemoteViews, as this cache // is shared by all children. @@ -4479,9 +4740,26 @@ public class RemoteViews implements Parcelable, Filter { dest.writeInt(1); mApplication.writeToParcel(dest, flags); } + if (mIsRoot || mIdealSize == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + mIdealSize.writeToParcel(dest, flags); + } dest.writeInt(mLayoutId); dest.writeInt(mLightBackgroundLayoutId); writeActionsToParcel(dest); + } else if (hasSizedRemoteViews()) { + dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS); + if (mIsRoot) { + mBitmapCache.writeBitmapsToParcel(dest, flags); + } + int childFlags = flags; + dest.writeInt(mSizedRemoteViews.size()); + for (RemoteViews view : mSizedRemoteViews) { + view.writeToParcel(dest, childFlags); + childFlags |= PARCELABLE_ELIDE_DUPLICATES; + } } else { dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); // We only write the bitmap cache if we are the root RemoteViews, as this cache @@ -4735,11 +5013,9 @@ public class RemoteViews implements Parcelable, Filter { * before starting the intent. * * @param fillIntent The intent which will be combined with the parent's PendingIntent in - * order to determine the behavior of the response - * + * order to determine the behavior of the response * @see RemoteViews#setPendingIntentTemplate(int, PendingIntent) * @see RemoteViews#setOnClickFillInIntent(int, Intent) - * @return */ @NonNull public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) { @@ -4754,9 +5030,8 @@ public class RemoteViews implements Parcelable, Filter { * the epicenter for the exit Transition. The position of the associated shared element in * the launched Activity will be the epicenter of its entering Transition. * - * @param viewId The id of the view to be shared as part of the transition + * @param viewId The id of the view to be shared as part of the transition * @param sharedElementName The shared element name for this view - * * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[]) */ @NonNull diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index f3de9828c4aa..64d09de49f2d 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -98,7 +98,7 @@ public class ScrollView extends FrameLayout { */ @NonNull @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768600) - private EdgeEffect mEdgeGlowTop = new EdgeEffect(getContext()); + private EdgeEffect mEdgeGlowTop; /** * Tracks the state of the bottom edge glow. @@ -108,7 +108,7 @@ public class ScrollView extends FrameLayout { */ @NonNull @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769386) - private EdgeEffect mEdgeGlowBottom = new EdgeEffect(getContext()); + private EdgeEffect mEdgeGlowBottom; /** * Position of the last motion event. @@ -213,6 +213,8 @@ public class ScrollView extends FrameLayout { public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mEdgeGlowTop = new EdgeEffect(context, attrs); + mEdgeGlowBottom = new EdgeEffect(context, attrs); initScrollView(); final TypedArray a = context.obtainStyledAttributes( @@ -679,7 +681,15 @@ public class ScrollView extends FrameLayout { * isFinished() is correct. */ mScroller.computeScrollOffset(); - mIsBeingDragged = !mScroller.isFinished(); + mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowBottom.isFinished() + || !mEdgeGlowTop.isFinished(); + // Catch the edge effect if it is active. + if (!mEdgeGlowTop.isFinished()) { + mEdgeGlowTop.onPullDistance(0f, ev.getX() / getWidth()); + } + if (!mEdgeGlowBottom.isFinished()) { + mEdgeGlowBottom.onPullDistance(0f, 1f - ev.getX() / getWidth()); + } if (mIsBeingDragged && mScrollStrictSpan == null) { mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll"); } @@ -732,7 +742,8 @@ public class ScrollView extends FrameLayout { if (getChildCount() == 0) { return false; } - if ((mIsBeingDragged = !mScroller.isFinished())) { + if ((mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowTop.isFinished() + || !mEdgeGlowBottom.isFinished())) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); @@ -793,6 +804,21 @@ public class ScrollView extends FrameLayout { boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); + final float displacement = ev.getX(activePointerIndex) / getWidth(); + if (canOverscroll) { + int consumed = 0; + if (deltaY < 0 && mEdgeGlowBottom.getDistance() != 0f) { + consumed = Math.round(getHeight() + * mEdgeGlowBottom.onPullDistance((float) deltaY / getHeight(), + 1 - displacement)); + } else if (deltaY > 0 && mEdgeGlowTop.getDistance() != 0f) { + consumed = Math.round(-getHeight() + * mEdgeGlowTop.onPullDistance((float) -deltaY / getHeight(), + displacement)); + } + deltaY -= consumed; + } + // Calling overScrollBy will call onOverScrolled, which // calls onScrollChanged if applicable. if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true) @@ -807,17 +833,17 @@ public class ScrollView extends FrameLayout { mLastMotionY -= mScrollOffset[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; - } else if (canOverscroll) { + } else if (canOverscroll && deltaY != 0f) { final int pulledToY = oldY + deltaY; if (pulledToY < 0) { - mEdgeGlowTop.onPull((float) deltaY / getHeight(), - ev.getX(activePointerIndex) / getWidth()); + mEdgeGlowTop.onPullDistance((float) -deltaY / getHeight(), + displacement); if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } } else if (pulledToY > range) { - mEdgeGlowBottom.onPull((float) deltaY / getHeight(), - 1.f - ev.getX(activePointerIndex) / getWidth()); + mEdgeGlowBottom.onPullDistance((float) deltaY / getHeight(), + 1.f - displacement); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ba43ee79e6f3..fbc1d4fe0ea2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4372,6 +4372,12 @@ <permission android:name="android.permission.POWER_SAVER" android:protectionLevel="signature|privileged" /> + <!-- Allows providing the system with battery predictions. + Superseded by DEVICE_POWER permission. @hide @SystemApi + --> + <permission android:name="android.permission.BATTERY_PREDICTION" + android:protectionLevel="signature|privileged" /> + <!-- Allows access to the PowerManager.userActivity function. <p>Not for use by third-party applications. @hide @SystemApi --> <permission android:name="android.permission.USER_ACTIVITY" @@ -5477,6 +5483,9 @@ <permission android:name="android.permission.SIGNAL_REBOOT_READINESS" android:protectionLevel="signature|privileged" /> + <!-- @hide Allows an application to get a People Tile preview for a given shortcut. --> + <permission android:name="android.permission.GET_PEOPLE_TILE_PREVIEW" + android:protectionLevel="signature|recents" /> <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> diff --git a/core/res/res/drawable/btn_borderless_material.xml b/core/res/res/drawable/btn_borderless_material.xml index 08e1060ebad3..1a0912ee47dc 100644 --- a/core/res/res/drawable/btn_borderless_material.xml +++ b/core/res/res/drawable/btn_borderless_material.xml @@ -15,7 +15,8 @@ --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="?attr/colorControlHighlight"> + android:color="?attr/colorControlHighlight" + android:rippleStyle="?attr/rippleStyle"> <item android:id="@id/mask" android:drawable="@drawable/btn_default_mtrl_shape" /> </ripple> diff --git a/core/res/res/drawable/btn_colored_material.xml b/core/res/res/drawable/btn_colored_material.xml index 7ba21e840a59..5274ef2f6dce 100644 --- a/core/res/res/drawable/btn_colored_material.xml +++ b/core/res/res/drawable/btn_colored_material.xml @@ -19,7 +19,8 @@ android:insetTop="@dimen/button_inset_vertical_material" android:insetRight="@dimen/button_inset_horizontal_material" android:insetBottom="@dimen/button_inset_vertical_material"> - <ripple android:color="?attr/colorControlHighlight"> + <ripple android:color="?attr/colorControlHighlight" + android:rippleStyle="?attr/rippleStyle"> <item> <shape android:shape="rectangle" android:tint="@color/btn_colored_background_material"> diff --git a/core/res/res/drawable/btn_default_material.xml b/core/res/res/drawable/btn_default_material.xml index ed2b5aacb236..6a9e62110ba4 100644 --- a/core/res/res/drawable/btn_default_material.xml +++ b/core/res/res/drawable/btn_default_material.xml @@ -15,6 +15,7 @@ --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="?attr/colorControlHighlight"> + android:color="?attr/colorControlHighlight" + android:rippleStyle="?attr/rippleStyle"> <item android:drawable="@drawable/btn_default_mtrl_shape" /> </ripple> diff --git a/core/res/res/drawable/btn_toggle_material.xml b/core/res/res/drawable/btn_toggle_material.xml index 8b19e4ae3561..7cee3820172b 100644 --- a/core/res/res/drawable/btn_toggle_material.xml +++ b/core/res/res/drawable/btn_toggle_material.xml @@ -21,7 +21,8 @@ android:insetBottom="@dimen/button_inset_vertical_material"> <layer-list android:paddingMode="stack"> <item> - <ripple android:color="?attr/colorControlHighlight"> + <ripple android:color="?attr/colorControlHighlight" + android:rippleStyle="?attr/rippleStyle"> <item> <shape android:shape="rectangle" android:tint="?attr/colorButtonNormal"> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 927a8b49c17e..67b810e4ebdf 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -6389,6 +6389,13 @@ <!-- The radius of the ripple when fully expanded. By default, the radius is computed based on the size of the ripple's container. --> <attr name="radius" /> + <!-- The style of the ripple drawable is solid by default --> + <attr name="rippleStyle"> + <!-- Solid is the default style --> + <enum name="solid" value="0" /> + <!-- Patterned style--> + <enum name="patterned" value="1" /> + </attr> </declare-styleable> <declare-styleable name="ScaleDrawable"> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 5546621b6ee8..e7c4947bed5f 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -245,19 +245,19 @@ <color name="system_main_0">#ffffff</color> <!-- Shade of the main system color at 95% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_50">#f0f0f0</color> + <color name="system_main_50">#f2f2f2</color> <!-- Shade of the main system color at 90% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_100">#e2e2e2</color> + <color name="system_main_100">#e3e3e3</color> <!-- Shade of the main system color at 80% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_200">#c6c6c6</color> + <color name="system_main_200">#c7c7c7</color> <!-- Shade of the main system color at 70% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_main_300">#ababab</color> <!-- Shade of the main system color at 60% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_400">#909090</color> + <color name="system_main_400">#8f8f8f</color> <!-- Shade of the main system color at 50% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_main_500">#757575</color> @@ -266,13 +266,13 @@ <color name="system_main_600">#5e5e5e</color> <!-- Shade of the main system color at 30% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_700">#464646</color> + <color name="system_main_700">#474747</color> <!-- Shade of the main system color at 20% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_main_800">#303030</color> <!-- Shade of the main system color at 10% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_900">#1b1b1b</color> + <color name="system_main_900">#1f1f1f</color> <!-- Darkest shade of the main color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_main_1000">#000000</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b7c755ef7f6b..dd048f3e0993 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1946,6 +1946,8 @@ <string name="config_systemAutomotiveProjection" translatable="false"></string> <!-- The name of the package that will hold the system cluster service role. --> <string name="config_systemAutomotiveCluster" translatable="false"></string> + <!-- The name of the package that will hold the system shell role. --> + <string name="config_systemShell" translatable="false">com.android.shell</string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false"></string> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index ca40564cbe5b..fdef08bc5faa 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3071,6 +3071,7 @@ <public name="windowSplashScreenAnimationDuration"/> <public name="windowSplashScreenBrandingImage"/> <public name="splashScreenTheme" /> + <public name="rippleStyle" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> @@ -3131,6 +3132,8 @@ <public name="config_systemAutomotiveCluster" /> <!-- @hide @SystemApi @TestApi --> <public name="config_systemAutomotiveProjection" /> + <!-- @hide @SystemApi --> + <public name="config_systemShell" /> </public-group> <public-group type="id" first-id="0x01020055"> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index ce4ee87e52a0..e7e049da18c9 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -224,6 +224,9 @@ easier. <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorForegroundInverse">@color/foreground_device_default_light</item> + + <!-- Ripple style--> + <item name="rippleStyle">solid</item> </style> <style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" /> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 403dc65abbf5..fe4d0cf80ee0 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -316,6 +316,8 @@ applications that come with the platform <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/> <permission name="android.permission.ACCESS_LOWPAN_STATE"/> <permission name="android.permission.BACKUP"/> + <!-- Needed for test only --> + <permission name="android.permission.BATTERY_PREDICTION"/> <permission name="android.permission.BATTERY_STATS"/> <permission name="android.permission.BIND_APPWIDGET"/> <permission name="android.permission.CHANGE_APP_IDLE_STATE"/> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index ded4a276880f..84da930aac54 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1075,12 +1075,6 @@ "group": "WM_DEBUG_IME", "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java" }, - "-856025122": { - "message": "SURFACE transparentRegionHint=%s: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "-855366859": { "message": " merging children in from %s: %s", "level": "VERBOSE", @@ -2821,12 +2815,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "1217926207": { - "message": "Activity not running, resuming next.", - "level": "VERBOSE", - "group": "WM_DEBUG_STATES", - "at": "com\/android\/server\/wm\/Task.java" - }, "1219600119": { "message": "addWindow: win=%s Callers=%s", "level": "DEBUG", @@ -3331,6 +3319,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/Task.java" }, + "1847414670": { + "message": "Activity not running or entered PiP, resuming next.", + "level": "VERBOSE", + "group": "WM_DEBUG_STATES", + "at": "com\/android\/server\/wm\/Task.java" + }, "1853793312": { "message": "Notify removed startingWindow %s", "level": "VERBOSE", diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java new file mode 100644 index 000000000000..80f65f919fa6 --- /dev/null +++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.drawable; + +import android.animation.Animator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Canvas; +import android.graphics.CanvasProperty; +import android.graphics.Paint; +import android.graphics.RecordingCanvas; +import android.graphics.animation.RenderNodeAnimator; +import android.util.ArraySet; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; + +import java.util.function.Consumer; + +/** + * @hide + */ +public final class RippleAnimationSession { + private static final int ENTER_ANIM_DURATION = 350; + private static final int EXIT_ANIM_OFFSET = ENTER_ANIM_DURATION; + private static final int EXIT_ANIM_DURATION = 350; + private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); + // Matches R.interpolator.fast_out_slow_in but as we have no context we can't just import that + private static final TimeInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); + + private Consumer<RippleAnimationSession> mOnSessionEnd; + private AnimationProperties<Float, Paint> mProperties; + private AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> mCanvasProperties; + private Runnable mOnUpdate; + private long mStartTime; + private boolean mForceSoftware; + private ArraySet<Animator> mActiveAnimations = new ArraySet(3); + + RippleAnimationSession(@NonNull AnimationProperties<Float, Paint> properties, + boolean forceSoftware) { + mProperties = properties; + mForceSoftware = forceSoftware; + } + + void end() { + for (Animator anim: mActiveAnimations) { + if (anim != null) anim.end(); + } + mActiveAnimations.clear(); + } + + @NonNull RippleAnimationSession enter(Canvas canvas) { + if (isHwAccelerated(canvas)) { + enterHardware((RecordingCanvas) canvas); + } else { + enterSoftware(); + } + mStartTime = System.nanoTime(); + return this; + } + + @NonNull RippleAnimationSession exit(Canvas canvas) { + if (isHwAccelerated(canvas)) exitHardware((RecordingCanvas) canvas); + else exitSoftware(); + return this; + } + + private void onAnimationEnd(Animator anim) { + mActiveAnimations.remove(anim); + } + + @NonNull RippleAnimationSession setOnSessionEnd( + @Nullable Consumer<RippleAnimationSession> onSessionEnd) { + mOnSessionEnd = onSessionEnd; + return this; + } + + RippleAnimationSession setOnAnimationUpdated(@Nullable Runnable run) { + mOnUpdate = run; + mProperties.setOnChange(mOnUpdate); + return this; + } + + private boolean isHwAccelerated(Canvas canvas) { + return canvas.isHardwareAccelerated() && !mForceSoftware; + } + + private void exitSoftware() { + ValueAnimator expand = ValueAnimator.ofFloat(.5f, 1f); + expand.setDuration(EXIT_ANIM_DURATION); + expand.setStartDelay(computeDelay()); + expand.addUpdateListener(updatedAnimation -> { + notifyUpdate(); + mProperties.getShader().setProgress((Float) expand.getAnimatedValue()); + }); + expand.addListener(new AnimatorListener(this) { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + Consumer<RippleAnimationSession> onEnd = mOnSessionEnd; + if (onEnd != null) onEnd.accept(RippleAnimationSession.this); + } + }); + expand.setInterpolator(LINEAR_INTERPOLATOR); + expand.start(); + mActiveAnimations.add(expand); + } + + private long computeDelay() { + long currentTime = System.nanoTime(); + long timePassed = (currentTime - mStartTime) / 1_000_000; + long difference = EXIT_ANIM_OFFSET; + return Math.max(difference - timePassed, 0); + } + private void notifyUpdate() { + Runnable onUpdate = mOnUpdate; + if (onUpdate != null) onUpdate.run(); + } + + RippleAnimationSession setForceSoftwareAnimation(boolean forceSw) { + mForceSoftware = forceSw; + return this; + } + + + private void exitHardware(RecordingCanvas canvas) { + AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> + props = getCanvasProperties(); + RenderNodeAnimator exit = + new RenderNodeAnimator(props.getProgress(), 1f); + exit.setDuration(EXIT_ANIM_DURATION); + exit.addListener(new AnimatorListener(this) { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + Consumer<RippleAnimationSession> onEnd = mOnSessionEnd; + if (onEnd != null) onEnd.accept(RippleAnimationSession.this); + } + }); + exit.setTarget(canvas); + exit.setInterpolator(DECELERATE_INTERPOLATOR); + + long delay = computeDelay(); + exit.setStartDelay(delay); + exit.start(); + mActiveAnimations.add(exit); + } + + private void enterHardware(RecordingCanvas can) { + AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> + props = getCanvasProperties(); + RenderNodeAnimator expand = + new RenderNodeAnimator(props.getProgress(), .5f); + expand.setTarget(can); + expand.setDuration(ENTER_ANIM_DURATION); + expand.addListener(new AnimatorListener(this)); + expand.setInterpolator(LINEAR_INTERPOLATOR); + expand.start(); + mActiveAnimations.add(expand); + } + + private void enterSoftware() { + ValueAnimator expand = ValueAnimator.ofFloat(0f, 0.5f); + expand.addUpdateListener(updatedAnimation -> { + notifyUpdate(); + mProperties.getShader().setProgress((Float) expand.getAnimatedValue()); + }); + expand.addListener(new AnimatorListener(this)); + expand.setInterpolator(LINEAR_INTERPOLATOR); + expand.start(); + mActiveAnimations.add(expand); + } + + @NonNull AnimationProperties<Float, Paint> getProperties() { + return mProperties; + } + + @NonNull AnimationProperties getCanvasProperties() { + if (mCanvasProperties == null) { + mCanvasProperties = new AnimationProperties<>( + CanvasProperty.createFloat(mProperties.getX()), + CanvasProperty.createFloat(mProperties.getY()), + CanvasProperty.createFloat(mProperties.getMaxRadius()), + CanvasProperty.createPaint(mProperties.getPaint()), + CanvasProperty.createFloat(mProperties.getProgress()), + mProperties.getShader()); + } + return mCanvasProperties; + } + + private static class AnimatorListener implements Animator.AnimatorListener { + private final RippleAnimationSession mSession; + + AnimatorListener(RippleAnimationSession session) { + mSession = session; + } + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + mSession.onAnimationEnd(animation); + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + } + + static class AnimationProperties<FloatType, PaintType> { + private final FloatType mY; + private FloatType mProgress; + private FloatType mMaxRadius; + private final PaintType mPaint; + private final FloatType mX; + private final RippleShader mShader; + private Runnable mOnChange; + + private void onChange() { + if (mOnChange != null) mOnChange.run(); + } + + private void setOnChange(Runnable onChange) { + mOnChange = onChange; + } + + AnimationProperties(FloatType x, FloatType y, FloatType maxRadius, + PaintType paint, FloatType progress, RippleShader shader) { + mY = y; + mX = x; + mMaxRadius = maxRadius; + mPaint = paint; + mShader = shader; + mProgress = progress; + } + + FloatType getProgress() { + return mProgress; + } + + FloatType getX() { + return mX; + } + + FloatType getY() { + return mY; + } + + FloatType getMaxRadius() { + return mMaxRadius; + } + + PaintType getPaint() { + return mPaint; + } + + RippleShader getShader() { + return mShader; + } + } +} diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index bab80cee2b26..5024875aab3c 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -16,6 +16,14 @@ package android.graphics.drawable; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.animation.ValueAnimator; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -27,17 +35,21 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; +import android.graphics.CanvasProperty; import android.graphics.Color; +import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.Shader; import android.os.Build; import android.util.AttributeSet; +import android.view.animation.LinearInterpolator; import com.android.internal.R; @@ -45,6 +57,9 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.ArrayList; import java.util.Arrays; /** @@ -94,6 +109,17 @@ import java.util.Arrays; * </pre> * * @attr ref android.R.styleable#RippleDrawable_color + * + * To change the ripple style, assign the value of "solid" or "patterned" to the android:rippleStyle + * attribute. + * + * <pre> + * <code><!-- A red ripple masked against an opaque rectangle. --/> + * <ripple android:rippleStyle="patterned"> + * </ripple></code> + * </pre> + * + * @attr ref android.R.styleable#RippleDrawable_rippleStyle */ public class RippleDrawable extends LayerDrawable { /** @@ -102,6 +128,29 @@ public class RippleDrawable extends LayerDrawable { */ public static final int RADIUS_AUTO = -1; + /** + * Ripple style where a solid circle is drawn. This is also the default style + * @see #setRippleStyle(int) + */ + public static final int STYLE_SOLID = 0; + /** + * Ripple style where a circle shape with a patterned, + * noisy interior expands from the hotspot to the bounds". + * @see #setRippleStyle(int) + */ + public static final int STYLE_PATTERNED = 1; + + /** + * Ripple drawing style + * @hide + */ + @Retention(SOURCE) + @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD}) + @IntDef({STYLE_SOLID, STYLE_PATTERNED}) + public @interface RippleStyle { + } + + private static final int BACKGROUND_OPACITY_DURATION = 80; private static final int MASK_UNKNOWN = -1; private static final int MASK_NONE = 0; private static final int MASK_CONTENT = 1; @@ -109,6 +158,7 @@ public class RippleDrawable extends LayerDrawable { /** The maximum number of ripples supported. */ private static final int MAX_RIPPLES = 10; + private static final LinearInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private final Rect mTempRect = new Rect(); @@ -172,6 +222,14 @@ public class RippleDrawable extends LayerDrawable { */ private boolean mForceSoftware; + // Patterned + private float mTargetBackgroundOpacity; + private ValueAnimator mBackgroundAnimation; + private float mBackgroundOpacity; + private boolean mRunBackgroundAnimation; + private boolean mExitingAnimation; + private ArrayList<RippleAnimationSession> mRunningAnimations = new ArrayList<>(); + /** * Constructor used for drawable inflation. */ @@ -235,7 +293,7 @@ public class RippleDrawable extends LayerDrawable { Arrays.fill(ripples, 0, count, null); } mExitingRipplesCount = 0; - + mExitingAnimation = true; // Always draw an additional "clean" frame after canceling animations. invalidateSelf(false); } @@ -276,21 +334,37 @@ public class RippleDrawable extends LayerDrawable { private void setRippleActive(boolean active) { if (mRippleActive != active) { mRippleActive = active; + } + if (mState.mRippleStyle == STYLE_SOLID) { if (active) { tryRippleEnter(); } else { tryRippleExit(); } + } else { + if (active) { + startPatternedAnimation(); + } else { + exitPatternedAnimation(); + } } } private void setBackgroundActive(boolean hovered, boolean focused, boolean pressed) { - if (mBackground == null && (hovered || focused)) { - mBackground = new RippleBackground(this, mHotspotBounds, isBounded()); - mBackground.setup(mState.mMaxRadius, mDensity); - } - if (mBackground != null) { - mBackground.setState(focused, hovered, pressed); + if (mState.mRippleStyle == STYLE_SOLID) { + if (mBackground == null && (hovered || focused)) { + mBackground = new RippleBackground(this, mHotspotBounds, isBounded()); + mBackground.setup(mState.mMaxRadius, mDensity); + } + if (mBackground != null) { + mBackground.setState(focused, hovered, pressed); + } + } else { + if (focused || hovered) { + enterPatternedBackgroundAnimation(focused, hovered); + } else { + exitPatternedBackgroundAnimation(); + } } } @@ -317,6 +391,9 @@ public class RippleDrawable extends LayerDrawable { mRipple.onBoundsChange(); } + mState.mMaxRadius = mState.mMaxRadius <= 0 && mState.mRippleStyle != STYLE_SOLID + ? (int) computeRadius() + : mState.mMaxRadius; invalidateSelf(); } @@ -330,7 +407,11 @@ public class RippleDrawable extends LayerDrawable { // If we just became visible, ensure the background and ripple // visibilities are consistent with their internal states. if (mRippleActive) { - tryRippleEnter(); + if (mState.mRippleStyle == STYLE_SOLID) { + tryRippleEnter(); + } else { + invalidateSelf(); + } } // Skip animations, just show the correct final states. @@ -489,6 +570,8 @@ public class RippleDrawable extends LayerDrawable { mState.mMaxRadius = a.getDimensionPixelSize( R.styleable.RippleDrawable_radius, mState.mMaxRadius); + + mState.mRippleStyle = a.getInteger(R.styleable.RippleDrawable_rippleStyle, STYLE_SOLID); } private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { @@ -535,9 +618,9 @@ public class RippleDrawable extends LayerDrawable { @Override public void setHotspot(float x, float y) { + mPendingX = x; + mPendingY = y; if (mRipple == null || mBackground == null) { - mPendingX = x; - mPendingY = y; mHasPending = true; } @@ -665,6 +748,14 @@ public class RippleDrawable extends LayerDrawable { */ @Override public void draw(@NonNull Canvas canvas) { + if (mState.mRippleStyle == STYLE_SOLID) { + drawSolid(canvas); + } else { + drawPatterned(canvas); + } + } + + private void drawSolid(Canvas canvas) { pruneRipples(); // Clip to the dirty bounds, which will be the drawable bounds if we @@ -681,6 +772,178 @@ public class RippleDrawable extends LayerDrawable { canvas.restoreToCount(saveCount); } + private void exitPatternedBackgroundAnimation() { + mTargetBackgroundOpacity = 0; + if (mBackgroundAnimation != null) mBackgroundAnimation.cancel(); + // after cancel + mRunBackgroundAnimation = true; + invalidateSelf(false); + } + + private void startPatternedAnimation() { + mRippleActive = true; + invalidateSelf(false); + } + + private void exitPatternedAnimation() { + mExitingAnimation = true; + invalidateSelf(false); + } + + private void enterPatternedBackgroundAnimation(boolean focused, boolean hovered) { + mBackgroundOpacity = 0; + mTargetBackgroundOpacity = focused ? .6f : hovered ? .2f : 0f; + if (mBackgroundAnimation != null) mBackgroundAnimation.cancel(); + // after cancel + mRunBackgroundAnimation = true; + invalidateSelf(false); + } + + private void startBackgroundAnimation() { + mRunBackgroundAnimation = false; + mBackgroundAnimation = ValueAnimator.ofFloat(mBackgroundOpacity, mTargetBackgroundOpacity); + mBackgroundAnimation.setInterpolator(LINEAR_INTERPOLATOR); + mBackgroundAnimation.setDuration(BACKGROUND_OPACITY_DURATION); + mBackgroundAnimation.addUpdateListener(update -> { + mBackgroundOpacity = (float) update.getAnimatedValue(); + invalidateSelf(false); + }); + mBackgroundAnimation.start(); + } + + private void drawPatterned(@NonNull Canvas canvas) { + final Rect bounds = getBounds(); + final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); + boolean useCanvasProps = shouldUseCanvasProps(canvas); + boolean changedHotspotBounds = !bounds.equals(mHotspotBounds); + if (isBounded()) { + canvas.clipRect(mHotspotBounds); + } + float x, y; + if (changedHotspotBounds) { + x = mHotspotBounds.exactCenterX(); + y = mHotspotBounds.exactCenterY(); + useCanvasProps = false; + } else { + x = mPendingX; + y = mPendingY; + } + boolean shouldAnimate = mRippleActive; + boolean shouldExit = mExitingAnimation; + mRippleActive = false; + mExitingAnimation = false; + getRipplePaint(); + drawContent(canvas); + drawPatternedBackground(canvas); + if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) { + RippleAnimationSession.AnimationProperties<Float, Paint> properties = + createAnimationProperties(x, y); + mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps) + .setOnAnimationUpdated(useCanvasProps ? null : () -> invalidateSelf(false)) + .setOnSessionEnd(session -> { + mRunningAnimations.remove(session); + }) + .setForceSoftwareAnimation(!useCanvasProps) + .enter(canvas)); + } + if (shouldExit) { + for (int i = 0; i < mRunningAnimations.size(); i++) { + RippleAnimationSession s = mRunningAnimations.get(i); + s.exit(canvas); + } + } + for (int i = 0; i < mRunningAnimations.size(); i++) { + RippleAnimationSession s = mRunningAnimations.get(i); + if (useCanvasProps) { + RippleAnimationSession.AnimationProperties<CanvasProperty<Float>, + CanvasProperty<Paint>> + p = s.getCanvasProperties(); + RecordingCanvas can = (RecordingCanvas) canvas; + can.drawRipple(p.getX(), p.getY(), p.getMaxRadius(), p.getPaint(), + p.getProgress(), p.getShader()); + } else { + RippleAnimationSession.AnimationProperties<Float, Paint> p = + s.getProperties(); + float posX, posY; + if (changedHotspotBounds) { + posX = x; + posY = y; + if (p.getPaint().getShader() instanceof RippleShader) { + RippleShader shader = (RippleShader) p.getPaint().getShader(); + shader.setOrigin(posX, posY); + } + } else { + posX = p.getX(); + posY = p.getY(); + } + float radius = p.getMaxRadius(); + canvas.drawCircle(posX, posY, radius, p.getPaint()); + } + } + canvas.restoreToCount(saveCount); + } + + private void drawPatternedBackground(Canvas c) { + if (mRunBackgroundAnimation) { + startBackgroundAnimation(); + } + if (mBackgroundOpacity == 0) return; + Paint p = mRipplePaint; + float newOpacity = mBackgroundOpacity; + final int origAlpha = p.getAlpha(); + final int alpha = Math.min((int) (origAlpha * newOpacity + 0.5f), 255); + if (alpha > 0) { + ColorFilter origFilter = p.getColorFilter(); + p.setColorFilter(mMaskColorFilter); + p.setAlpha(alpha); + Rect b = mHotspotBounds; + c.drawCircle(b.centerX(), b.centerY(), mState.mMaxRadius, p); + p.setAlpha(origAlpha); + p.setColorFilter(origFilter); + } + } + + private float computeRadius() { + Rect b = getDirtyBounds(); + float gap = 0; + float radius = (float) Math.sqrt(b.width() * b.width() + b.height() * b.height()) / 2 + gap; + return radius; + } + + @NonNull + private RippleAnimationSession.AnimationProperties<Float, Paint> createAnimationProperties( + float x, float y) { + Paint p = new Paint(mRipplePaint); + float radius = mState.mMaxRadius; + RippleAnimationSession.AnimationProperties<Float, Paint> properties; + RippleShader shader = new RippleShader(); + int color = mMaskColorFilter == null + ? mState.mColor.getColorForState(getState(), Color.BLACK) + : mMaskColorFilter.getColor(); + color = color | 0xFF000000; + shader.setColor(color); + shader.setOrigin(x, y); + shader.setRadius(radius); + shader.setProgress(.0f); + properties = new RippleAnimationSession.AnimationProperties<>( + x, y, radius, p, 0f, + shader); + if (mMaskShader == null) { + shader.setHasMask(false); + } else { + shader.setShader(mMaskShader); + shader.setHasMask(true); + } + p.setShader(shader); + p.setColorFilter(null); + p.setColor(color); + return properties; + } + + private boolean shouldUseCanvasProps(Canvas c) { + return !mForceSoftware && c.isHardwareAccelerated(); + } + @Override public void invalidateSelf() { invalidateSelf(true); @@ -774,18 +1037,23 @@ public class RippleDrawable extends LayerDrawable { // Draw the appropriate mask anchored to (0,0). final int left = bounds.left; final int top = bounds.top; - mMaskCanvas.translate(-left, -top); + if (mState.mRippleStyle == STYLE_SOLID) { + mMaskCanvas.translate(-left, -top); + } if (maskType == MASK_EXPLICIT) { drawMask(mMaskCanvas); } else if (maskType == MASK_CONTENT) { drawContent(mMaskCanvas); } - mMaskCanvas.translate(left, top); + if (mState.mRippleStyle == STYLE_SOLID) { + mMaskCanvas.translate(left, top); + } } private int getMaskType() { if (mRipple == null && mExitingRipplesCount <= 0 - && (mBackground == null || !mBackground.isVisible())) { + && (mBackground == null || !mBackground.isVisible()) + && mState.mRippleStyle == STYLE_SOLID) { // We might need a mask later. return MASK_UNKNOWN; } @@ -874,7 +1142,7 @@ public class RippleDrawable extends LayerDrawable { updateMaskShaderIfNeeded(); // Position the shader to account for canvas translation. - if (mMaskShader != null) { + if (mMaskShader != null && mState.mRippleStyle == STYLE_SOLID) { final Rect bounds = getBounds(); mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y); mMaskShader.setLocalMatrix(mMaskMatrix); @@ -973,6 +1241,31 @@ public class RippleDrawable extends LayerDrawable { return this; } + /** + * Sets the visual style of the ripple. + * + * @see #STYLE_SOLID + * @see #STYLE_PATTERNED + * + * @param style The style of the ripple + */ + public void setRippleStyle(@RippleStyle int style) throws IllegalArgumentException { + if (style == STYLE_SOLID || style == STYLE_PATTERNED) { + mState.mRippleStyle = style; + } else { + throw new IllegalArgumentException("Invalid style value " + style); + } + } + + /** + * Get the current ripple style + * @return Ripple style + */ + public @RippleStyle int getRippleStyle() { + return mState.mRippleStyle; + } + + @Override RippleState createConstantState(LayerState state, Resources res) { return new RippleState(state, this, res); @@ -983,6 +1276,7 @@ public class RippleDrawable extends LayerDrawable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA); int mMaxRadius = RADIUS_AUTO; + int mRippleStyle = STYLE_SOLID; public RippleState(LayerState orig, RippleDrawable owner, Resources res) { super(orig, owner, res); @@ -992,6 +1286,7 @@ public class RippleDrawable extends LayerDrawable { mTouchThemeAttrs = origs.mTouchThemeAttrs; mColor = origs.mColor; mMaxRadius = origs.mMaxRadius; + mRippleStyle = origs.mRippleStyle; if (origs.mDensity != mDensity) { applyDensityScaling(orig.mDensity, mDensity); diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java new file mode 100644 index 000000000000..500efdd84854 --- /dev/null +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.drawable; + +import android.annotation.ColorInt; +import android.annotation.NonNull; +import android.graphics.Color; +import android.graphics.RuntimeShader; +import android.graphics.Shader; + +final class RippleShader extends RuntimeShader { + private static final String SHADER = "uniform float2 in_origin;\n" + + "uniform float in_maxRadius;\n" + + "uniform float in_progress;\n" + + "uniform float in_hasMask;\n" + + "uniform float4 in_color;\n" + + "uniform shader in_shader;\n" + + "float dist2(float2 p0, float2 pf) { return sqrt((pf.x - p0.x) * (pf.x - p0.x) + " + + "(pf.y - p0.y) * (pf.y - p0.y)); }\n" + + "float mod2(float a, float b) { return a - (b * floor(a / b)); }\n" + + "float rand(float2 src) { return fract(sin(dot(src.xy, float2(12.9898, 78.233))) * " + + "43758.5453123); }\n" + + "float4 main(float2 p)\n" + + "{\n" + + " float fraction = in_progress;\n" + + " float2 fragCoord = p;//sk_FragCoord.xy;\n" + + " float maxDist = in_maxRadius;\n" + + " float fragDist = dist2(in_origin, fragCoord.xy);\n" + + " float circleRadius = maxDist * fraction;\n" + + " float colorVal = (fragDist - circleRadius) / maxDist;\n" + + " float d = fragDist < circleRadius \n" + + " ? 1. - abs(colorVal * 3. * smoothstep(0., 1., fraction)) \n" + + " : 1. - abs(colorVal * 5.);\n" + + " d = smoothstep(0., 1., d);\n" + + " float divider = 2.;\n" + + " float x = floor(fragCoord.x / divider);\n" + + " float y = floor(fragCoord.y / divider);\n" + + " float density = .95;\n" + + " d = rand(float2(x, y)) > density ? d : d * .2;\n" + + " d = d * rand(float2(fraction, x * y));\n" + + " float alpha = 1. - pow(fraction, 2.);\n" + + " if (in_hasMask != 0.) {return sample(in_shader).a * in_color * d * alpha;}\n" + + " return in_color * d * alpha;\n" + + "}\n"; + + RippleShader() { + super(SHADER, false); + } + + public void setShader(@NonNull Shader s) { + setInputShader("in_shader", s); + } + + public void setRadius(float radius) { + setUniform("in_maxRadius", radius); + } + + public void setOrigin(float x, float y) { + setUniform("in_origin", new float[] {x, y}); + } + + public void setProgress(float progress) { + setUniform("in_progress", progress); + } + + public void setHasMask(boolean hasMask) { + setUniform("in_hasMask", hasMask ? 1 : 0); + } + + public void setColor(@ColorInt int colorIn) { + Color color = Color.valueOf(colorIn); + this.setUniform("in_color", new float[] {color.red(), + color.green(), color.blue(), color.alpha()}); + } +} 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 ae5300502993..725f87d93e4e 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 @@ -375,17 +375,29 @@ public class PhonePipMenuController implements PipMenuController { } /** - * Hides the menu activity. + * Hides the menu view. */ public void hideMenu() { + hideMenu(true /* animate */, true /* resize */); + } + + /** + * Hides the menu view. + * + * @param animate whether to animate the menu fadeout + * @param resize whether or not to resize the PiP with the state change + */ + public void hideMenu(boolean animate, boolean resize) { final boolean isMenuVisible = isMenuVisible(); if (DEBUG) { Log.d(TAG, "hideMenu() state=" + mMenuState + " isMenuVisible=" + isMenuVisible + + " animate=" + animate + + " resize=" + resize + " callers=\n" + Debug.getCallers(5, " ")); } if (isMenuVisible) { - mPipMenuView.hideMenu(); + mPipMenuView.hideMenu(animate, resize); } } @@ -404,15 +416,6 @@ public class PhonePipMenuController implements PipMenuController { } /** - * Preemptively mark the menu as invisible, used when we are directly manipulating the pinned - * stack and don't want to trigger a resize which can animate the stack in a conflicting way - * (ie. when manually expanding or dismissing). - */ - public void hideMenuWithoutResize() { - onMenuStateChanged(MENU_STATE_NONE, false /* resize */, null /* callback */); - } - - /** * Sets the menu actions to the actions provided by the current PiP menu. */ @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 962c4672644a..1cf3a48e9575 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -89,7 +89,6 @@ public class PipMenuView extends FrameLayout { private static final boolean ENABLE_RESIZE_HANDLE = false; private int mMenuState; - private boolean mResize = true; private boolean mAllowMenuTimeout = true; private boolean mAllowTouches = true; @@ -329,16 +328,21 @@ public class PipMenuView extends FrameLayout { hideMenu(null); } + void hideMenu(boolean animate, boolean resize) { + hideMenu(null, true /* notifyMenuVisibility */, animate, resize); + } + void hideMenu(Runnable animationEndCallback) { - hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */); + hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */, + true /* resize */); } private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility, - boolean animate) { + boolean animate, boolean resize) { if (mMenuState != MENU_STATE_NONE) { cancelDelayedHide(); if (notifyMenuVisibility) { - notifyMenuStateChange(MENU_STATE_NONE, mResize, null); + notifyMenuStateChange(MENU_STATE_NONE, resize, null); } mMenuContainerAnimator = new AnimatorSet(); ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, @@ -469,7 +473,8 @@ public class PipMenuView extends FrameLayout { private void expandPip() { // Do not notify menu visibility when hiding the menu, the controller will do this when it // handles the message - hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */); + hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */, + true /* resize */); } private void dismissPip() { @@ -479,7 +484,8 @@ public class PipMenuView extends FrameLayout { final boolean animate = mMenuState != MENU_STATE_CLOSE; // Do not notify menu visibility when hiding the menu, the controller will do this when it // handles the message - hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate); + hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate, + true /* resize */); } private void showSettings() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index b19dcae2def8..eae8945ce6be 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -17,8 +17,7 @@ package com.android.wm.shell.pip.phone; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; +import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -325,7 +324,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, + " callers=\n" + Debug.getCallers(5, " ")); } cancelPhysicsAnimation(); - mMenuController.hideMenuWithoutResize(); + mMenuController.hideMenu(false /* animate */, false /* resize */); mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION); } @@ -338,7 +337,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, " ")); } cancelPhysicsAnimation(); - mMenuController.hideMenuWithoutResize(); + mMenuController.hideMenu(true /* animate*/, false /* resize */); mPipTaskOrganizer.removePip(); } @@ -371,9 +370,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** * Stash PiP to the closest edge. We set velocityY to 0 to limit pure horizontal motion. */ - void stashToEdge(float velocityX, @Nullable Runnable postBoundsUpdateCallback) { - mPipBoundsState.setStashed(velocityX < 0 ? STASH_TYPE_LEFT : STASH_TYPE_RIGHT); - movetoTarget(velocityX, 0 /* velocityY */, postBoundsUpdateCallback, true /* isStash */); + void stashToEdge(float velX, float velY, @Nullable Runnable postBoundsUpdateCallback) { + velY = mPipBoundsState.getStashedState() == STASH_TYPE_NONE ? 0 : velY; + movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */); } private void movetoTarget( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 1ef9ffa494f4..78ee1868eee7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -106,7 +106,7 @@ public class PipResizeGestureHandler { // For pinch-resize private boolean mThresholdCrossed0; private boolean mThresholdCrossed1; - private boolean mUsingPinchToZoom = false; + private boolean mUsingPinchToZoom = true; private float mAngle = 0; int mFirstIndex = -1; int mSecondIndex = -1; @@ -139,7 +139,7 @@ public class PipResizeGestureHandler { mEnablePinchResize = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, PIP_PINCH_RESIZE, - /* defaultValue = */ false); + /* defaultValue = */ true); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor, new DeviceConfig.OnPropertiesChangedListener() { @@ -147,7 +147,7 @@ public class PipResizeGestureHandler { public void onPropertiesChanged(DeviceConfig.Properties properties) { if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) { mEnablePinchResize = properties.getBoolean( - PIP_PINCH_RESIZE, /* defaultValue = */ false); + PIP_PINCH_RESIZE, /* defaultValue = */ true); } } }); @@ -516,8 +516,8 @@ public class PipResizeGestureHandler { } if (mThresholdCrossed) { if (mPhonePipMenuController.isMenuVisible()) { - mPhonePipMenuController.hideMenuWithoutResize(); - mPhonePipMenuController.hideMenu(); + mPhonePipMenuController.hideMenu(false /* animate */, + false /* resize */); } final Rect currentPipBounds = mPipBoundsState.getBounds(); mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index e69c6f2f47bc..afc7b5294a2f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -19,7 +19,9 @@ package com.android.wm.shell.pip.phone; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; +import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT; import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE; import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL; import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE; @@ -63,8 +65,9 @@ import java.util.function.Consumer; * the PIP. */ public class PipTouchHandler { + @VisibleForTesting static final float MINIMUM_SIZE_PERCENT = 0.4f; + private static final String TAG = "PipTouchHandler"; - private static final float MINIMUM_SIZE_PERCENT = 0.4f; private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f; // Allow PIP to resize to a slightly bigger state upon touch @@ -809,7 +812,6 @@ public class PipTouchHandler { } if (touchState.startedDragging()) { - mPipBoundsState.setStashed(STASH_TYPE_NONE); mSavedSnapFraction = -1f; mPipDismissTargetHandler.showDismissTargetMaybe(); } @@ -862,10 +864,10 @@ public class PipTouchHandler { // Reset the touch state on up before the fling settles mTouchState.reset(); - if (mEnableStash && !mPipBoundsState.isStashed() - && shouldStash(vel, getPossiblyMotionBounds())) { - mMotionHelper.stashToEdge(vel.x, this::stashEndAction /* endAction */); + if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) { + mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */); } else { + mPipBoundsState.setStashed(STASH_TYPE_NONE); mMotionHelper.flingToSnapTarget(vel.x, vel.y, this::flingEndAction /* endAction */); } @@ -876,6 +878,9 @@ public class PipTouchHandler { < mPipBoundsState.getMaxSize().x && mPipBoundsState.getBounds().height() < mPipBoundsState.getMaxSize().y; + if (mMenuController.isMenuVisible()) { + mMenuController.hideMenu(false /* animate */, false /* resize */); + } if (toExpand) { mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); animateToMaximizedState(null); @@ -916,6 +921,11 @@ public class PipTouchHandler { && mPipExclusionBoundsChangeListener.get() != null) { mPipExclusionBoundsChangeListener.get().accept(mPipBoundsState.getBounds()); } + if (mPipBoundsState.getBounds().left < 0) { + mPipBoundsState.setStashed(STASH_TYPE_LEFT); + } else { + mPipBoundsState.setStashed(STASH_TYPE_RIGHT); + } } private void flingEndAction() { @@ -933,12 +943,12 @@ public class PipTouchHandler { private boolean shouldStash(PointF vel, Rect motionBounds) { // If user flings the PIP window above the minimum velocity, stash PIP. - // Only allow stashing to the edge if the user starts dragging the PIP from the - // opposite edge. + // Only allow stashing to the edge if PIP wasn't previously stashed on the opposite + // edge. final boolean stashFromFlingToEdge = ((vel.x < -mStashVelocityThreshold - && mDownSavedFraction > 1f && mDownSavedFraction < 2f) + && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) || (vel.x > mStashVelocityThreshold - && mDownSavedFraction > 3f && mDownSavedFraction < 4f)); + && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT)); // If User releases the PIP window while it's out of the display bounds, put // PIP into stashed mode. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 449ad88f6532..19930485047c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -22,16 +22,14 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.graphics.Point; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.util.Size; import androidx.test.filters.SmallTest; -import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PipBoundsAlgorithm; @@ -59,6 +57,9 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper(setAsMainLooper = true) public class PipTouchHandlerTest extends ShellTestCase { + private static final int INSET = 10; + private static final int PIP_LENGTH = 100; + private PipTouchHandler mPipTouchHandler; @Mock @@ -85,8 +86,9 @@ public class PipTouchHandlerTest extends ShellTestCase { private PipMotionHelper mMotionHelper; private PipResizeGestureHandler mPipResizeGestureHandler; + private DisplayLayout mDisplayLayout; private Rect mInsetBounds; - private Rect mMinBounds; + private Rect mPipBounds; private Rect mCurBounds; private boolean mFromImeAdjustment; private boolean mFromShelfAdjustment; @@ -109,12 +111,18 @@ public class PipTouchHandlerTest extends ShellTestCase { mPipTouchHandler.setPipMotionHelper(mMotionHelper); mPipTouchHandler.setPipResizeGestureHandler(mPipResizeGestureHandler); - // Assume a display of 1000 x 1000 - // inset of 10 - mInsetBounds = new Rect(10, 10, 990, 990); + mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay()); + mPipBoundsState.setDisplayLayout(mDisplayLayout); + mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET, + mPipBoundsState.getDisplayBounds().top + INSET, + mPipBoundsState.getDisplayBounds().right - INSET, + mPipBoundsState.getDisplayBounds().bottom - INSET); // minBounds of 100x100 bottom right corner - mMinBounds = new Rect(890, 890, 990, 990); - mCurBounds = new Rect(mMinBounds); + mPipBounds = new Rect(mPipBoundsState.getDisplayBounds().right - INSET - PIP_LENGTH, + mPipBoundsState.getDisplayBounds().bottom - INSET - PIP_LENGTH, + mPipBoundsState.getDisplayBounds().right - INSET, + mPipBoundsState.getDisplayBounds().bottom - INSET); + mCurBounds = new Rect(mPipBounds); mFromImeAdjustment = false; mFromShelfAdjustment = false; mDisplayRotation = 0; @@ -122,37 +130,23 @@ public class PipTouchHandlerTest extends ShellTestCase { } @Test - public void updateMovementBounds_minBounds() { - Rect expectedMinMovementBounds = new Rect(); - mPipBoundsAlgorithm.getMovementBounds(mMinBounds, mInsetBounds, expectedMinMovementBounds, + public void updateMovementBounds_minMaxBounds() { + final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(), + mPipBoundsState.getDisplayBounds().height()); + Rect expectedMovementBounds = new Rect(); + mPipBoundsAlgorithm.getMovementBounds(mPipBounds, mInsetBounds, expectedMovementBounds, 0); - mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds, + mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds, mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation); - assertEquals(expectedMinMovementBounds, mPipBoundsState.getNormalMovementBounds()); + assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds()); verify(mPipResizeGestureHandler, times(1)) - .updateMinSize(mMinBounds.width(), mMinBounds.height()); - } - - @Test - public void updateMovementBounds_maxBounds() { - Point displaySize = new Point(); - mContext.getDisplay().getRealSize(displaySize); - Size maxSize = mPipBoundsAlgorithm.getSizeForAspectRatio(1, - mContext.getResources().getDimensionPixelSize( - R.dimen.pip_expanded_shortest_edge_size), displaySize.x, displaySize.y); - Rect maxBounds = new Rect(0, 0, maxSize.getWidth(), maxSize.getHeight()); - Rect expectedMaxMovementBounds = new Rect(); - mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, expectedMaxMovementBounds, - 0); - - mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds, - mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation); + .updateMinSize(mPipBounds.width(), mPipBounds.height()); - assertEquals(expectedMaxMovementBounds, mPipBoundsState.getExpandedMovementBounds()); verify(mPipResizeGestureHandler, times(1)) - .updateMaxSize(maxBounds.width(), maxBounds.height()); + .updateMaxSize(shorterLength - 2 * mInsetBounds.left, + shorterLength - 2 * mInsetBounds.left); } @Test @@ -160,7 +154,7 @@ public class PipTouchHandlerTest extends ShellTestCase { mFromImeAdjustment = true; mPipTouchHandler.onImeVisibilityChanged(true /* imeVisible */, mImeHeight); - mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds, + mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds, mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation); verify(mMotionHelper, times(1)).animateToOffset(any(), anyInt()); diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp index 74cf1fda1b75..20a8a8c71a0f 100644 --- a/libs/hwui/Animator.cpp +++ b/libs/hwui/Animator.cpp @@ -160,10 +160,6 @@ void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) { } } - if (!mHasStartValue) { - doSetStartValue(getValue(mTarget)); - } - if (!mStagingRequests.empty()) { // No interpolator was set, use the default if (mPlayState == PlayState::NotStarted && !mInterpolator) { @@ -270,9 +266,11 @@ bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) { // to call setValue even if the animation isn't yet running or is still // being delayed as we need to override the staging value if (playTime < 0) { - setValue(mTarget, mFromValue); return false; } + if (!this->mHasStartValue) { + doSetStartValue(getValue(mTarget)); + } float fraction = 1.0f; if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 8d090f824e71..d896c1fc82b5 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -567,6 +567,25 @@ public class AudioManager { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int FLAG_FROM_KEY = 1 << 12; + /** @hide */ + @IntDef(flag = false, prefix = "FLAG", value = { + FLAG_SHOW_UI, + FLAG_ALLOW_RINGER_MODES, + FLAG_PLAY_SOUND, + FLAG_REMOVE_SOUND_AND_VIBRATE, + FLAG_VIBRATE, + FLAG_FIXED_VOLUME, + FLAG_BLUETOOTH_ABS_VOLUME, + FLAG_SHOW_SILENT_HINT, + FLAG_HDMI_SYSTEM_AUDIO_VOLUME, + FLAG_ACTIVE_MEDIA_ONLY, + FLAG_SHOW_UI_WARNINGS, + FLAG_SHOW_VIBRATE_HINT, + FLAG_FROM_KEY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Flags {} + // The iterator of TreeMap#entrySet() returns the entries in ascending key order. private static final TreeMap<Integer, String> FLAG_NAMES = new TreeMap<>(); diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java index 27f72687ccbe..ede1dbf47d36 100644 --- a/media/java/android/media/AudioPlaybackConfiguration.java +++ b/media/java/android/media/AudioPlaybackConfiguration.java @@ -181,6 +181,21 @@ public final class AudioPlaybackConfiguration implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface PlayerState {} + /** @hide */ + public static String playerStateToString(@PlayerState int state) { + switch (state) { + case PLAYER_STATE_UNKNOWN: return "PLAYER_STATE_UNKNOWN"; + case PLAYER_STATE_RELEASED: return "PLAYER_STATE_RELEASED"; + case PLAYER_STATE_IDLE: return "PLAYER_STATE_IDLE"; + case PLAYER_STATE_STARTED: return "PLAYER_STATE_STARTED"; + case PLAYER_STATE_PAUSED: return "PLAYER_STATE_PAUSED"; + case PLAYER_STATE_STOPPED: return "PLAYER_STATE_STOPPED"; + case PLAYER_UPDATE_DEVICE_ID: return "PLAYER_UPDATE_DEVICE_ID"; + default: + return "invalid state " + state; + } + } + // immutable data private final int mPlayerIId; diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index e056d435198a..7fb83f17a9d4 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -2726,8 +2726,10 @@ public class AudioTrack extends PlayerBase } } synchronized(mPlayStateLock) { + baseStart(0); // unknown device at this point native_start(); - baseStart(native_getRoutedDeviceId()); + // FIXME see b/179218630 + //baseStart(native_getRoutedDeviceId()); if (mPlayState == PLAYSTATE_PAUSED_STOPPING) { mPlayState = PLAYSTATE_STOPPING; } else { diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index ca0d29f2f47f..2c45ed3cb861 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1356,6 +1356,7 @@ public class MediaPlayer extends PlayerBase } private void startImpl() { + baseStart(0); // unknown device at this point stayAwake(true); _start(); } @@ -1381,6 +1382,7 @@ public class MediaPlayer extends PlayerBase public void stop() throws IllegalStateException { stayAwake(false); _stop(); + baseStop(); } private native void _stop() throws IllegalStateException; @@ -1394,6 +1396,7 @@ public class MediaPlayer extends PlayerBase public void pause() throws IllegalStateException { stayAwake(false); _pause(); + basePause(); } private native void _pause() throws IllegalStateException; @@ -3479,7 +3482,8 @@ public class MediaPlayer extends PlayerBase case MEDIA_STOPPED: { tryToDisableNativeRoutingCallback(); - baseStop(); + // FIXME see b/179218630 + //baseStop(); TimeProvider timeProvider = mTimeProvider; if (timeProvider != null) { timeProvider.onStopped(); @@ -3489,15 +3493,17 @@ public class MediaPlayer extends PlayerBase case MEDIA_STARTED: { - baseStart(native_getRoutedDeviceId()); + // FIXME see b/179218630 + //baseStart(native_getRoutedDeviceId()); tryToEnableNativeRoutingCallback(); } // fall through case MEDIA_PAUSED: { - if (msg.what == MEDIA_PAUSED) { - basePause(); - } + // FIXME see b/179218630 + //if (msg.what == MEDIA_PAUSED) { + // basePause(); + //} TimeProvider timeProvider = mTimeProvider; if (timeProvider != null) { timeProvider.onPaused(msg.what == MEDIA_PAUSED); diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index f580ea5d57de..13a3436569aa 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -1127,9 +1127,10 @@ public final class MediaSessionManager { * toast showing the volume should be shown. * * @param sessionToken the remote media session token - * @param flags extra information about how to handle the volume change + * @param flags flags containing extra action or information regarding the volume change */ - void onVolumeChanged(@NonNull MediaSession.Token sessionToken, int flags); + void onVolumeChanged(@NonNull MediaSession.Token sessionToken, + @AudioManager.Flags int flags); /** * Called when the default remote session is changed where the default remote session diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 35a4e81eefe4..539a81bf43a0 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -100,6 +100,7 @@ <uses-permission android:name="android.permission.REBOOT" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> <uses-permission android:name="android.permission.POWER_SAVER" /> + <uses-permission android:name="android.permission.BATTERY_PREDICTION" /> <uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" /> <uses-permission android:name="android.permission.BACKUP" /> <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 5af024448987..2faca8dbdcbf 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -600,6 +600,14 @@ android:permission="android.permission.BIND_REMOTEVIEWS" android:exported="false" /> + <!-- ContentProvider that returns a People Tile preview for a given shortcut --> + <provider + android:name="com.android.systemui.people.PeopleProvider" + android:authorities="com.android.systemui.people.PeopleProvider" + android:exported="true" + android:permission="android.permission.GET_PEOPLE_TILE_PREVIEW"> + </provider> + <!-- a gallery of delicious treats --> <service android:name=".DessertCaseDream" diff --git a/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml new file mode 100644 index 000000000000..51c442abf2fd --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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 + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?android:attr/colorControlHighlight" + android:radius="40dp"/> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PeopleProviderUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PeopleProviderUtils.java new file mode 100644 index 000000000000..15cf3696fec7 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PeopleProviderUtils.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.system; + +/** + * These strings are part of the {@link com.android.systemui.people.PeopleProvider} API + * contract. The API returns a People Tile preview that can be displayed by calling packages. + * The provider is part of the SystemUI service, and the strings live here for shared access with + * Launcher (caller). + */ +public class PeopleProviderUtils { + /** + * ContentProvider URI scheme. + * @hide + */ + public static final String PEOPLE_PROVIDER_SCHEME = "content://"; + + /** + * ContentProvider URI authority. + * @hide + */ + public static final String PEOPLE_PROVIDER_AUTHORITY = + "com.android.systemui.people.PeopleProvider"; + + /** + * Method name for getting People Tile preview. + * @hide + */ + public static final String GET_PEOPLE_TILE_PREVIEW_METHOD = "get_people_tile_preview"; + + /** + * Extras bundle key specifying shortcut Id of the People Tile preview requested. + * @hide + */ + public static final String EXTRAS_KEY_SHORTCUT_ID = "shortcut_id"; + + /** + * Extras bundle key specifying package name of the People Tile preview requested. + * @hide + */ + public static final String EXTRAS_KEY_PACKAGE_NAME = "package_name"; + + /** + * Extras bundle key specifying {@code UserHandle} of the People Tile preview requested. + * @hide + */ + public static final String EXTRAS_KEY_USER_HANDLE = "user_handle"; + + /** + * Response bundle key to access the returned People Tile preview. + * @hide + */ + public static final String RESPONSE_KEY_REMOTE_VIEWS = "remote_views"; + + /** + * Name of the permission needed to get a People Tile preview for a given conversation shortcut. + * @hide + */ + public static final String GET_PEOPLE_TILE_PREVIEW_PERMISSION = + "android.permission.GET_PEOPLE_TILE_PREVIEW"; + +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 538daaf343f4..a0c5958284ec 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -166,6 +167,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final TelephonyManager mTelephonyManager; private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory; private final FalsingCollector mFalsingCollector; + private final boolean mIsNewLayoutEnabled; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -176,7 +178,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> @Main Resources resources, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, EmergencyButtonController.Factory emergencyButtonControllerFactory, - FalsingCollector falsingCollector) { + FalsingCollector falsingCollector, + FeatureFlags featureFlags) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -188,6 +191,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mTelephonyManager = telephonyManager; mEmergencyButtonControllerFactory = emergencyButtonControllerFactory; mFalsingCollector = falsingCollector; + mIsNewLayoutEnabled = featureFlags.isKeyguardLayoutEnabled(); } /** Create a new {@link KeyguardInputViewController}. */ @@ -212,21 +216,22 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener, emergencyButtonController, mFalsingCollector); + mLiftToActivateListener, emergencyButtonController, mFalsingCollector, + mIsNewLayoutEnabled); } else if (keyguardInputView instanceof KeyguardSimPinView) { return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, emergencyButtonController, - mFalsingCollector); + mFalsingCollector, mIsNewLayoutEnabled); } else if (keyguardInputView instanceof KeyguardSimPukView) { return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, emergencyButtonController, - mFalsingCollector); + mFalsingCollector, mIsNewLayoutEnabled); } throw new RuntimeException("Unable to find controller for " + keyguardInputView); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 6a6b964c2a8f..825ea2570df0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -167,6 +167,20 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView } /** + * By default, the new layout will be enabled. When false, revert to the old style. + */ + public void setIsNewLayoutEnabled(boolean isEnabled) { + if (!isEnabled) { + for (int i = 0; i < mButtons.length; i++) { + mButtons[i].disableNewLayout(); + } + mDeleteButton.disableNewLayout(); + mOkButton.disableNewLayout(); + reloadColors(); + } + } + + /** * Reload colors from resources. **/ public void reloadColors() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 262bed3f695c..a456d42f5be5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -35,11 +35,12 @@ public class KeyguardPinViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, EmergencyButtonController emergencyButtonController, - FalsingCollector falsingCollector) { + FalsingCollector falsingCollector, boolean isNewLayoutEnabled) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, emergencyButtonController, falsingCollector); mKeyguardUpdateMonitor = keyguardUpdateMonitor; + view.setIsNewLayoutEnabled(isNewLayoutEnabled); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index a5a3a80b8d42..4d2ebbb4a594 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -79,13 +79,14 @@ public class KeyguardSimPinViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, EmergencyButtonController emergencyButtonController, - FalsingCollector falsingCollector) { + FalsingCollector falsingCollector, boolean isNewLayoutEnabled) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, emergencyButtonController, falsingCollector); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); + view.setIsNewLayoutEnabled(isNewLayoutEnabled); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index b584bf75831e..0d9bb6f73b49 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -85,13 +85,14 @@ public class KeyguardSimPukViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, EmergencyButtonController emergencyButtonController, - FalsingCollector falsingCollector) { + FalsingCollector falsingCollector, boolean isNewLayoutEnabled) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, emergencyButtonController, falsingCollector); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); + view.setIsNewLayoutEnabled(isNewLayoutEnabled); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index 372810677649..886c3729124b 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java @@ -16,17 +16,23 @@ package com.android.keyguard; import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.VectorDrawable; import android.util.AttributeSet; +import android.view.ContextThemeWrapper; import android.view.MotionEvent; import android.view.ViewGroup; +import com.android.settingslib.Utils; +import com.android.systemui.R; + /** * Similar to the {@link NumPadKey}, but displays an image. */ public class NumPadButton extends AlphaOptimizedImageButton { - private final NumPadAnimator mAnimator; + private NumPadAnimator mAnimator; public NumPadButton(Context context, AttributeSet attrs) { super(context, attrs); @@ -36,25 +42,34 @@ public class NumPadButton extends AlphaOptimizedImageButton { } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - mAnimator.updateMargin((ViewGroup.MarginLayoutParams) getLayoutParams()); + public void setLayoutParams(ViewGroup.LayoutParams params) { + if (mAnimator != null) mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params); + super.setLayoutParams(params); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - // Set width/height to the same value to ensure a smooth circle for the bg - setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); + // Set width/height to the same value to ensure a smooth circle for the bg, but shrink + // the height to match the old pin bouncer + int width = getMeasuredWidth(); + int height = mAnimator == null ? (int) (width * .75f) : width; + + setMeasuredDimension(getMeasuredWidth(), height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - mAnimator.onLayout(b - t); + if (mAnimator != null) mAnimator.onLayout(b - t); } @Override public boolean onTouchEvent(MotionEvent event) { - mAnimator.start(); + if (mAnimator != null) mAnimator.start(); return super.onTouchEvent(event); } @@ -62,6 +77,23 @@ public class NumPadButton extends AlphaOptimizedImageButton { * Reload colors from resources. **/ public void reloadColors() { - mAnimator.reloadColors(getContext()); + if (mAnimator != null) { + mAnimator.reloadColors(getContext()); + } else { + // Needed for old style pin + int textColor = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary) + .getDefaultColor(); + ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(textColor)); + } + } + + /** + * By default, the new layout will be enabled. Invoking will revert to the old style + */ + public void disableNewLayout() { + mAnimator = null; + ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey); + setBackground(getContext().getResources().getDrawable( + R.drawable.ripple_drawable_pin, ctw.getTheme())); } } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index 756d6107570a..01e1c632ad83 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -22,6 +22,7 @@ import android.graphics.drawable.GradientDrawable; import android.os.PowerManager; import android.os.SystemClock; import android.util.AttributeSet; +import android.view.ContextThemeWrapper; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -47,7 +48,7 @@ public class NumPadKey extends ViewGroup { private int mTextViewResId; private PasswordTextView mTextView; - private final NumPadAnimator mAnimator; + private NumPadAnimator mAnimator; private View.OnClickListener mListener = new View.OnClickListener() { @Override @@ -131,6 +132,17 @@ public class NumPadKey extends ViewGroup { } /** + * By default, the new layout will be enabled. Invoking will revert to the old style + */ + public void disableNewLayout() { + findViewById(R.id.klondike_text).setVisibility(View.VISIBLE); + mAnimator = null; + ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey); + setBackground(getContext().getResources().getDrawable( + R.drawable.ripple_drawable_pin, ctw.getTheme())); + } + + /** * Reload colors from resources. **/ public void reloadColors() { @@ -141,7 +153,7 @@ public class NumPadKey extends ViewGroup { mDigitText.setTextColor(textColor); mKlondikeText.setTextColor(klondikeColor); - mAnimator.reloadColors(getContext()); + if (mAnimator != null) mAnimator.reloadColors(getContext()); } @Override @@ -150,14 +162,14 @@ public class NumPadKey extends ViewGroup { doHapticKeyClick(); } - mAnimator.start(); + if (mAnimator != null) mAnimator.start(); return super.onTouchEvent(event); } @Override public void setLayoutParams(ViewGroup.LayoutParams params) { - mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params); + if (mAnimator != null) mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params); super.setLayoutParams(params); } @@ -167,8 +179,12 @@ public class NumPadKey extends ViewGroup { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); - // Set width/height to the same value to ensure a smooth circle for the bg - setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); + // Set width/height to the same value to ensure a smooth circle for the bg, but shrink + // the height to match the old pin bouncer + int width = getMeasuredWidth(); + int height = mAnimator == null ? (int) (width * .75f) : width; + + setMeasuredDimension(getMeasuredWidth(), height); } @Override @@ -187,7 +203,7 @@ public class NumPadKey extends ViewGroup { left = centerX - mKlondikeText.getMeasuredWidth() / 2; mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom); - mAnimator.onLayout(b - t); + if (mAnimator != null) mAnimator.onLayout(b - t); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 71ec33e16e0e..b6a232d576b8 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -16,7 +16,10 @@ package com.android.systemui; +import android.app.WallpaperColors; +import android.graphics.Bitmap; import android.graphics.Rect; +import android.graphics.RectF; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; @@ -26,13 +29,16 @@ import android.util.Log; import android.util.Size; import android.view.SurfaceHolder; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.glwallpaper.EglHelper; -import com.android.systemui.glwallpaper.GLWallpaperRenderer; import com.android.systemui.glwallpaper.ImageWallpaperRenderer; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import javax.inject.Inject; @@ -45,8 +51,13 @@ public class ImageWallpaper extends WallpaperService { // We delayed destroy render context that subsequent render requests have chance to cancel it. // This is to avoid destroying then recreating render context in a very short time. private static final int DELAY_FINISH_RENDERING = 1000; + private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS = + new RectF(0, 0, 1, 1); private static final boolean DEBUG = false; + private ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>(); private HandlerThread mWorker; + // scaled down version + private Bitmap mMiniBitmap; @Inject public ImageWallpaper() { @@ -70,6 +81,7 @@ public class ImageWallpaper extends WallpaperService { super.onDestroy(); mWorker.quitSafely(); mWorker = null; + mMiniBitmap = null; } class GLEngine extends Engine { @@ -80,7 +92,7 @@ public class ImageWallpaper extends WallpaperService { @VisibleForTesting static final int MIN_SURFACE_HEIGHT = 64; - private GLWallpaperRenderer mRenderer; + private ImageWallpaperRenderer mRenderer; private EglHelper mEglHelper; private final Runnable mFinishRenderingTask = this::finishRendering; private boolean mNeedRedraw; @@ -101,6 +113,12 @@ public class ImageWallpaper extends WallpaperService { setFixedSizeAllowed(true); setOffsetNotificationsEnabled(false); updateSurfaceSize(); + mMiniBitmap = null; + if (mWorker == null || mWorker.getThreadHandler() == null) { + updateMiniBitmap(); + } else { + mWorker.getThreadHandler().post(this::updateMiniBitmap); + } } EglHelper getEglHelperInstance() { @@ -111,6 +129,20 @@ public class ImageWallpaper extends WallpaperService { return new ImageWallpaperRenderer(getDisplayContext()); } + private void updateMiniBitmap() { + mRenderer.useBitmap(b -> { + int size = Math.min(b.getWidth(), b.getHeight()); + float scale = 1.0f; + if (size > MIN_SURFACE_WIDTH) { + scale = (float) MIN_SURFACE_WIDTH / (float) size; + } + mMiniBitmap = Bitmap.createScaledBitmap(b, Math.round(scale * b.getWidth()), + Math.round(scale * b.getHeight()), false); + computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap); + mLocalColorsToAdd.clear(); + }); + } + private void updateSurfaceSize() { SurfaceHolder holder = getSurfaceHolder(); Size frameSize = mRenderer.reportSurfaceSize(); @@ -126,6 +158,7 @@ public class ImageWallpaper extends WallpaperService { @Override public void onDestroy() { + mMiniBitmap = null; mWorker.getThreadHandler().post(() -> { mRenderer.finish(); mRenderer = null; @@ -134,6 +167,61 @@ public class ImageWallpaper extends WallpaperService { }); } + + + @Override + public boolean supportsLocalColorExtraction() { + return true; + } + + @Override + public void addLocalColorsAreas(@NonNull List<RectF> regions) { + mWorker.getThreadHandler().post(() -> { + Bitmap bitmap = mMiniBitmap; + if (bitmap == null) { + mLocalColorsToAdd.addAll(regions); + } else { + computeAndNotifyLocalColors(regions, bitmap); + } + }); + } + + private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) { + List<WallpaperColors> colors = getLocalWallpaperColors(regions, b); + try { + notifyLocalColorsChanged(regions, colors); + } catch (RuntimeException e) { + Log.e(TAG, e.getMessage(), e); + } + } + + @Override + public void removeLocalColorsAreas(@NonNull List<RectF> regions) { + // No-OP + } + + private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas, + Bitmap b) { + List<WallpaperColors> colors = new ArrayList<>(areas.size()); + for (int i = 0; i < areas.size(); i++) { + RectF area = areas.get(i); + if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) { + colors.add(null); + continue; + } + Rect subImage = new Rect( + Math.round(area.left * b.getWidth()), + Math.round(area.top * b.getHeight()), + Math.round(area.right * b.getWidth()), + Math.round(area.bottom * b.getHeight())); + Bitmap colorImg = Bitmap.createBitmap(b, + subImage.left, subImage.top, subImage.width(), subImage.height()); + WallpaperColors color = WallpaperColors.fromBitmap(colorImg); + colors.add(color); + } + return colors; + } + @Override public void onSurfaceCreated(SurfaceHolder holder) { if (mWorker == null) return; diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt index 38a82f8c9908..36937d622b5b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt @@ -16,10 +16,19 @@ package com.android.systemui.controls.dagger +import android.content.ContentResolver +import android.content.Context +import android.database.ContentObserver +import android.provider.Settings import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.settings.SecureSettings +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT import dagger.Lazy import java.util.Optional import javax.inject.Inject @@ -28,15 +37,43 @@ import javax.inject.Inject * Pseudo-component to inject into classes outside `com.android.systemui.controls`. * * If `featureEnabled` is false, all the optionals should be empty. The controllers will only be - * instantiated if `featureEnabled` is true. + * instantiated if `featureEnabled` is true. Can also be queried for the availability of controls. */ @SysUISingleton class ControlsComponent @Inject constructor( @ControlsFeatureEnabled private val featureEnabled: Boolean, + private val context: Context, private val lazyControlsController: Lazy<ControlsController>, private val lazyControlsUiController: Lazy<ControlsUiController>, - private val lazyControlsListingController: Lazy<ControlsListingController> + private val lazyControlsListingController: Lazy<ControlsListingController>, + private val lockPatternUtils: LockPatternUtils, + private val keyguardStateController: KeyguardStateController, + private val userTracker: UserTracker, + private val secureSettings: SecureSettings ) { + + private val contentResolver: ContentResolver + get() = context.contentResolver + + private var canShowWhileLockedSetting = false + + val showWhileLockedObserver = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + updateShowWhileLocked() + } + } + + init { + if (featureEnabled) { + secureSettings.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), + false, /* notifyForDescendants */ + showWhileLockedObserver + ) + updateShowWhileLocked() + } + } + fun getControlsController(): Optional<ControlsController> { return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty() } @@ -52,4 +89,37 @@ class ControlsComponent @Inject constructor( Optional.empty() } } -}
\ No newline at end of file + + /** + * @return true if controls are feature-enabled and have available services to serve controls + */ + fun isEnabled() = featureEnabled && lazyControlsController.get().available + + /** + * Returns one of 3 states: + * * AVAILABLE - Controls can be made visible + * * AVAILABLE_AFTER_UNLOCK - Controls can be made visible only after device unlock + * * UNAVAILABLE - Controls are not enabled + */ + fun getVisibility(): Visibility { + if (!isEnabled()) return Visibility.UNAVAILABLE + if (lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier) + == STRONG_AUTH_REQUIRED_AFTER_BOOT) { + return Visibility.AVAILABLE_AFTER_UNLOCK + } + if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) { + return Visibility.AVAILABLE_AFTER_UNLOCK + } + + return Visibility.AVAILABLE + } + + private fun updateShowWhileLocked() { + canShowWhileLockedSetting = secureSettings.getInt( + Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0 + } + + enum class Visibility { + AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 8af45a5c0ef1..d85b10167697 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -25,6 +25,8 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; +import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; import android.animation.Animator; @@ -250,6 +252,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final IWindowManager mIWindowManager; private final Executor mBackgroundExecutor; private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>(); + private ControlsComponent mControlsComponent; private Optional<ControlsController> mControlsControllerOptional; private final RingerModeTracker mRingerModeTracker; private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms @@ -338,6 +341,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mSysuiColorExtractor = colorExtractor; mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; + mControlsComponent = controlsComponent; mControlsUiControllerOptional = controlsComponent.getControlsUiController(); mIWindowManager = iWindowManager; mBackgroundExecutor = backgroundExecutor; @@ -387,7 +391,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (mDialog.mWalletViewController != null) { mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked); } - if (!mDialog.isShowingControls() && shouldShowControls()) { + if (!mDialog.isShowingControls() + && mControlsComponent.getVisibility() == AVAILABLE) { mDialog.showControls(mControlsUiControllerOptional.get()); } if (unlocked) { @@ -397,14 +402,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } }); - if (controlsComponent.getControlsListingController().isPresent()) { - controlsComponent.getControlsListingController().get() + if (mControlsComponent.getControlsListingController().isPresent()) { + mControlsComponent.getControlsListingController().get() .addCallback(list -> { mControlsServiceInfos = list; // This callback may occur after the dialog has been shown. If so, add // controls into the already visible space or show the lock msg if needed. if (mDialog != null) { - if (!mDialog.isShowingControls() && shouldShowControls()) { + if (!mDialog.isShowingControls() + && mControlsComponent.getVisibility() == AVAILABLE) { mDialog.showControls(mControlsUiControllerOptional.get()); } else if (shouldShowLockMessage(mDialog)) { mDialog.showLockMessage(); @@ -704,7 +710,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mDepthController.setShowingHomeControls(true); ControlsUiController uiController = null; - if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) { + if (mControlsComponent.getVisibility() == AVAILABLE) { uiController = mControlsUiControllerOptional.get(); } ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter, @@ -2687,26 +2693,24 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return isPanelDebugModeEnabled(context); } - private boolean shouldShowControls() { - boolean showOnLockScreen = mShowLockScreenCardsAndControls && mLockPatternUtils - .getStrongAuthForUser(getCurrentUser().id) != STRONG_AUTH_REQUIRED_AFTER_BOOT; - return controlsAvailable() - && (mKeyguardStateController.isUnlocked() || showOnLockScreen); - } - private boolean controlsAvailable() { return mDeviceProvisioned - && mControlsUiControllerOptional.isPresent() - && mControlsUiControllerOptional.get().getAvailable() + && mControlsComponent.isEnabled() && !mControlsServiceInfos.isEmpty(); } private boolean shouldShowLockMessage(ActionsDialog dialog) { + return mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK + || isWalletAvailableAfterUnlock(dialog); + } + + // Temporary while we move items out of the power menu + private boolean isWalletAvailableAfterUnlock(ActionsDialog dialog) { boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id) == STRONG_AUTH_REQUIRED_AFTER_BOOT; return !mKeyguardStateController.isUnlocked() && (!mShowLockScreenCardsAndControls || isLockedAfterBoot) - && (controlsAvailable() || dialog.isWalletViewAvailable()); + && dialog.isWalletViewAvailable(); } private void onPowerMenuLockScreenSettingsChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java index 1a0356c4446d..01a353ce8f1f 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java @@ -58,6 +58,14 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { mWallpaper = new ImageGLWallpaper(mProgram); } + /** + * @hide + * @return + */ + public void useBitmap(Consumer<Bitmap> c) { + mTexture.use(c); + } + @Override public boolean isWcgContent() { return mTexture.isWcgContent(); diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java new file mode 100644 index 000000000000..e7458a3df801 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.people; + +import android.app.people.ConversationChannel; +import android.app.people.IPeopleManager; +import android.app.people.PeopleSpaceTile; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.Log; +import android.widget.RemoteViews; + +import com.android.systemui.shared.system.PeopleProviderUtils; + +/** API that returns a People Tile preview. */ +public class PeopleProvider extends ContentProvider { + + LauncherApps mLauncherApps; + IPeopleManager mPeopleManager; + + private static final String TAG = "PeopleProvider"; + private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; + private static final String EMPTY_STRING = ""; + + @Override + public Bundle call(String method, String arg, Bundle extras) { + if (!doesCallerHavePermission()) { + String callingPackage = getCallingPackage(); + Log.w(TAG, "API not accessible to calling package: " + callingPackage); + throw new SecurityException("API not accessible to calling package: " + callingPackage); + } + if (!PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD.equals(method)) { + Log.w(TAG, "Invalid method"); + throw new IllegalArgumentException("Invalid method"); + } + + // If services are not set as mocks in tests, fetch them now. + mPeopleManager = mPeopleManager != null ? mPeopleManager + : IPeopleManager.Stub.asInterface( + ServiceManager.getService(Context.PEOPLE_SERVICE)); + mLauncherApps = mLauncherApps != null ? mLauncherApps + : getContext().getSystemService(LauncherApps.class); + + if (mPeopleManager == null || mLauncherApps == null) { + Log.w(TAG, "Null system managers"); + return null; + } + + if (extras == null) { + Log.w(TAG, "Extras can't be null"); + throw new IllegalArgumentException("Extras can't be null"); + } + + String shortcutId = extras.getString( + PeopleProviderUtils.EXTRAS_KEY_SHORTCUT_ID, EMPTY_STRING); + String packageName = extras.getString( + PeopleProviderUtils.EXTRAS_KEY_PACKAGE_NAME, EMPTY_STRING); + UserHandle userHandle = extras.getParcelable( + PeopleProviderUtils.EXTRAS_KEY_USER_HANDLE); + if (shortcutId.isEmpty()) { + Log.w(TAG, "Invalid shortcut id"); + throw new IllegalArgumentException("Invalid shortcut id"); + } + + if (packageName.isEmpty()) { + Log.w(TAG, "Invalid package name"); + throw new IllegalArgumentException("Invalid package name"); + } + if (userHandle == null) { + Log.w(TAG, "Null user handle"); + throw new IllegalArgumentException("Null user handle"); + } + + ConversationChannel channel; + try { + channel = mPeopleManager.getConversation( + packageName, userHandle.getIdentifier(), shortcutId); + } catch (Exception e) { + Log.w(TAG, "Exception getting tiles: " + e); + return null; + } + PeopleSpaceTile tile = PeopleSpaceUtils.getTile(channel, mLauncherApps); + + if (tile == null) { + if (DEBUG) Log.i(TAG, "No tile was returned"); + return null; + } + + if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId); + RemoteViews view = PeopleSpaceUtils.createRemoteViews(getContext(), tile, 0); + final Bundle bundle = new Bundle(); + bundle.putParcelable(PeopleProviderUtils.RESPONSE_KEY_REMOTE_VIEWS, view); + return bundle; + } + + private boolean doesCallerHavePermission() { + return getContext().checkPermission( + PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION, + Binder.getCallingPid(), Binder.getCallingUid()) + == PackageManager.PERMISSION_GRANTED; + } + + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + throw new IllegalArgumentException("Invalid method"); + } + + @Override + public String getType(Uri uri) { + throw new IllegalArgumentException("Invalid method"); + } + + @Override + public Uri insert(Uri uri, ContentValues initialValues) { + throw new IllegalArgumentException("Invalid method"); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + throw new IllegalArgumentException("Invalid method"); + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + throw new IllegalArgumentException("Invalid method"); + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index dd054848aed2..7eb1fc175c5a 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -49,6 +49,7 @@ import android.provider.ContactsContract; import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; import android.service.notification.StatusBarNotification; +import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.RemoteViews; @@ -438,7 +439,7 @@ public class PeopleSpaceUtils { } /** Creates a {@link RemoteViews} for {@code tile}. */ - private static RemoteViews createRemoteViews(Context context, + public static RemoteViews createRemoteViews(Context context, PeopleSpaceTile tile, int appWidgetId) { RemoteViews views; if (tile.getNotificationKey() != null) { @@ -455,6 +456,9 @@ public class PeopleSpaceUtils { PeopleSpaceTile tile, int appWidgetId) { try { views.setTextViewText(R.id.name, tile.getUserName().toString()); + views.setImageViewIcon(R.id.person_icon, tile.getUserIcon()); + views.setBoolean(R.id.content_background, "setClipToOutline", true); + views.setImageViewBitmap( R.id.package_icon, PeopleSpaceUtils.convertDrawableToBitmap( @@ -462,8 +466,6 @@ public class PeopleSpaceUtils { tile.getPackageName()) ) ); - views.setImageViewIcon(R.id.person_icon, tile.getUserIcon()); - views.setBoolean(R.id.content_background, "setClipToOutline", true); Intent activityIntent = new Intent(context, LaunchConversationActivity.class); activityIntent.addFlags( @@ -612,6 +614,22 @@ public class PeopleSpaceUtils { .collect(Collectors.toList()); } + /** Returns {@code PeopleSpaceTile} based on provided {@ConversationChannel}. */ + public static PeopleSpaceTile getTile(ConversationChannel channel, LauncherApps launcherApps) { + if (channel == null) { + Log.i(TAG, "ConversationChannel is null"); + return null; + } + + PeopleSpaceTile tile = new PeopleSpaceTile.Builder(channel, launcherApps).build(); + if (!PeopleSpaceUtils.shouldKeepConversation(tile)) { + Log.i(TAG, "PeopleSpaceTile is not valid"); + return null; + } + + return tile; + } + /** Returns the last interaction time with the user specified by {@code PeopleSpaceTile}. */ private static Long getLastInteraction(IPeopleManager peopleManager, PeopleSpaceTile tile) { @@ -701,7 +719,7 @@ public class PeopleSpaceUtils { * </li> */ public static boolean shouldKeepConversation(PeopleSpaceTile tile) { - return tile != null && tile.getUserName().length() != 0; + return tile != null && !TextUtils.isEmpty(tile.getUserName()); } private static boolean hasBirthdayStatus(PeopleSpaceTile tile, Context context) { @@ -792,8 +810,7 @@ public class PeopleSpaceUtils { private static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager, Context context, int appWidgetId, PeopleSpaceTile tile) { updateAppWidgetOptions(appWidgetManager, appWidgetId, tile); - RemoteViews views = createRemoteViews(context, - tile, appWidgetId); + RemoteViews views = createRemoteViews(context, tile, appWidgetId); appWidgetManager.updateAppWidget(appWidgetId, views); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 6176a5702dcf..41445917a011 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -25,6 +25,7 @@ import com.android.internal.logging.MetricsLogger import com.android.systemui.R import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.dagger.ControlsComponent +import com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsDialog import com.android.systemui.dagger.qualifiers.Background @@ -91,7 +92,7 @@ class DeviceControlsTile @Inject constructor( override fun isAvailable(): Boolean { return featureFlags.isKeyguardLayoutEnabled && controlsLockscreen && - controlsComponent.getControlsUiController().isPresent + controlsComponent.getVisibility() != UNAVAILABLE } override fun newTileState(): QSTile.State { @@ -154,4 +155,4 @@ class DeviceControlsTile @Inject constructor( override fun getTileLabel(): CharSequence { return mContext.getText(R.string.quick_controls_title) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java index d56c806554d4..dc639dce4951 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java @@ -95,6 +95,17 @@ public class ScrollCaptureClient { this.requested = request; this.captured = captured; } + + @Override + public String toString() { + return "CaptureResult{" + + "requested=" + requested + + " (" + requested.width() + "x" + requested.height() + ")" + + ", captured=" + captured + + " (" + captured.width() + "x" + captured.height() + ")" + + ", image=" + image + + '}'; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index d97f644c5d23..ad5e637b189e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -253,7 +253,7 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener && result.captured.height() < result.requested.height(); boolean finish = false; - if (partialResult) { + if (partialResult || emptyResult) { // Potentially reached a vertical boundary. Extend in the other direction. switch (mDirection) { case DOWN: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 2ce0a8776266..986333ce5010 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON; import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK; @@ -183,6 +184,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private ControlsComponent mControlsComponent; private int mLockScreenMode; private BroadcastDispatcher mBroadcastDispatcher; + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; public KeyguardBottomAreaView(Context context) { this(context, null); @@ -295,7 +297,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); getContext().registerReceiverAsUser(mDevicePolicyReceiver, UserHandle.ALL, filter, null, null); - Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback); + mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); + mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); mKeyguardStateController.addCallback(this); } @@ -307,7 +310,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mRightExtension.destroy(); mLeftExtension.destroy(); getContext().unregisterReceiver(mDevicePolicyReceiver); - Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback); + mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); } private void initAccessibility() { @@ -410,12 +413,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } private void updateLeftAffordanceIcon() { - if (mDozing) { - mAltLeftButton.setVisibility(GONE); - } else if (mAltLeftButton.getDrawable() != null) { - mAltLeftButton.setVisibility(VISIBLE); - } - if (!mShowLeftAffordance || mDozing) { mLeftAffordanceView.setVisibility(GONE); return; @@ -430,6 +427,14 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLeftAffordanceView.setContentDescription(state.contentDescription); } + private void updateControlsVisibility() { + if (mDozing || mControlsComponent.getVisibility() != AVAILABLE) { + mAltLeftButton.setVisibility(GONE); + } else { + mAltLeftButton.setVisibility(VISIBLE); + } + } + public boolean isLeftVoiceAssist() { return mLeftIsVoiceAssist; } @@ -769,6 +774,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL updateCameraVisibility(); updateLeftAffordanceIcon(); + updateControlsVisibility(); if (dozing) { mOverlayContainer.setVisibility(INVISIBLE); @@ -889,35 +895,27 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } private void setupControls() { - if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) { + boolean inNewLayout = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; + boolean settingEnabled = Settings.Global.getInt(mContext.getContentResolver(), + "controls_lockscreen", 0) == 1; + if (!inNewLayout || !settingEnabled || !mControlsComponent.isEnabled()) { mAltLeftButton.setVisibility(View.GONE); - mAltLeftButton.setOnClickListener(null); - return; - } - - if (Settings.Global.getInt(mContext.getContentResolver(), "controls_lockscreen", 0) == 0) { return; } - if (mControlsComponent.getControlsListingController().isPresent()) { - mControlsComponent.getControlsListingController().get() - .addCallback(list -> { - if (!list.isEmpty()) { - mAltLeftButton.setImageDrawable(list.get(0).loadIcon()); - mAltLeftButton.setVisibility(View.VISIBLE); - mAltLeftButton.setOnClickListener((v) -> { - ControlsUiController ui = mControlsComponent - .getControlsUiController().get(); - mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher) - .show(ui); - }); - - } else { - mAltLeftButton.setVisibility(View.GONE); - mAltLeftButton.setOnClickListener(null); - } - }); - } + mControlsComponent.getControlsListingController().get() + .addCallback(list -> { + if (!list.isEmpty()) { + mAltLeftButton.setImageDrawable(list.get(0).loadIcon()); + mAltLeftButton.setOnClickListener((v) -> { + ControlsUiController ui = mControlsComponent + .getControlsUiController().get(); + mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher) + .show(ui); + }); + } + updateControlsVisibility(); + }); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java index 0c69ffdef372..10332bd31bd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java @@ -100,6 +100,7 @@ public class ImageWallpaperTest extends SysuiTestCase { return new ImageWallpaper() { @Override public Engine onCreateEngine() { + onCreate(); return new GLEngine(mHandler) { @Override public Context getDisplayContext() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt index 7fe682793152..b8f91b8d4719 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt @@ -17,11 +17,18 @@ package com.android.systemui.controls.dagger import android.testing.AndroidTestingRunner +import android.provider.Settings import androidx.test.filters.SmallTest +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController +import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.settings.SecureSettings import dagger.Lazy import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -29,7 +36,11 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -42,20 +53,29 @@ class ControlsComponentTest : SysuiTestCase() { private lateinit var uiController: ControlsUiController @Mock private lateinit var listingController: ControlsListingController + @Mock + private lateinit var keyguardStateController: KeyguardStateController + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private lateinit var userTracker: UserTracker + @Mock + private lateinit var lockPatternUtils: LockPatternUtils + @Mock + private lateinit var secureSettings: SecureSettings + + companion object { + fun <T> eq(value: T): T = Mockito.eq(value) ?: value + } @Before fun setUp() { MockitoAnnotations.initMocks(this) + + `when`(userTracker.userHandle.identifier).thenReturn(0) } @Test fun testFeatureEnabled() { - val component = ControlsComponent( - true, - Lazy { controller }, - Lazy { uiController }, - Lazy { listingController } - ) + val component = setupComponent(true) assertTrue(component.getControlsController().isPresent) assertEquals(controller, component.getControlsController().get()) @@ -67,15 +87,80 @@ class ControlsComponentTest : SysuiTestCase() { @Test fun testFeatureDisabled() { - val component = ControlsComponent( - false, - Lazy { controller }, - Lazy { uiController }, - Lazy { listingController } - ) + val component = setupComponent(false) assertFalse(component.getControlsController().isPresent) assertFalse(component.getControlsUiController().isPresent) assertFalse(component.getControlsListingController().isPresent) } -}
\ No newline at end of file + + @Test + fun testFeatureDisabledVisibility() { + val component = setupComponent(false) + + assertEquals(ControlsComponent.Visibility.UNAVAILABLE, component.getVisibility()) + } + + @Test + fun testFeatureEnabledAfterBootVisibility() { + `when`(controller.available).thenReturn(true) + `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) + .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT) + val component = setupComponent(true) + + assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility()) + } + + @Test + fun testFeatureEnabledAndCannotShowOnLockScreenVisibility() { + `when`(controller.available).thenReturn(true) + `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) + .thenReturn(STRONG_AUTH_NOT_REQUIRED) + `when`(keyguardStateController.isUnlocked()).thenReturn(false) + `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt())) + .thenReturn(0) + val component = setupComponent(true) + + assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility()) + } + + @Test + fun testFeatureEnabledAndCanShowOnLockScreenVisibility() { + `when`(controller.available).thenReturn(true) + `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) + .thenReturn(STRONG_AUTH_NOT_REQUIRED) + `when`(keyguardStateController.isUnlocked()).thenReturn(false) + `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt())) + .thenReturn(1) + val component = setupComponent(true) + + assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility()) + } + + @Test + fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() { + `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt())) + .thenReturn(0) + `when`(controller.available).thenReturn(true) + `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) + .thenReturn(STRONG_AUTH_NOT_REQUIRED) + `when`(keyguardStateController.isUnlocked()).thenReturn(true) + val component = setupComponent(true) + + assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility()) + } + + private fun setupComponent(enabled: Boolean): ControlsComponent { + return ControlsComponent( + enabled, + mContext, + Lazy { controller }, + Lazy { uiController }, + Lazy { listingController }, + lockPatternUtils, + keyguardStateController, + userTracker, + secureSettings + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index 78ee5936fe0b..1062fae52e7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -74,12 +74,14 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.settings.SecureSettings; import org.junit.Before; import org.junit.Test; @@ -135,6 +137,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController; @Mock private Handler mHandler; @Mock private UserContextProvider mUserContextProvider; + @Mock private UserTracker mUserTracker; + @Mock private SecureSettings mSecureSettings; private ControlsComponent mControlsComponent; private TestableLooper mTestableLooper; @@ -149,9 +153,14 @@ public class GlobalActionsDialogTest extends SysuiTestCase { when(mUserContextProvider.getUserContext()).thenReturn(mContext); mControlsComponent = new ControlsComponent( true, + mContext, () -> mControlsController, () -> mControlsUiController, - () -> mControlsListingController + () -> mControlsListingController, + mLockPatternUtils, + mKeyguardStateController, + mUserTracker, + mSecureSettings ); mGlobalActionsDialog = new GlobalActionsDialog(mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java new file mode 100644 index 000000000000..b3ad6ef8da6e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.people; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.app.people.ConversationChannel; +import android.app.people.IPeopleManager; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.UserHandle; +import android.testing.AndroidTestingRunner; +import android.widget.RemoteViews; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.shared.system.PeopleProviderUtils; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class PeopleProviderTest extends SysuiTestCase { + private static final String TAG = "PeopleProviderTest"; + + private static final Uri URI = Uri.parse(PeopleProviderUtils.PEOPLE_PROVIDER_SCHEME + + PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY); + + private static final String SHORTCUT_ID_A = "shortcut_id_a"; + private static final String PACKAGE_NAME_A = "package_name_a"; + private static final UserHandle USER_HANDLE_A = UserHandle.of(1); + private static final String USERNAME = "username"; + + private final ShortcutInfo mShortcutInfo = + new ShortcutInfo.Builder(mContext, SHORTCUT_ID_A).setLongLabel(USERNAME).build(); + private final ConversationChannel mConversationChannel = + new ConversationChannel(mShortcutInfo, USER_HANDLE_A.getIdentifier(), + null, null, 0L, false); + + private Bundle mExtras = new Bundle(); + + @Mock + private LauncherApps mLauncherApps; + @Mock + private PackageManager mPackageManager; + @Mock + private IPeopleManager mPeopleManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContext.setMockPackageManager(mPackageManager); + + PeopleProviderTestable provider = new PeopleProviderTestable(); + provider.initializeForTesting( + mContext, PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY); + provider.setLauncherApps(mLauncherApps); + provider.setPeopleManager(mPeopleManager); + mContext.getContentResolver().addProvider( + PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY, provider); + + mContext.getTestablePermissions().setPermission( + PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION, + PackageManager.PERMISSION_GRANTED); + + when(mPeopleManager.getConversation( + eq(PACKAGE_NAME_A), eq(USER_HANDLE_A.getIdentifier()), eq(SHORTCUT_ID_A))) + .thenReturn(mConversationChannel); + + mExtras.putString(PeopleProviderUtils.EXTRAS_KEY_SHORTCUT_ID, SHORTCUT_ID_A); + mExtras.putString(PeopleProviderUtils.EXTRAS_KEY_PACKAGE_NAME, PACKAGE_NAME_A); + mExtras.putParcelable(PeopleProviderUtils.EXTRAS_KEY_USER_HANDLE, USER_HANDLE_A); + } + + @Test + public void testPermissionDeniedThrowsSecurityException() throws RemoteException { + mContext.getTestablePermissions().setPermission( + PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION, + PackageManager.PERMISSION_DENIED); + try { + Bundle result = mContext.getContentResolver() + .call(URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, null); + Assert.fail("Call should have failed with SecurityException"); + } catch (SecurityException e) { + } catch (Exception e) { + Assert.fail("Call should have failed with SecurityException"); + } + } + + @Test + public void testPermissionGrantedNoExtraReturnsNull() throws RemoteException { + try { + Bundle result = mContext.getContentResolver() + .call(URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, null); + Assert.fail("Call should have failed with IllegalArgumentException"); + } catch (IllegalArgumentException e) { + } catch (Exception e) { + Assert.fail("Call should have failed with IllegalArgumentException"); + } + } + + @Test + public void testPermissionGrantedExtrasReturnsRemoteViews() throws RemoteException { + try { + Bundle result = mContext.getContentResolver().call( + URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, mExtras); + RemoteViews views = result.getParcelable( + PeopleProviderUtils.RESPONSE_KEY_REMOTE_VIEWS); + assertThat(views).isNotNull(); + } catch (Exception e) { + Assert.fail("Fail " + e); + } + } + + @Test + public void testPermissionGrantedNoConversationForShortcutReturnsNull() throws RemoteException { + when(mPeopleManager.getConversation( + eq(PACKAGE_NAME_A), eq(USER_HANDLE_A.getIdentifier()), eq(SHORTCUT_ID_A))) + .thenReturn(null); + try { + Bundle result = mContext.getContentResolver().call( + URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, mExtras); + assertThat(result).isNull(); + } catch (Exception e) { + Assert.fail("Fail " + e); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java new file mode 100644 index 000000000000..ac1893413c50 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.people; + +import android.app.people.IPeopleManager; +import android.content.Context; +import android.content.pm.LauncherApps; +import android.content.pm.ProviderInfo; + +public class PeopleProviderTestable extends PeopleProvider { + + public void initializeForTesting(Context context, String authority) { + ProviderInfo info = new ProviderInfo(); + info.authority = authority; + + attachInfoForTesting(context, info); + } + + void setLauncherApps(LauncherApps launcherApps) { + mLauncherApps = launcherApps; + } + + void setPeopleManager(IPeopleManager peopleManager) { + mPeopleManager = peopleManager; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index b7b967866d47..d3dbe2bf7d2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -25,6 +25,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger +import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController @@ -36,15 +37,19 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.FeatureFlags +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock @@ -91,6 +96,15 @@ class DeviceControlsTileTest : SysuiTestCase() { private lateinit var testableLooper: TestableLooper private lateinit var tile: DeviceControlsTile + @Mock + private lateinit var keyguardStateController: KeyguardStateController + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private lateinit var userTracker: UserTracker + @Mock + private lateinit var lockPatternUtils: LockPatternUtils + @Mock + private lateinit var secureSettings: SecureSettings + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -101,9 +115,14 @@ class DeviceControlsTileTest : SysuiTestCase() { controlsComponent = ControlsComponent( true, + mContext, { controlsController }, { controlsUiController }, - { controlsListingController } + { controlsListingController }, + lockPatternUtils, + keyguardStateController, + userTracker, + secureSettings ) globalSettings = FakeSettings() @@ -111,16 +130,20 @@ class DeviceControlsTileTest : SysuiTestCase() { globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 1) `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(true) + `when`(userTracker.userHandle.identifier).thenReturn(0) + tile = createTile() } @Test fun testAvailable() { + `when`(controlsController.available).thenReturn(true) assertThat(tile.isAvailable).isTrue() } @Test fun testNotAvailableFeature() { + `when`(controlsController.available).thenReturn(true) `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(false) assertThat(tile.isAvailable).isFalse() @@ -130,9 +153,14 @@ class DeviceControlsTileTest : SysuiTestCase() { fun testNotAvailableControls() { controlsComponent = ControlsComponent( false, + mContext, { controlsController }, { controlsUiController }, - { controlsListingController } + { controlsListingController }, + lockPatternUtils, + keyguardStateController, + userTracker, + secureSettings ) tile = createTile() @@ -264,4 +292,4 @@ class DeviceControlsTileTest : SysuiTestCase() { globalSettings ) } -}
\ No newline at end of file +} diff --git a/services/core/Android.bp b/services/core/Android.bp index 542a260a5130..9f369101dd7c 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -129,8 +129,8 @@ java_library_static { "android.hardware.light-V1-java", "android.hardware.tv.cec-V1.0-java", "android.hardware.weaver-V1.0-java", - "android.hardware.biometrics.face-V1.1-java", "android.hardware.biometrics.face-V1-java", + "android.hardware.biometrics.face-V1.0-java", "android.hardware.biometrics.fingerprint-V2.3-java", "android.hardware.biometrics.fingerprint-V1-java", "android.hardware.oemlock-V1.0-java", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 558fbc25d7df..154e1831ceee 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3584,10 +3584,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) { - handleRegisterNetworkRequest(Collections.singletonList(nri)); + handleRegisterNetworkRequests(Collections.singleton(nri)); } - private void handleRegisterNetworkRequest(@NonNull final List<NetworkRequestInfo> nris) { + private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) { ensureRunningOnConnectivityServiceThread(); for (final NetworkRequestInfo nri : nris) { mNetworkRequestInfoLogs.log("REGISTER " + nri); @@ -3718,7 +3718,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private NetworkRequestInfo getNriForAppRequest( NetworkRequest request, int callingUid, String requestedOperation) { - final NetworkRequestInfo nri = mNetworkRequests.get(request); + // Looking up the app passed param request in mRequests isn't possible since it may return + // null for a request managed by a per-app default. Therefore use getNriForAppRequest() to + // do the lookup since that will also find per-app default managed requests. + final NetworkRequestInfo nri = getNriForAppRequest(request); if (nri != null) { if (Process.SYSTEM_UID != callingUid && Process.NETWORK_STACK_UID != callingUid @@ -3767,8 +3770,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nri == null) { return; } - // handleReleaseNetworkRequest() paths don't apply to multilayer requests. - ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequest"); if (VDBG || (DBG && request.isRequest())) { log("releasing " + request + " (release request)"); } @@ -3780,7 +3781,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) { ensureRunningOnConnectivityServiceThread(); - nri.unlinkDeathRecipient(); for (final NetworkRequest req : nri.mRequests) { mNetworkRequests.remove(req); @@ -3803,6 +3803,16 @@ public class ConnectivityService extends IConnectivityManager.Stub cancelNpiRequests(nri); } + private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) { + for (final NetworkRequestInfo nri : nris) { + if (mDefaultRequest == nri) { + // Make sure we never remove the default request. + continue; + } + handleRemoveNetworkRequest(nri); + } + } + private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) { for (final NetworkRequest req : nri.mRequests) { cancelNpiRequest(req); @@ -4973,12 +4983,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void onUserAdded(int userId) { - mPermissionMonitor.onUserAdded(userId); + private void onUserAdded(UserHandle user) { + mPermissionMonitor.onUserAdded(user); } - private void onUserRemoved(int userId) { - mPermissionMonitor.onUserRemoved(userId); + private void onUserRemoved(UserHandle user) { + mPermissionMonitor.onUserRemoved(user); } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -4986,15 +4996,18 @@ public class ConnectivityService extends IConnectivityManager.Stub public void onReceive(Context context, Intent intent) { ensureRunningOnConnectivityServiceThread(); final String action = intent.getAction(); - final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); - // UserId should be filled for below intents, check the existence. - if (userId == UserHandle.USER_NULL) return; + // User should be filled for below intents, check the existence. + if (user == null) { + Log.wtf(TAG, intent.getAction() + " broadcast without EXTRA_USER"); + return; + } if (Intent.ACTION_USER_ADDED.equals(action)) { - onUserAdded(userId); + onUserAdded(user); } else if (Intent.ACTION_USER_REMOVED.equals(action)) { - onUserRemoved(userId); + onUserRemoved(user); } else { Log.wtf(TAG, "received unexpected intent: " + action); } @@ -5107,6 +5120,21 @@ public class ConnectivityService extends IConnectivityManager.Stub final int mUid; @Nullable final String mCallingAttributionTag; + // In order to preserve the mapping of NetworkRequest-to-callback when apps register + // callbacks using a returned NetworkRequest, the original NetworkRequest needs to be + // maintained for keying off of. This is only a concern when the original nri + // mNetworkRequests changes which happens currently for apps that register callbacks to + // track the default network. In those cases, the nri is updated to have mNetworkRequests + // that match the per-app default nri that currently tracks the calling app's uid so that + // callbacks are fired at the appropriate time. When the callbacks fire, + // mNetworkRequestForCallback will be used so as to preserve the caller's mapping. When + // callbacks are updated to key off of an nri vs NetworkRequest, this stops being an issue. + // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects. + @NonNull + private final NetworkRequest mNetworkRequestForCallback; + NetworkRequest getNetworkRequestForCallback() { + return mNetworkRequestForCallback; + } /** * Get the list of UIDs this nri applies to. @@ -5122,13 +5150,15 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { - this(Collections.singletonList(r), pi, callingAttributionTag); + this(Collections.singletonList(r), r, pi, callingAttributionTag); } NetworkRequestInfo(@NonNull final List<NetworkRequest> r, - @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { + @NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi, + @Nullable String callingAttributionTag) { + ensureAllNetworkRequestsHaveType(r); mRequests = initializeRequests(r); - ensureAllNetworkRequestsHaveType(mRequests); + mNetworkRequestForCallback = requestForCallback; mPendingIntent = pi; mMessenger = null; mBinder = null; @@ -5140,15 +5170,17 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m, @Nullable final IBinder binder, @Nullable String callingAttributionTag) { - this(Collections.singletonList(r), m, binder, callingAttributionTag); + this(Collections.singletonList(r), r, m, binder, callingAttributionTag); } - NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @Nullable final Messenger m, + NetworkRequestInfo(@NonNull final List<NetworkRequest> r, + @NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m, @Nullable final IBinder binder, @Nullable String callingAttributionTag) { super(); + ensureAllNetworkRequestsHaveType(r); mRequests = initializeRequests(r); + mNetworkRequestForCallback = requestForCallback; mMessenger = m; - ensureAllNetworkRequestsHaveType(mRequests); mBinder = binder; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); @@ -5163,12 +5195,26 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + NetworkRequestInfo(@NonNull final NetworkRequestInfo nri, + @NonNull final List<NetworkRequest> r) { + super(); + ensureAllNetworkRequestsHaveType(r); + mRequests = initializeRequests(r); + mNetworkRequestForCallback = nri.getNetworkRequestForCallback(); + mMessenger = nri.mMessenger; + mBinder = nri.mBinder; + mPid = nri.mPid; + mUid = nri.mUid; + mPendingIntent = nri.mPendingIntent; + mCallingAttributionTag = nri.mCallingAttributionTag; + } + NetworkRequestInfo(@NonNull final NetworkRequest r) { this(Collections.singletonList(r)); } NetworkRequestInfo(@NonNull final List<NetworkRequest> r) { - this(r, null /* pi */, null /* callingAttributionTag */); + this(r, r.get(0), null /* pi */, null /* callingAttributionTag */); } // True if this NRI is being satisfied. It also accounts for if the nri has its satisifer @@ -5327,7 +5373,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities} // is unused and will be replaced by ones appropriate for the caller. // This allows callers to keep track of the default network for their app. - networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid); + networkCapabilities = copyDefaultNetworkCapabilitiesForUid( + defaultNc, callingUid, callingPackageName); enforceAccessPermission(); break; case TRACK_SYSTEM_DEFAULT: @@ -5366,10 +5413,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } ensureValid(networkCapabilities); - NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, + final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, nextNetworkRequestId(), reqType); - NetworkRequestInfo nri = - new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag); + final NetworkRequestInfo nri = getNriToRegister( + networkRequest, messenger, binder, callingAttributionTag); if (DBG) log("requestNetwork for " + nri); // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were @@ -5391,6 +5438,30 @@ public class ConnectivityService extends IConnectivityManager.Stub return networkRequest; } + /** + * Return the nri to be used when registering a network request. Specifically, this is used with + * requests registered to track the default request. If there is currently a per-app default + * tracking the app requestor, then we need to create a version of this nri that mirrors that of + * the tracking per-app default so that callbacks are sent to the app requestor appropriately. + * @param nr the network request for the nri. + * @param msgr the messenger for the nri. + * @param binder the binder for the nri. + * @param callingAttributionTag the calling attribution tag for the nri. + * @return the nri to register. + */ + private NetworkRequestInfo getNriToRegister(@NonNull final NetworkRequest nr, + @Nullable final Messenger msgr, @Nullable final IBinder binder, + @Nullable String callingAttributionTag) { + final List<NetworkRequest> requests; + if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) { + requests = copyDefaultNetworkRequestsForUid( + nr.getRequestorUid(), nr.getRequestorPackageName()); + } else { + requests = Collections.singletonList(nr); + } + return new NetworkRequestInfo(requests, nr, msgr, binder, callingAttributionTag); + } + private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities, String callingPackageName, String callingAttributionTag) { if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) { @@ -5684,6 +5755,102 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** + * Return the default network request currently tracking the given uid. + * @param uid the uid to check. + * @return the NetworkRequestInfo tracking the given uid. + */ + @NonNull + private NetworkRequestInfo getDefaultRequestTrackingUid(@NonNull final int uid) { + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + if (nri == mDefaultRequest) { + continue; + } + // Checking the first request is sufficient as only multilayer requests will have more + // than one request and for multilayer, all requests will track the same uids. + if (nri.mRequests.get(0).networkCapabilities.appliesToUid(uid)) { + return nri; + } + } + return mDefaultRequest; + } + + /** + * Get a copy of the network requests of the default request that is currently tracking the + * given uid. + * @param requestorUid the uid to check the default for. + * @param requestorPackageName the requestor's package name. + * @return a copy of the default's NetworkRequest that is tracking the given uid. + */ + @NonNull + private List<NetworkRequest> copyDefaultNetworkRequestsForUid( + @NonNull final int requestorUid, @NonNull final String requestorPackageName) { + return copyNetworkRequestsForUid( + getDefaultRequestTrackingUid(requestorUid).mRequests, + requestorUid, requestorPackageName); + } + + /** + * Copy the given nri's NetworkRequest collection. + * @param requestsToCopy the NetworkRequest collection to be copied. + * @param requestorUid the uid to set on the copied collection. + * @param requestorPackageName the package name to set on the copied collection. + * @return the copied NetworkRequest collection. + */ + @NonNull + private List<NetworkRequest> copyNetworkRequestsForUid( + @NonNull final List<NetworkRequest> requestsToCopy, @NonNull final int requestorUid, + @NonNull final String requestorPackageName) { + final List<NetworkRequest> requests = new ArrayList<>(); + for (final NetworkRequest nr : requestsToCopy) { + requests.add(new NetworkRequest(copyDefaultNetworkCapabilitiesForUid( + nr.networkCapabilities, requestorUid, requestorPackageName), + nr.legacyType, nextNetworkRequestId(), nr.type)); + } + return requests; + } + + @NonNull + private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid( + @NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid, + @NonNull final String requestorPackageName) { + final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy); + netCap.removeCapability(NET_CAPABILITY_NOT_VPN); + netCap.setSingleUid(requestorUid); + netCap.setUids(new ArraySet<>()); + restrictRequestUidsForCallerAndSetRequestorInfo( + netCap, requestorUid, requestorPackageName); + return netCap; + } + + /** + * Get the nri that is currently being tracked for callbacks by per-app defaults. + * @param nr the network request to check for equality against. + * @return the nri if one exists, null otherwise. + */ + @Nullable + private NetworkRequestInfo getNriForAppRequest(@NonNull final NetworkRequest nr) { + for (final NetworkRequestInfo nri : mNetworkRequests.values()) { + if (nri.getNetworkRequestForCallback().equals(nr)) { + return nri; + } + } + return null; + } + + /** + * Check if an nri is currently being managed by per-app default networking. + * @param nri the nri to check. + * @return true if this nri is currently being managed by per-app default networking. + */ + private boolean isPerAppTrackedNri(@NonNull final NetworkRequestInfo nri) { + // nri.mRequests.get(0) is only different from the original request filed in + // nri.getNetworkRequestForCallback() if nri.mRequests was changed by per-app default + // functionality therefore if these two don't match, it means this particular nri is + // currently being managed by a per-app default. + return nri.getNetworkRequestForCallback() != nri.mRequests.get(0); + } + + /** * Determine if an nri is a managed default request that disallows default networking. * @param nri the request to evaluate * @return true if device-default networking is disallowed @@ -6761,13 +6928,9 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } Bundle bundle = new Bundle(); - // In the case of multi-layer NRIs, the first request is not necessarily the one that - // is satisfied. This is vexing, but the ConnectivityManager code that receives this - // callback is only using the request as a token to identify the callback, so it doesn't - // matter too much at this point as long as the callback can be found. // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects. // TODO: check if defensive copies of data is needed. - final NetworkRequest nrForCallback = new NetworkRequest(nri.mRequests.get(0)); + final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback(); putParcelable(bundle, nrForCallback); Message msg = Message.obtain(); if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) { @@ -8707,7 +8870,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) { log("set OEM network preferences :" + preference.toString()); } - final List<NetworkRequestInfo> nris = + final ArraySet<NetworkRequestInfo> nris = new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference); updateDefaultNetworksForOemNetworkPreference(nris); mOemNetworkPreferences = preference; @@ -8719,27 +8882,88 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void updateDefaultNetworksForOemNetworkPreference( - @NonNull final List<NetworkRequestInfo> nris) { + @NonNull final Set<NetworkRequestInfo> nris) { + handleRemoveNetworkRequests(mDefaultNetworkRequests); + addPerAppDefaultNetworkRequests(nris); + } + + private void addPerAppDefaultNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) { ensureRunningOnConnectivityServiceThread(); - clearNonDefaultNetworkAgents(); - addDefaultNetworkRequests(nris); + mDefaultNetworkRequests.addAll(nris); + final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate = + getPerAppCallbackRequestsToUpdate(); + handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate); + final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris); + nrisToRegister.addAll( + createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate)); + handleRegisterNetworkRequests(nrisToRegister); } - private void clearNonDefaultNetworkAgents() { - // Copy mDefaultNetworkRequests to iterate and remove elements from it in - // handleRemoveNetworkRequest() without getting a ConcurrentModificationException. - final NetworkRequestInfo[] nris = - mDefaultNetworkRequests.toArray(new NetworkRequestInfo[0]); + /** + * All current requests that are tracking the default network need to be assessed as to whether + * or not the current set of per-application default requests will be changing their default + * network. If so, those requests will need to be updated so that they will send callbacks for + * default network changes at the appropriate time. Additionally, those requests tracking the + * default that were previously updated by this flow will need to be reassessed. + * @return the nris which will need to be updated. + */ + private ArraySet<NetworkRequestInfo> getPerAppCallbackRequestsToUpdate() { + final ArraySet<NetworkRequestInfo> defaultCallbackRequests = new ArraySet<>(); + // Get the distinct nris to check since for multilayer requests, it is possible to have the + // same nri in the map's values for each of its NetworkRequest objects. + final ArraySet<NetworkRequestInfo> nris = new ArraySet<>(mNetworkRequests.values()); for (final NetworkRequestInfo nri : nris) { - if (mDefaultRequest != nri) { - handleRemoveNetworkRequest(nri); + // Include this nri if it is currently being tracked. + if (isPerAppTrackedNri(nri)) { + defaultCallbackRequests.add(nri); + continue; + } + // We only track callbacks for requests tracking the default. + if (NetworkRequest.Type.TRACK_DEFAULT != nri.mRequests.get(0).type) { + continue; + } + // Include this nri if it will be tracked by the new per-app default requests. + final boolean isNriGoingToBeTracked = + getDefaultRequestTrackingUid(nri.mUid) != mDefaultRequest; + if (isNriGoingToBeTracked) { + defaultCallbackRequests.add(nri); } } + return defaultCallbackRequests; } - private void addDefaultNetworkRequests(@NonNull final List<NetworkRequestInfo> nris) { - mDefaultNetworkRequests.addAll(nris); - handleRegisterNetworkRequest(nris); + /** + * Create nris for those network requests that are currently tracking the default network that + * are being controlled by a per-application default. + * @param perAppCallbackRequestsForUpdate the baseline network requests to be used as the + * foundation when creating the nri. Important items include the calling uid's original + * NetworkRequest to be used when mapping callbacks as well as the caller's uid and name. These + * requests are assumed to have already been validated as needing to be updated. + * @return the Set of nris to use when registering network requests. + */ + private ArraySet<NetworkRequestInfo> createPerAppCallbackRequestsToRegister( + @NonNull final ArraySet<NetworkRequestInfo> perAppCallbackRequestsForUpdate) { + final ArraySet<NetworkRequestInfo> callbackRequestsToRegister = new ArraySet<>(); + for (final NetworkRequestInfo callbackRequest : perAppCallbackRequestsForUpdate) { + final NetworkRequestInfo trackingNri = + getDefaultRequestTrackingUid(callbackRequest.mUid); + + // If this nri is not being tracked, the change it back to an untracked nri. + if (trackingNri == mDefaultRequest) { + callbackRequestsToRegister.add(new NetworkRequestInfo( + callbackRequest, + Collections.singletonList(callbackRequest.getNetworkRequestForCallback()))); + continue; + } + + final String requestorPackageName = + callbackRequest.mRequests.get(0).getRequestorPackageName(); + callbackRequestsToRegister.add(new NetworkRequestInfo( + callbackRequest, + copyNetworkRequestsForUid( + trackingNri.mRequests, callbackRequest.mUid, requestorPackageName))); + } + return callbackRequestsToRegister; } /** @@ -8747,9 +8971,9 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @VisibleForTesting final class OemNetworkRequestFactory { - List<NetworkRequestInfo> createNrisFromOemNetworkPreferences( + ArraySet<NetworkRequestInfo> createNrisFromOemNetworkPreferences( @NonNull final OemNetworkPreferences preference) { - final List<NetworkRequestInfo> nris = new ArrayList<>(); + final ArraySet<NetworkRequestInfo> nris = new ArraySet<>(); final SparseArray<Set<Integer>> uids = createUidsFromOemNetworkPreferences(preference); for (int i = 0; i < uids.size(); i++) { diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 0aee78050929..7f0b9621bb36 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -568,7 +568,7 @@ public final class SensorPrivacyService extends SystemService { // User may no longer exist or isn't set continue; } - int sensor = parser.getAttributeIndex(null, XML_ATTRIBUTE_SENSOR); + int sensor = parser.getAttributeInt(null, XML_ATTRIBUTE_SENSOR); boolean isEnabled = parser.getAttributeBoolean(null, XML_ATTRIBUTE_ENABLED); SparseBooleanArray userIndividualEnabled = individualEnabled.get( diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 68a084e6d249..8af1b5be1517 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -264,8 +264,8 @@ public final class PlaybackActivityMonitor */ public void playerEvent(int piid, int event, int deviceId, int binderUid) { if (DEBUG) { - Log.v(TAG, String.format("playerEvent(piid=%d, deviceId=%d, event=%d)", - piid, deviceId, event)); + Log.v(TAG, String.format("playerEvent(piid=%d, deviceId=%d, event=%s)", + piid, deviceId, AudioPlaybackConfiguration.playerStateToString(event))); } final boolean change; synchronized(mPlayerLock) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 0b78dd01f855..a4b3ac57a4df 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -46,7 +46,7 @@ import java.util.ArrayList; /** * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0} - * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces. + * HIDL interface. */ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java index 1a7544fc7f01..fc1200a4b42a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java @@ -41,7 +41,7 @@ import java.util.Arrays; /** * Face-specific enroll client supporting the {@link android.hardware.biometrics.face.V1_0} - * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces. + * HIDL interface. */ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> { @@ -103,18 +103,9 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> { disabledFeatures.add(disabledFeature); } - android.hardware.biometrics.face.V1_1.IBiometricsFace daemon11 = - android.hardware.biometrics.face.V1_1.IBiometricsFace.castFrom(getFreshDaemon()); try { - final int status; - if (daemon11 != null) { - status = daemon11.enroll_1_1(token, mTimeoutSec, disabledFeatures, mSurfaceHandle); - } else if (mSurfaceHandle == null) { - status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures); - } else { - Slog.e(TAG, "enroll(): surface is only supported in @1.1 HAL"); - status = BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS; - } + final int status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures); + if (status != Status.OK) { onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java index c3d54c2b7fbb..72c5ee5e78c4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java @@ -29,8 +29,7 @@ import com.android.server.biometrics.sensors.GenerateChallengeClient; /** * Face-specific generateChallenge client supporting the - * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1} - * HIDL interfaces. + * {@link android.hardware.biometrics.face.V1_0} HIDL interface. */ public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiometricsFace> { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java index 722a3b843e12..b1083d410fec 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java @@ -33,7 +33,7 @@ import com.android.server.biometrics.sensors.HalClientMonitor; /** * Face-specific getFeature client supporting the {@link android.hardware.biometrics.face.V1_0} - * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces. + * HIDL interface. */ public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java index abfda499cf0f..1e3b92dcbf61 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java @@ -33,8 +33,7 @@ import java.util.Map; /** * Face-specific internal cleanup client supporting the - * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1} - * HIDL interfaces. + * {@link android.hardware.biometrics.face.V1_0} HIDL interface. */ class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsFace> { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java index 9a0974b472cb..f2a9afce1ff7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java @@ -32,8 +32,7 @@ import java.util.List; /** * Face-specific internal enumerate client supporting the - * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1} - * HIDL interfaces. + * {@link android.hardware.biometrics.face.V1_0} HIDL interface. */ class FaceInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFace> { private static final String TAG = "FaceInternalEnumerateClient"; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java index acae89928460..d63791c99dd4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java @@ -33,7 +33,7 @@ import java.util.Map; /** * Face-specific removal client supporting the {@link android.hardware.biometrics.face.V1_0} - * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces. + * HIDL interface. */ class FaceRemovalClient extends RemovalClient<Face, IBiometricsFace> { private static final String TAG = "FaceRemovalClient"; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java index 14a46481ddc6..9d977d60e705 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java @@ -30,7 +30,7 @@ import java.util.ArrayList; /** * Face-specific resetLockout client supporting the {@link android.hardware.biometrics.face.V1_0} - * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces. + * HIDL interface. */ public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java index e5edfafcef61..28580dece284 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java @@ -27,7 +27,7 @@ import com.android.server.biometrics.sensors.RevokeChallengeClient; /** * Face-specific revokeChallenge client supporting the {@link android.hardware.biometrics.face.V1_0} - * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces. + * HIDL interface. */ public class FaceRevokeChallengeClient extends RevokeChallengeClient<IBiometricsFace> { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java index 6290e001f65b..cc3d8f0e28ba 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java @@ -33,7 +33,7 @@ import java.util.ArrayList; /** * Face-specific setFeature client supporting the {@link android.hardware.biometrics.face.V1_0} - * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces. + * HIDL interface. */ public class FaceSetFeatureClient extends HalClientMonitor<IBiometricsFace> { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java index 13bd1c27d8c8..4cdb68df70af 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java @@ -18,11 +18,11 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.Nullable; import android.hardware.biometrics.face.V1_0.FaceError; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; import android.hardware.biometrics.face.V1_0.OptionalBool; import android.hardware.biometrics.face.V1_0.OptionalUint64; import android.hardware.biometrics.face.V1_0.Status; -import android.hardware.biometrics.face.V1_1.IBiometricsFace; import android.os.NativeHandle; import android.os.RemoteException; import android.util.Slog; @@ -129,15 +129,4 @@ public class TestHal extends IBiometricsFace.Stub { return 0; } - @Override - public int enrollRemotely(ArrayList<Byte> hat, int timeoutSec, - ArrayList<Integer> disabledFeatures) { - return 0; - } - - @Override - public int enroll_1_1(ArrayList<Byte> hat, int timeoutSec, ArrayList<Integer> disabledFeatures, - NativeHandle nativeHandle) { - return 0; - } } diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index 8d21f6f0f59f..8bf188696c27 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -83,9 +83,8 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse private final INetd mNetd; private final Dependencies mDeps; - // Values are User IDs. @GuardedBy("this") - private final Set<Integer> mUsers = new HashSet<>(); + private final Set<UserHandle> mUsers = new HashSet<>(); // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission. @GuardedBy("this") @@ -173,10 +172,7 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms); } - final List<UserHandle> users = mUserManager.getUserHandles(true /* excludeDying */); - for (UserHandle user : users) { - mUsers.add(user.getIdentifier()); - } + mUsers.addAll(mUserManager.getUserHandles(true /* excludeDying */)); final SparseArray<ArraySet<String>> systemPermission = SystemConfig.getInstance().getSystemPermissions(); @@ -259,16 +255,15 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse return array; } - private void update(Set<Integer> users, Map<Integer, Boolean> apps, boolean add) { + private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) { List<Integer> network = new ArrayList<>(); List<Integer> system = new ArrayList<>(); for (Entry<Integer, Boolean> app : apps.entrySet()) { List<Integer> list = app.getValue() ? system : network; - for (int user : users) { - final UserHandle handle = UserHandle.of(user); - if (handle == null) continue; + for (UserHandle user : users) { + if (user == null) continue; - list.add(UserHandle.getUid(handle, app.getKey())); + list.add(UserHandle.getUid(user, app.getKey())); } } try { @@ -291,14 +286,10 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse * * @hide */ - public synchronized void onUserAdded(int user) { - if (user < 0) { - loge("Invalid user in onUserAdded: " + user); - return; - } + public synchronized void onUserAdded(@NonNull UserHandle user) { mUsers.add(user); - Set<Integer> users = new HashSet<>(); + Set<UserHandle> users = new HashSet<>(); users.add(user); update(users, mApps, true); } @@ -310,14 +301,10 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse * * @hide */ - public synchronized void onUserRemoved(int user) { - if (user < 0) { - loge("Invalid user in onUserRemoved: " + user); - return; - } + public synchronized void onUserRemoved(@NonNull UserHandle user) { mUsers.remove(user); - Set<Integer> users = new HashSet<>(); + Set<UserHandle> users = new HashSet<>(); users.add(user); update(users, mApps, false); } diff --git a/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING b/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING new file mode 100644 index 000000000000..7fbf426c71c4 --- /dev/null +++ b/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "UpdatableSystemFontTest" + } + ] +} diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index a8a6bcec2313..2112247650a5 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -72,7 +72,6 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.os.incremental.IncrementalManager; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.service.pm.PackageServiceDumpProto; @@ -2615,6 +2614,8 @@ public final class Settings implements Watchable, Snappable { } else { serializer.attributeInt(null, "sharedUserId", pkg.appId); } + serializer.attributeFloat(null, "loadingProgress", + pkg.getIncrementalStates().getProgress()); writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions); @@ -3389,6 +3390,9 @@ public final class Settings implements Watchable, Snappable { if (ps.appId <= 0) { ps.appId = parser.getAttributeInt(null, "sharedUserId", 0); } + final float loadingProgress = + parser.getAttributeFloat(null, "loadingProgress", 0); + ps.setLoadingProgress(loadingProgress); int outerDepth = parser.getDepth(); int type; @@ -4582,7 +4586,7 @@ public final class Settings implements Watchable, Snappable { pw.print(prefix); pw.print(" installerAttributionTag="); pw.println(ps.installSource.installerAttributionTag); } - if (IncrementalManager.isIncrementalPath(ps.getPathString())) { + if (ps.isPackageLoading()) { pw.print(prefix); pw.println(" loadingProgress=" + (int) (ps.getIncrementalStates().getProgress() * 100) + "%"); } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 88fdc4aad5cf..c0b820293649 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -5138,10 +5138,15 @@ public final class PowerManagerService extends SystemService // Get current time before acquiring the lock so that the calculated end time is as // accurate as possible. final long nowElapsed = SystemClock.elapsedRealtime(); - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.BATTERY_PREDICTION) + != PackageManager.PERMISSION_GRANTED) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, "setBatteryDischargePrediction"); + } final long timeRemainingMs = timeRemaining.getDuration().toMillis(); - // A non-positive number means the battery should be dead right now... + // A non-positive number means the battery should be dead right now... Preconditions.checkArgumentPositive(timeRemainingMs, "Given time remaining is not positive: " + timeRemainingMs); diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 8aab3a66ada3..12590eba81f8 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -38,10 +38,12 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgent.ValidationStatus; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.RouteInfo; import android.net.TelephonyNetworkSpecifier; +import android.net.Uri; import android.net.annotations.PolicyDirection; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; @@ -151,6 +153,9 @@ public class VcnGatewayConnection extends StateMachine { @VisibleForTesting(visibility = Visibility.PRIVATE) static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM"; + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String SAFEMODE_TIMEOUT_ALARM = TAG + "_SAFEMODE_TIMEOUT_ALARM"; + private static final int[] MERGED_CAPABILITIES = new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING}; private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; @@ -167,6 +172,9 @@ public class VcnGatewayConnection extends StateMachine { @VisibleForTesting(visibility = Visibility.PRIVATE) static final int TEARDOWN_TIMEOUT_SECONDS = 5; + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int SAFEMODE_TIMEOUT_SECONDS = 30; + private interface EventInfo {} /** @@ -409,6 +417,23 @@ public class VcnGatewayConnection extends StateMachine { // TODO(b/178426520): implement handling of this event private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9; + /** + * Sent when this VcnGatewayConnection has entered Safemode. + * + * <p>A VcnGatewayConnection enters Safemode when it takes over {@link + * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}. + * + * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link + * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down + * its VcnGatewayConnectin(s). + * + * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not + * validated yet), and RetryTimeoutState. + * + * @param arg1 The "all" token; this signal is always honored. + */ + private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10; + @VisibleForTesting(visibility = Visibility.PRIVATE) @NonNull final DisconnectedState mDisconnectedState = new DisconnectedState(); @@ -520,11 +545,13 @@ public class VcnGatewayConnection extends StateMachine { * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable * otherwise. */ - private NetworkAgent mNetworkAgent; + @VisibleForTesting(visibility = Visibility.PRIVATE) + NetworkAgent mNetworkAgent; @Nullable private WakeupMessage mTeardownTimeoutAlarm; @Nullable private WakeupMessage mDisconnectRequestAlarm; @Nullable private WakeupMessage mRetryTimeoutAlarm; + @Nullable private WakeupMessage mSafemodeTimeoutAlarm; public VcnGatewayConnection( @NonNull VcnContext vcnContext, @@ -611,6 +638,7 @@ public class VcnGatewayConnection extends StateMachine { cancelTeardownTimeoutAlarm(); cancelDisconnectRequestAlarm(); cancelRetryTimeoutAlarm(); + cancelSafemodeAlarm(); mUnderlyingNetworkTracker.teardown(); } @@ -899,6 +927,30 @@ public class VcnGatewayConnection extends StateMachine { removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED); } + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setSafemodeAlarm() { + // Only schedule a NEW alarm if none is already set. + if (mSafemodeTimeoutAlarm != null) { + return; + } + + final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL); + mSafemodeTimeoutAlarm = + createScheduledAlarm( + SAFEMODE_TIMEOUT_ALARM, + delayedMessage, + TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS)); + } + + private void cancelSafemodeAlarm() { + if (mSafemodeTimeoutAlarm != null) { + mSafemodeTimeoutAlarm.cancel(); + mSafemodeTimeoutAlarm = null; + } + + removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED); + } + private void sessionLost(int token, @Nullable Exception exception) { sendMessageAndAcquireWakeLock( EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception)); @@ -1072,6 +1124,8 @@ public class VcnGatewayConnection extends StateMachine { if (mIkeSession != null || mNetworkAgent != null) { Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState"); } + + cancelSafemodeAlarm(); } @Override @@ -1095,6 +1149,12 @@ public class VcnGatewayConnection extends StateMachine { break; } } + + @Override + protected void exitState() { + // Safe to blindly set up, as it is cancelled and cleared on entering this state + setSafemodeAlarm(); + } } private abstract class ActiveBaseState extends BaseState { @@ -1185,6 +1245,10 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mDisconnectedState); } break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -1267,6 +1331,10 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -1310,6 +1378,14 @@ public class VcnGatewayConnection extends StateMachine { public void unwanted() { teardownAsynchronously(); } + + @Override + public void onValidationStatus( + @ValidationStatus int status, @Nullable Uri redirectUri) { + if (status == NetworkAgent.VALIDATION_STATUS_VALID) { + clearFailedAttemptCounterAndSafeModeAlarm(); + } + } }; agent.register(); @@ -1318,6 +1394,14 @@ public class VcnGatewayConnection extends StateMachine { return agent; } + protected void clearFailedAttemptCounterAndSafeModeAlarm() { + mVcnContext.ensureRunningOnLooperThread(); + + // Validated connection, clear failed attempt counter + mFailedAttempts = 0; + cancelSafemodeAlarm(); + } + protected void applyTransform( int token, @NonNull IpSecTunnelInterface tunnelIface, @@ -1325,7 +1409,7 @@ public class VcnGatewayConnection extends StateMachine { @NonNull IpSecTransform transform, int direction) { try { - // TODO: Set underlying network of tunnel interface + // TODO(b/180163196): Set underlying network of tunnel interface // Transforms do not need to be persisted; the IkeSession will keep them alive mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); @@ -1397,9 +1481,6 @@ public class VcnGatewayConnection extends StateMachine { teardownAsynchronously(); } } - - // Successful connection, clear failed attempt counter - mFailedAttempts = 0; } @Override @@ -1436,6 +1517,10 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -1455,6 +1540,7 @@ public class VcnGatewayConnection extends StateMachine { // mUnderlying assumed non-null, given check above. // If network changed, migrate. Otherwise, update any existing networkAgent. if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) { + Slog.v(TAG, "Migrating to new network: " + mUnderlying.network); mIkeSession.setNetwork(mUnderlying.network); } else { // oldUnderlying is non-null & underlying network itself has not changed @@ -1478,8 +1564,19 @@ public class VcnGatewayConnection extends StateMachine { mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig); } else { updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig); + + // mNetworkAgent not null, so the VCN Network has already been established. Clear + // the failed attempt counter and safe mode alarm since this transition is complete. + clearFailedAttemptCounterAndSafeModeAlarm(); } } + + @Override + protected void exitState() { + // Attempt to set the safe mode alarm - this requires the Vcn Network being validated + // while in ConnectedState (which cancels the previous alarm) + setSafemodeAlarm(); + } } /** @@ -1526,6 +1623,10 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -1708,6 +1809,15 @@ public class VcnGatewayConnection extends StateMachine { } @Override + public void onIpSecTransformsMigrated( + @NonNull IpSecTransform inIpSecTransform, + @NonNull IpSecTransform outIpSecTransform) { + Slog.v(TAG, "ChildTransformsMigrated; token " + mToken); + onIpSecTransformCreated(inIpSecTransform, IpSecManager.DIRECTION_IN); + onIpSecTransformCreated(outIpSecTransform, IpSecManager.DIRECTION_OUT); + } + + @Override public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) { // Nothing to be done; no references to the IpSecTransform are held, and this transform // will be closed by the IKE library. diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 5697564ce93f..370d921de2af 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -31,6 +31,7 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.ILocalWallpaperColorConsumer; import android.app.IWallpaperManager; import android.app.IWallpaperManagerCallback; import android.app.PendingIntent; @@ -59,6 +60,7 @@ import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Color; import android.graphics.Rect; +import android.graphics.RectF; import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.Bundle; @@ -85,7 +87,10 @@ import android.service.wallpaper.IWallpaperService; import android.service.wallpaper.WallpaperService; import android.system.ErrnoException; import android.system.Os; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.EventLog; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -105,7 +110,6 @@ import com.android.server.EventLogTags; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import com.android.server.pm.UserManagerInternal; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.wm.WindowManagerInternal; @@ -136,6 +140,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private static final String TAG = "WallpaperManagerService"; private static final boolean DEBUG = false; private static final boolean DEBUG_LIVE = true; + private static final @NonNull RectF LOCAL_COLOR_BOUNDS = + new RectF(0, 0, 1, 1); public static class Lifecycle extends SystemService { private IWallpaperManagerService mService; @@ -866,6 +872,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray(); private int mCurrentUserId = UserHandle.USER_NULL; private boolean mInAmbientMode; + private ArrayMap<IBinder, ArraySet<RectF>> mLocalColorCallbackAreas = + new ArrayMap<>(); + private ArrayMap<RectF, RemoteCallbackList<ILocalWallpaperColorConsumer>> + mLocalColorAreaCallbacks = new ArrayMap<>(); + private ArrayMap<Integer, ArraySet<RectF>> mLocalColorDisplayIdAreas = new ArrayMap<>(); + private ArrayMap<IBinder, Integer> mLocalColorCallbackDisplayId = new ArrayMap<>(); static class WallpaperData { @@ -1276,6 +1288,32 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } @Override + public void onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors, + int displayId) { + forEachDisplayConnector(displayConnector -> { + if (displayConnector.mDisplayId == displayId) { + RemoteCallbackList<ILocalWallpaperColorConsumer> callbacks; + ArrayMap<IBinder, Integer> callbackDisplayIds; + synchronized (mLock) { + callbacks = mLocalColorAreaCallbacks.get(area); + callbackDisplayIds = new ArrayMap<>(mLocalColorCallbackDisplayId); + } + if (callbacks == null) return; + callbacks.broadcast(c -> { + try { + int targetDisplayId = + callbackDisplayIds.get(c.asBinder()); + if (targetDisplayId == displayId) c.onColorsChanged(area, colors); + } catch (RemoteException e) { + e.printStackTrace(); + } + }); + } + }); + } + + + @Override public void onServiceDisconnected(ComponentName name) { synchronized (mLock) { Slog.w(TAG, "Wallpaper service gone: " + name); @@ -1437,6 +1475,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } catch (RemoteException e) { Slog.w(TAG, "Failed to request wallpaper colors", e); } + + ArraySet<RectF> areas = mLocalColorDisplayIdAreas.get(displayId); + if (areas != null && areas.size() != 0) { + try { + connector.mEngine.addLocalColorsAreas(new ArrayList<>(areas)); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to register local colors areas", e); + } + } } } @@ -2340,6 +2387,115 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return true; } + private IWallpaperEngine getEngine(int which, int userId, int displayId) { + WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId); + if (wallpaperData == null) return null; + IWallpaperEngine engine = null; + synchronized (mLock) { + for (int i = 0; i < wallpaperData.connection.mDisplayConnector.size(); i++) { + int id = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId; + int currentWhich = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId; + if (id != displayId && currentWhich != which) continue; + engine = wallpaperData.connection.mDisplayConnector.get(i).mEngine; + break; + } + } + return engine; + } + + @Override + public void addOnLocalColorsChangedListener(@NonNull ILocalWallpaperColorConsumer callback, + @NonNull List<RectF> regions, int which, int userId, int displayId) + throws RemoteException { + if (which != FLAG_LOCK && which != FLAG_SYSTEM) { + throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM"); + } + IWallpaperEngine engine = getEngine(which, userId, displayId); + if (engine == null) return; + ArrayList<RectF> validAreas = new ArrayList<>(regions.size()); + synchronized (mLock) { + ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback); + if (areas == null) areas = new ArraySet<>(regions.size()); + areas.addAll(regions); + mLocalColorCallbackAreas.put(callback.asBinder(), areas); + } + for (int i = 0; i < regions.size(); i++) { + if (!LOCAL_COLOR_BOUNDS.contains(regions.get(i))) { + continue; + } + RemoteCallbackList callbacks; + synchronized (mLock) { + callbacks = mLocalColorAreaCallbacks.get( + regions.get(i)); + if (callbacks == null) { + callbacks = new RemoteCallbackList(); + mLocalColorAreaCallbacks.put(regions.get(i), callbacks); + } + mLocalColorCallbackDisplayId.put(callback.asBinder(), displayId); + ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId); + if (displayAreas == null) { + displayAreas = new ArraySet<>(1); + mLocalColorDisplayIdAreas.put(displayId, displayAreas); + } + displayAreas.add(regions.get(i)); + } + validAreas.add(regions.get(i)); + callbacks.register(callback); + } + engine.addLocalColorsAreas(validAreas); + } + + @Override + public void removeOnLocalColorsChangedListener( + @NonNull ILocalWallpaperColorConsumer callback, int which, int userId, + int displayId) throws RemoteException { + if (which != FLAG_LOCK && which != FLAG_SYSTEM) { + throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM"); + } + final UserHandle callingUser = Binder.getCallingUserHandle(); + if (callingUser.getIdentifier() != userId) { + throw new SecurityException("calling user id does not match"); + } + final long identity = Binder.clearCallingIdentity(); + ArrayList<RectF> removeAreas = new ArrayList<>(); + ArrayList<Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>> + callbacksToRemove = new ArrayList<>(); + try { + synchronized (mLock) { + ArraySet<RectF> areas = mLocalColorCallbackAreas.remove(callback.asBinder()); + mLocalColorCallbackDisplayId.remove(callback.asBinder()); + if (areas == null) areas = new ArraySet<>(); + for (RectF area : areas) { + RemoteCallbackList callbacks = mLocalColorAreaCallbacks.get(area); + if (callbacks == null) continue; + callbacksToRemove.add(new Pair<>(callbacks, callback)); + if (callbacks.getRegisteredCallbackCount() == 0) { + mLocalColorAreaCallbacks.remove(area); + removeAreas.add(area); + } + ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId); + if (displayAreas != null) { + displayAreas.remove(area); + } + } + } + for (int i = 0; i < callbacksToRemove.size(); i++) { + Pair<RemoteCallbackList, ILocalWallpaperColorConsumer> + pair = callbacksToRemove.get(i); + pair.first.unregister(pair.second); + } + } catch (Exception e) { + // ignore any exception + } finally { + Binder.restoreCallingIdentity(identity); + } + + if (removeAreas.size() == 0) return; + IWallpaperEngine engine = getEngine(which, userId, displayId); + if (engine == null) return; + engine.removeLocalColorsAreas(removeAreas); + } + @Override public WallpaperColors getWallpaperColors(int which, int userId, int displayId) throws RemoteException { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8bd4dfd054b6..66d5c77a5c5e 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5855,12 +5855,8 @@ class Task extends WindowContainer<WindowContainer> { mTaskSupervisor.acquireLaunchWakelock(); } - if (didAutoPip) { - // Already entered PIP mode, no need to keep pausing. - return true; - } - - if (mPausingActivity != null) { + // If already entered PIP mode, no need to keep pausing. + if (mPausingActivity != null && !didAutoPip) { // Have the window manager pause its key dispatching until the new // activity has started. If we're pausing the activity just because // the screen is being turned off and the UI is sleeping, don't interrupt @@ -5883,9 +5879,9 @@ class Task extends WindowContainer<WindowContainer> { } } else { - // This activity failed to schedule the - // pause, so just treat it as being paused now. - ProtoLog.v(WM_DEBUG_STATES, "Activity not running, resuming next."); + // This activity either failed to schedule the pause or it entered PIP mode, + // so just treat it as being paused now. + ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next."); if (resuming == null) { mRootWindowContainer.resumeFocusedTasksTopActivities(); } diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java index f9b25d9342d3..f87d5993c1b5 100644 --- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java @@ -30,6 +30,7 @@ import android.annotation.NonNull; import android.util.Pair; import android.util.SparseIntArray; +import androidx.test.filters.LargeTest; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; @@ -496,6 +497,7 @@ public class WorkCountTrackerTest { } @Test + @LargeTest public void testRandom13() { assertThat(EQUAL_PROBABILITY_CDF.length).isEqualTo(NUM_WORK_TYPES); @@ -508,9 +510,9 @@ public class WorkCountTrackerTest { Pair.create(WORK_TYPE_BGUSER, 3)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)); - final double probStop = 0.01; - final double[] numTypesCdf = buildCdf(0, 0.05, 0.05, 0.9); - final double probStart = 0.99; + final double probStop = 0.13; + final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.85); + final double probStart = 0.87; checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, EQUAL_PROBABILITY_CDF, numTypesCdf, probStop); diff --git a/tests/UpdatableSystemFontTest/TEST_MAPPING b/tests/UpdatableSystemFontTest/TEST_MAPPING index a5c447934f43..7fbf426c71c4 100644 --- a/tests/UpdatableSystemFontTest/TEST_MAPPING +++ b/tests/UpdatableSystemFontTest/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "UpdatableSystemFontTest" } diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index 3556c72776dc..8f5ae97bc4c5 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -89,8 +89,8 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class PermissionMonitorTest { - private static final int MOCK_USER1 = 0; - private static final int MOCK_USER2 = 1; + private static final UserHandle MOCK_USER1 = UserHandle.of(0); + private static final UserHandle MOCK_USER2 = UserHandle.of(1); private static final int MOCK_UID1 = 10001; private static final int MOCK_UID2 = 10086; private static final int SYSTEM_UID1 = 1000; @@ -123,10 +123,7 @@ public class PermissionMonitorTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); when(mUserManager.getUserHandles(eq(true))).thenReturn( - Arrays.asList(new UserHandle[] { - new UserHandle(MOCK_USER1), - new UserHandle(MOCK_USER2), - })); + Arrays.asList(new UserHandle[] { MOCK_USER1, MOCK_USER2 })); mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps)); @@ -184,7 +181,8 @@ public class PermissionMonitorTest { return packageInfo; } - private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) { + private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, + UserHandle user) { final PackageInfo pkgInfo; if (hasSystemPermission) { pkgInfo = systemPackageInfoWithPermissions( @@ -192,7 +190,7 @@ public class PermissionMonitorTest { } else { pkgInfo = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, new String[] {}, ""); } - pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); + pkgInfo.applicationInfo.uid = UserHandle.getUid(user, UserHandle.getAppId(uid)); return pkgInfo; } @@ -382,8 +380,8 @@ public class PermissionMonitorTest { }).when(mockNetd).networkClearPermissionForUser(any(int[].class)); } - public void expectPermission(Boolean permission, int[] users, int[] apps) { - for (final int user : users) { + public void expectPermission(Boolean permission, UserHandle[] users, int[] apps) { + for (final UserHandle user : users) { for (final int app : apps) { final int uid = UserHandle.getUid(user, app); if (!mApps.containsKey(uid)) { @@ -396,8 +394,8 @@ public class PermissionMonitorTest { } } - public void expectNoPermission(int[] users, int[] apps) { - for (final int user : users) { + public void expectNoPermission(UserHandle[] users, int[] apps) { + for (final UserHandle user : users) { for (final int app : apps) { final int uid = UserHandle.getUid(user, app); if (mApps.containsKey(uid)) { @@ -425,46 +423,48 @@ public class PermissionMonitorTest { // Add SYSTEM_PACKAGE2, expect only have network permission. mPermissionMonitor.onUserAdded(MOCK_USER1); - addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID}); + addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID}); // Add SYSTEM_PACKAGE1, expect permission escalate. - addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID); - mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID}); + addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID}); mPermissionMonitor.onUserAdded(MOCK_USER2); - mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2}, + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID}); - addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); - mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2}, + addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID}); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2}, + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1}); // Remove MOCK_UID1, expect no permission left for all user. mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1); - removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); - mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1}); + removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, + new int[]{MOCK_UID1}); // Remove SYSTEM_PACKAGE1, expect permission downgrade. when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2}); - removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_PACKAGE1, SYSTEM_UID); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2}, + removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, + SYSTEM_PACKAGE1, SYSTEM_UID); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID}); mPermissionMonitor.onUserRemoved(MOCK_USER1); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID}); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER2}, new int[]{SYSTEM_UID}); // Remove all packages, expect no permission left. when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{}); - removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID); - mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, + removePackageForUsers(new UserHandle[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID, MOCK_UID1}); // Remove last user, expect no redundant clearPermission is invoked. mPermissionMonitor.onUserRemoved(MOCK_USER2); - mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID, MOCK_UID1}); } @@ -548,14 +548,14 @@ public class PermissionMonitorTest { // Normal package add/remove operations will trigger multiple intent for uids corresponding to // each user. To simulate generic package operations, the onPackageAdded/Removed will need to be // called multiple times with the uid corresponding to each user. - private void addPackageForUsers(int[] users, String packageName, int uid) { - for (final int user : users) { + private void addPackageForUsers(UserHandle[] users, String packageName, int uid) { + for (final UserHandle user : users) { mPermissionMonitor.onPackageAdded(packageName, UserHandle.getUid(user, uid)); } } - private void removePackageForUsers(int[] users, String packageName, int uid) { - for (final int user : users) { + private void removePackageForUsers(UserHandle[] users, String packageName, int uid) { + for (final UserHandle user : users) { mPermissionMonitor.onPackageRemoved(packageName, UserHandle.getUid(user, uid)); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index faa8b234cf51..e7154802f1f2 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -34,8 +34,10 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.net.LinkProperties; +import android.net.NetworkAgent; import android.net.NetworkCapabilities; import androidx.test.filters.SmallTest; @@ -73,6 +75,11 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test + public void testEnterStateDoesNotCancelSafemodeAlarm() { + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + } + + @Test public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() @@ -121,7 +128,24 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test + public void testMigratedTransformsAreApplied() throws Exception { + getChildSessionCallback() + .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform()); + mTestLooper.dispatchAll(); + + for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) { + verify(mIpSecSvc) + .applyTunnelModeTransform( + eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); + } + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + } + + @Test public void testChildOpenedRegistersNetwork() throws Exception { + // Verify scheduled but not canceled when entering ConnectedState + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + final VcnChildSessionConfiguration mMockChildSessionConfig = mock(VcnChildSessionConfiguration.class); doReturn(Collections.singletonList(TEST_INTERNAL_ADDR)) @@ -163,24 +187,41 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection for (int cap : mConfig.getAllExposedCapabilities()) { assertTrue(nc.hasCapability(cap)); } + + // Now that Vcn Network is up, notify it as validated and verify the Safemode alarm is + // canceled + mGatewayConnection.mNetworkAgent.onValidationStatus( + NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */); + verify(mSafemodeTimeoutAlarm).cancel(); } @Test public void testChildSessionClosedTriggersDisconnect() throws Exception { + // Verify scheduled but not canceled when entering ConnectedState + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + getChildSessionCallback().onClosed(); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); + + // Since network never validated, verify mSafemodeTimeoutAlarm not canceled + verifyNoMoreInteractions(mSafemodeTimeoutAlarm); } @Test public void testIkeSessionClosedTriggersDisconnect() throws Exception { + // Verify scheduled but not canceled when entering ConnectedState + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + getIkeSessionCallback().onClosed(); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); - verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */); + + // Since network never validated, verify mSafemodeTimeoutAlarm not canceled + verifyNoMoreInteractions(mSafemodeTimeoutAlarm); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java index 760379d6649d..07282c920088 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java @@ -106,4 +106,9 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio verify(mIkeSession).close(); verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */); } + + @Test + public void testSafemodeTimeoutNotifiesCallback() { + verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 1a1787ac19d9..49ce54d4f684 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -48,7 +48,8 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect .createIpSecTunnelInterface( DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network); mGatewayConnection.setTunnelInterface(tunnelIface); - mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState); + + // Don't need to transition to DisconnectedState because it is the starting state mTestLooper.dispatchAll(); } @@ -78,6 +79,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -98,5 +100,6 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect assertNull(mGatewayConnection.getCurrentState()); verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any()); + verifySafemodeTimeoutAlarmAndGetCallback(true /* expectCanceled */); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java index b09f3a008c29..22eab2a1aa65 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java @@ -80,4 +80,9 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); } + + @Test + public void testSafemodeTimeoutNotifiesCallback() { + verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java index d37e92f661cc..6c2607586629 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java @@ -91,4 +91,9 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */); } + + @Test + public void testSafemodeTimeoutNotifiesCallback() { + verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 0b44e03a5e5a..ac9ec0663df2 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -20,6 +20,7 @@ import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkR import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; +import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; @@ -50,6 +51,7 @@ import android.os.ParcelUuid; import android.os.PowerManager; import android.os.test.TestLooper; +import com.android.internal.util.State; import com.android.internal.util.WakeupMessage; import com.android.server.IpSecService; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; @@ -108,6 +110,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final WakeupMessage mTeardownTimeoutAlarm; @NonNull protected final WakeupMessage mDisconnectRequestAlarm; @NonNull protected final WakeupMessage mRetryTimeoutAlarm; + @NonNull protected final WakeupMessage mSafemodeTimeoutAlarm; @NonNull protected final IpSecService mIpSecSvc; @NonNull protected final ConnectivityManager mConnMgr; @@ -128,6 +131,7 @@ public class VcnGatewayConnectionTestBase { mTeardownTimeoutAlarm = mock(WakeupMessage.class); mDisconnectRequestAlarm = mock(WakeupMessage.class); mRetryTimeoutAlarm = mock(WakeupMessage.class); + mSafemodeTimeoutAlarm = mock(WakeupMessage.class); mIpSecSvc = mock(IpSecService.class); setupIpSecManager(mContext, mIpSecSvc); @@ -150,6 +154,7 @@ public class VcnGatewayConnectionTestBase { setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM); setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM); setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM); + setUpWakeupMessage(mSafemodeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM); doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime(); } @@ -253,4 +258,24 @@ public class VcnGatewayConnectionTestBase { delayInMillis, expectCanceled); } + + protected Runnable verifySafemodeTimeoutAlarmAndGetCallback(boolean expectCanceled) { + return verifyWakeupMessageSetUpAndGetCallback( + VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM, + mSafemodeTimeoutAlarm, + TimeUnit.SECONDS.toMillis(VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS), + expectCanceled); + } + + protected void verifySafemodeTimeoutNotifiesCallback(@NonNull State expectedState) { + // Safemode timer starts when VcnGatewayConnection exits DisconnectedState (the initial + // state) + final Runnable delayedEvent = + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + delayedEvent.run(); + mTestLooper.dispatchAll(); + + verify(mGatewayStatusCallback).onEnteredSafemode(); + assertEquals(expectedState, mGatewayConnection.getCurrentState()); + } } |