summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt12
-rw-r--r--core/api/system-current.txt8
-rw-r--r--core/api/test-current.txt3
-rw-r--r--core/java/android/app/ILocalWallpaperColorConsumer.aidl27
-rw-r--r--core/java/android/app/IWallpaperManager.aidl14
-rw-r--r--core/java/android/app/WallpaperManager.java76
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java105
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java6
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java2
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java28
-rw-r--r--core/java/android/graphics/fonts/FontManager.java70
-rw-r--r--core/java/android/net/UidRange.java9
-rw-r--r--core/java/android/os/PowerManager.java8
-rw-r--r--core/java/android/service/wallpaper/EngineWindowPage.java96
-rw-r--r--core/java/android/service/wallpaper/IWallpaperConnection.aidl2
-rw-r--r--core/java/android/service/wallpaper/IWallpaperEngine.aidl5
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java492
-rw-r--r--core/java/android/view/View.java4
-rw-r--r--core/java/android/widget/HorizontalScrollView.java45
-rw-r--r--core/java/android/widget/RemoteViews.java341
-rw-r--r--core/java/android/widget/ScrollView.java44
-rw-r--r--core/res/AndroidManifest.xml9
-rw-r--r--core/res/res/drawable/btn_borderless_material.xml3
-rw-r--r--core/res/res/drawable/btn_colored_material.xml3
-rw-r--r--core/res/res/drawable/btn_default_material.xml3
-rw-r--r--core/res/res/drawable/btn_toggle_material.xml3
-rw-r--r--core/res/res/values/attrs.xml7
-rw-r--r--core/res/res/values/colors.xml12
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/public.xml3
-rw-r--r--core/res/res/values/themes_device_defaults.xml3
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--data/etc/services.core.protolog.json18
-rw-r--r--graphics/java/android/graphics/drawable/RippleAnimationSession.java284
-rw-r--r--graphics/java/android/graphics/drawable/RippleDrawable.java323
-rw-r--r--graphics/java/android/graphics/drawable/RippleShader.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java28
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java62
-rw-r--r--libs/hwui/Animator.cpp8
-rw-r--r--media/java/android/media/AudioManager.java19
-rw-r--r--media/java/android/media/AudioPlaybackConfiguration.java15
-rw-r--r--media/java/android/media/AudioTrack.java4
-rw-r--r--media/java/android/media/MediaPlayer.java16
-rw-r--r--media/java/android/media/session/MediaSessionManager.java5
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/AndroidManifest.xml8
-rw-r--r--packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml20
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/PeopleProviderUtils.java76
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadButton.java48
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadKey.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java92
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java157
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt111
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java156
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt34
-rw-r--r--services/core/Android.bp2
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java318
-rw-r--r--services/core/java/com/android/server/SensorPrivacyService.java2
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java15
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java13
-rw-r--r--services/core/java/com/android/server/connectivity/PermissionMonitor.java33
-rw-r--r--services/core/java/com/android/server/graphics/fonts/TEST_MAPPING7
-rw-r--r--services/core/java/com/android/server/pm/Settings.java8
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java9
-rw-r--r--services/core/java/com/android/server/vcn/VcnGatewayConnection.java120
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java158
-rw-r--r--services/core/java/com/android/server/wm/Task.java14
-rw-r--r--services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java8
-rw-r--r--tests/UpdatableSystemFontTest/TEST_MAPPING2
-rw-r--r--tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java64
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java43
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java5
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java5
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java5
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java5
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java25
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>&lt;!-- A red ripple masked against an opaque rectangle. --/>
+ * &lt;ripple android:rippleStyle="patterned">
+ * &lt;/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());
+ }
}