Merge "Expand TestableDeviceConfigTest#getProperties()." into rvc-dev
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2399e37..f613df2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -385,6 +385,15 @@
*/
public static final int WATCH_FOREGROUND_CHANGES = 1 << 0;
+
+ /**
+ * Flag to determine whether we should log noteOp/startOp calls to make sure they
+ * are correctly used
+ *
+ * @hide
+ */
+ public static final boolean NOTE_OP_COLLECTION_ENABLED = false;
+
/**
* @hide
*/
@@ -7103,6 +7112,7 @@
public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
@Nullable String featureId, @Nullable String message) {
try {
+ collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
@@ -7263,6 +7273,7 @@
int myUid = Process.myUid();
try {
+ collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(proxiedUid, proxiedPackageName, op);
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
@@ -7583,6 +7594,7 @@
public int startOpNoThrow(int op, int uid, @NonNull String packageName,
boolean startIfModeDefault, @Nullable String featureId, @Nullable String message) {
try {
+ collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
@@ -8492,4 +8504,24 @@
public static int leftCircularDistance(int from, int to, int size) {
return (to + size - from) % size;
}
+
+ /**
+ * Helper method for noteOp, startOp and noteProxyOp to call AppOpsService to collect/log
+ * stack traces
+ *
+ * <p> For each call, the stacktrace op code, package name and long version code will be
+ * passed along where it will be logged/collected
+ *
+ * @param op The operation to note
+ */
+ private void collectNoteOpCallsForValidation(int op) {
+ if (NOTE_OP_COLLECTION_ENABLED) {
+ try {
+ mService.collectNoteOpCallsForValidation(getFormattedStackTrace(),
+ op, mContext.getOpPackageName(), mContext.getApplicationInfo().longVersionCode);
+ } catch (RemoteException e) {
+ // Swallow error, only meant for logging ops, should not affect flow of the code
+ }
+ }
+ }
}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 1cefbd9..c44a0bd 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -38,6 +38,8 @@
void releaseWakeLock(IBinder lock, int flags);
void updateWakeLockUids(IBinder lock, in int[] uids);
oneway void powerHint(int hintId, int data);
+ oneway void setPowerBoost(int boost, int durationMs);
+ oneway void setPowerMode(int mode, boolean enabled);
void updateWakeLockWorkSource(IBinder lock, in WorkSource ws, String historyTag);
boolean isWakeLockLevelSupported(int level);
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 9661a08..51f01ca 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -201,6 +201,119 @@
*/
public abstract void powerHint(int hintId, int data);
+ /**
+ * Boost: It is sent when user interacting with the device, for example,
+ * touchscreen events are incoming.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Boost.aidl
+ */
+ public static final int BOOST_INTERACTION = 0;
+
+ /**
+ * Boost: It indicates that the framework is likely to provide a new display
+ * frame soon. This implies that the device should ensure that the display
+ * processing path is powered up and ready to receive that update.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Boost.aidl
+ */
+ public static final int BOOST_DISPLAY_UPDATE_IMMINENT = 1;
+
+ /**
+ * SetPowerBoost() indicates the device may need to boost some resources, as
+ * the load is likely to increase before the kernel governors can react.
+ * Depending on the boost, it may be appropriate to raise the frequencies of
+ * CPU, GPU, memory subsystem, or stop CPU from going into deep sleep state.
+ *
+ * @param boost Boost which is to be set with a timeout.
+ * @param durationMs The expected duration of the user's interaction, if
+ * known, or 0 if the expected duration is unknown.
+ * a negative value indicates canceling previous boost.
+ * A given platform can choose to boost some time based on durationMs,
+ * and may also pick an appropriate timeout for 0 case.
+ */
+ public abstract void setPowerBoost(int boost, int durationMs);
+
+ /**
+ * Mode: It indicates that the device is to allow wake up when the screen
+ * is tapped twice.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_DOUBLE_TAP_TO_WAKE = 0;
+
+ /**
+ * Mode: It indicates Low power mode is activated or not. Low power mode
+ * is intended to save battery at the cost of performance.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_LOW_POWER = 1;
+
+ /**
+ * Mode: It indicates Sustained Performance mode is activated or not.
+ * Sustained performance mode is intended to provide a consistent level of
+ * performance for a prolonged amount of time.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_SUSTAINED_PERFORMANCE = 2;
+
+ /**
+ * Mode: It sets the device to a fixed performance level which can be sustained
+ * under normal indoor conditions for at least 10 minutes.
+ * Fixed performance mode puts both upper and lower bounds on performance such
+ * that any workload run while in a fixed performance mode should complete in
+ * a repeatable amount of time.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_FIXED_PERFORMANCE = 3;
+
+ /**
+ * Mode: It indicates VR Mode is activated or not. VR mode is intended to
+ * provide minimum guarantee for performance for the amount of time the device
+ * can sustain it.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_VR = 4;
+
+ /**
+ * Mode: It indicates that an application has been launched.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_LAUNCH = 5;
+
+ /**
+ * Mode: It indicates that the device is about to enter a period of expensive
+ * rendering.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_EXPENSIVE_RENDERING = 6;
+
+ /**
+ * Mode: It indicates that the device is about entering/leaving interactive
+ * state or on-interactive state.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_INTERACTIVE = 7;
+
+ /**
+ * Mode: It indicates the device is in device idle, externally known as doze.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_DEVICE_IDLE = 8;
+
+ /**
+ * Mode: It indicates that display is either off or still on but is optimized
+ * for low power.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_DISPLAY_INACTIVE = 9;
+
+ /**
+ * SetPowerMode() is called to enable/disable specific hint mode, which
+ * may result in adjustment of power/performance parameters of the
+ * cpufreq governor and other controls on device side.
+ *
+ * @param mode Mode which is to be enable/disable.
+ * @param enabled true to enable, false to disable the mode.
+ */
+ public abstract void setPowerMode(int mode, boolean enabled);
+
/** Returns whether there hasn't been a user activity event for the given number of ms. */
public abstract boolean wasDeviceIdleFor(long ms);
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 1c1c254..907ea55 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -103,4 +103,6 @@
int checkOperationRaw(int code, int uid, String packageName);
void reloadNonHistoricalState();
+
+ void collectNoteOpCallsForValidation(String stackTrace, int op, String packageName, long version);
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index afa58d5..49edcf7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -195,6 +195,7 @@
<permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
<permission name="android.permission.USE_RESERVED_DISK"/>
</privapp-permissions>
diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
index 55207b3..6ecab51 100644
--- a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
+++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
@@ -18,8 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fullscreen_user_switcher"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fitsSystemWindows="true">
+ android:layout_height="match_parent">
<LinearLayout
android:id="@+id/container"
@@ -28,11 +27,10 @@
android:layout_alignParentTop="true"
android:orientation="vertical">
- <!-- TODO(b/150302361): Status bar is commented out since a top inset is being added which causes it to be displayed below the top of the screen. -->
- <!-- <include
- layout="@layout/car_status_bar_header"
- android:layout_alignParentTop="true"
- android:theme="@android:style/Theme"/>-->
+ <include
+ layout="@layout/car_status_bar_header"
+ android:layout_alignParentTop="true"
+ android:theme="@android:style/Theme"/>
<FrameLayout
@@ -42,9 +40,8 @@
android:id="@+id/user_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"/>
- <!-- TODO(b/150302361): Re-add marginTop once status bar has been added back. -->
- <!-- android:layout_marginTop="@dimen/car_user_switcher_margin_top"/>-->
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="@dimen/car_user_switcher_margin_top"/>
</FrameLayout>
</LinearLayout>
diff --git a/packages/CarSystemUI/res/layout/super_notification_shade.xml b/packages/CarSystemUI/res/layout/super_notification_shade.xml
index cb65045..e36d8ca 100644
--- a/packages/CarSystemUI/res/layout/super_notification_shade.xml
+++ b/packages/CarSystemUI/res/layout/super_notification_shade.xml
@@ -72,11 +72,6 @@
android:layout_marginBottom="@dimen/navigation_bar_height"
android:visibility="invisible"/>
- <include layout="@layout/headsup_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible"/>
-
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index c010881..59fa9d0 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -18,6 +18,7 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.bubbles.dagger.BubbleModule;
+import com.android.systemui.car.notification.CarNotificationModule;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
@@ -49,7 +50,8 @@
/** Binder for car specific {@link SystemUI} modules. */
@Module(includes = {RecentsModule.class, CarStatusBarModule.class, NotificationsModule.class,
- BubbleModule.class, KeyguardModule.class, OverlayWindowModule.class})
+ BubbleModule.class, KeyguardModule.class, OverlayWindowModule.class,
+ CarNotificationModule.class})
public abstract class CarSystemUIBinder {
/** Inject into AuthController. */
@Binds
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index bae42b5..22c3acf 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -22,6 +22,7 @@
import android.content.Context;
import com.android.keyguard.KeyguardViewController;
+import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarDeviceProvisionedControllerImpl;
import com.android.systemui.dagger.SystemUIRootComponent;
import com.android.systemui.dock.DockManager;
@@ -137,4 +138,8 @@
@Binds
abstract DeviceProvisionedController bindDeviceProvisionedController(
CarDeviceProvisionedControllerImpl deviceProvisionedController);
+
+ @Binds
+ abstract CarDeviceProvisionedController bindCarDeviceProvisionedController(
+ CarDeviceProvisionedControllerImpl deviceProvisionedController);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
index 38d5211b..09e62d2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
@@ -44,8 +44,9 @@
CarSettings.Secure.KEY_SETUP_WIZARD_IN_PROGRESS);
private final ContentObserver mCarSettingsObserver = new ContentObserver(
Dependency.get(Dependency.MAIN_HANDLER)) {
+
@Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
+ public void onChange(boolean selfChange, Uri uri, int flags) {
if (USER_SETUP_IN_PROGRESS_URI.equals(uri)) {
notifyUserSetupInProgressChanged();
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
new file mode 100644
index 0000000..689d2d5
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
@@ -0,0 +1,118 @@
+/*
+ * 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 com.android.systemui.car.notification;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.car.notification.R;
+import com.android.car.notification.headsup.CarHeadsUpNotificationContainer;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.car.CarStatusBar;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/**
+ * A controller for SysUI's HUN display.
+ */
+@Singleton
+public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotificationContainer {
+ private final CarDeviceProvisionedController mCarDeviceProvisionedController;
+ private final Lazy<CarStatusBar> mCarStatusBarLazy;
+
+ private final ViewGroup mWindow;
+ private final FrameLayout mHeadsUpContentFrame;
+
+ private final boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
+
+ @Inject
+ CarHeadsUpNotificationSystemContainer(Context context,
+ @Main Resources resources,
+ CarDeviceProvisionedController deviceProvisionedController,
+ WindowManager windowManager,
+ // TODO: Remove dependency on CarStatusBar
+ Lazy<CarStatusBar> carStatusBarLazy) {
+ mCarDeviceProvisionedController = deviceProvisionedController;
+ mCarStatusBarLazy = carStatusBarLazy;
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+ PixelFormat.TRANSLUCENT);
+
+ lp.gravity = Gravity.TOP;
+ lp.setTitle("HeadsUpNotification");
+
+ mWindow = (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.headsup_container, null, false);
+ windowManager.addView(mWindow, lp);
+ mWindow.setVisibility(View.INVISIBLE);
+ mHeadsUpContentFrame = mWindow.findViewById(R.id.headsup_content);
+
+ mEnableHeadsUpNotificationWhenNotificationShadeOpen = resources.getBoolean(
+ R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
+ }
+
+ private void animateShow() {
+ if ((mEnableHeadsUpNotificationWhenNotificationShadeOpen
+ || !mCarStatusBarLazy.get().isPanelExpanded()) && isCurrentUserSetup()) {
+ mWindow.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void animateHide() {
+ mWindow.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void displayNotification(View notificationView) {
+ mHeadsUpContentFrame.addView(notificationView);
+ animateShow();
+ }
+
+ @Override
+ public void removeNotification(View notificationView) {
+ mHeadsUpContentFrame.removeView(notificationView);
+ if (mHeadsUpContentFrame.getChildCount() == 0) {
+ animateHide();
+ }
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mWindow.getVisibility() == View.VISIBLE;
+ }
+
+ private boolean isCurrentUserSetup() {
+ return mCarDeviceProvisionedController.isCurrentUserSetup()
+ && !mCarDeviceProvisionedController.isCurrentUserSetupInProgress();
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java
new file mode 100644
index 0000000..b7bc631
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java
@@ -0,0 +1,84 @@
+/*
+ * 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 com.android.systemui.car.notification;
+
+import android.content.Context;
+
+import com.android.car.notification.CarHeadsUpNotificationManager;
+import com.android.car.notification.CarNotificationListener;
+import com.android.car.notification.CarUxRestrictionManagerWrapper;
+import com.android.car.notification.NotificationClickHandlerFactory;
+import com.android.car.notification.NotificationDataManager;
+import com.android.car.notification.headsup.CarHeadsUpNotificationContainer;
+import com.android.internal.statusbar.IStatusBarService;
+
+import javax.inject.Singleton;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Module for Car SysUI Notifications
+ */
+@Module
+public abstract class CarNotificationModule {
+ @Provides
+ @Singleton
+ static NotificationClickHandlerFactory provideNotificationClickHandlerFactory(
+ IStatusBarService barService) {
+ return new NotificationClickHandlerFactory(barService);
+ }
+
+ @Provides
+ @Singleton
+ static NotificationDataManager provideNotificationDataManager() {
+ return new NotificationDataManager();
+ }
+
+ @Provides
+ @Singleton
+ static CarUxRestrictionManagerWrapper provideCarUxRestrictionManagerWrapper() {
+ return new CarUxRestrictionManagerWrapper();
+ }
+
+ @Provides
+ @Singleton
+ static CarNotificationListener provideCarNotificationListener(Context context,
+ CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
+ CarHeadsUpNotificationManager carHeadsUpNotificationManager,
+ NotificationDataManager notificationDataManager) {
+ CarNotificationListener listener = new CarNotificationListener();
+ listener.registerAsSystemService(context, carUxRestrictionManagerWrapper,
+ carHeadsUpNotificationManager, notificationDataManager);
+ return listener;
+ }
+
+ @Provides
+ @Singleton
+ static CarHeadsUpNotificationManager provideCarHeadsUpNotificationManager(Context context,
+ NotificationClickHandlerFactory notificationClickHandlerFactory,
+ NotificationDataManager notificationDataManager,
+ CarHeadsUpNotificationContainer headsUpNotificationDisplay) {
+ return new CarHeadsUpNotificationManager(context, notificationClickHandlerFactory,
+ notificationDataManager, headsUpNotificationDisplay);
+ }
+
+ @Binds
+ abstract CarHeadsUpNotificationContainer bindsCarHeadsUpNotificationContainer(
+ CarHeadsUpNotificationSystemContainer carHeadsUpNotificationSystemContainer);
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 2045527..b63162b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -49,7 +49,6 @@
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BarTransitions;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.FileDescriptor;
@@ -105,7 +104,7 @@
public CarNavigationBar(Context context,
CarNavigationBarController carNavigationBarController,
WindowManager windowManager,
- DeviceProvisionedController deviceProvisionedController,
+ CarDeviceProvisionedController deviceProvisionedController,
CommandQueue commandQueue,
AutoHideController autoHideController,
ButtonSelectionStateListener buttonSelectionStateListener,
@@ -117,8 +116,7 @@
super(context);
mCarNavigationBarController = carNavigationBarController;
mWindowManager = windowManager;
- mCarDeviceProvisionedController = (CarDeviceProvisionedController)
- deviceProvisionedController;
+ mCarDeviceProvisionedController = deviceProvisionedController;
mCommandQueue = commandQueue;
mAutoHideController = autoHideController;
mButtonSelectionStateListener = buttonSelectionStateListener;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 7ad3d45..de768cb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -43,11 +43,9 @@
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.car.notification.CarHeadsUpNotificationManager;
import com.android.car.notification.CarNotificationListener;
import com.android.car.notification.CarNotificationView;
import com.android.car.notification.CarUxRestrictionManagerWrapper;
-import com.android.car.notification.HeadsUpEntry;
import com.android.car.notification.NotificationClickHandlerFactory;
import com.android.car.notification.NotificationDataManager;
import com.android.car.notification.NotificationViewController;
@@ -133,7 +131,6 @@
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -185,14 +182,15 @@
private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy;
private final ShadeController mShadeController;
private final CarServiceProvider mCarServiceProvider;
+ private final NotificationDataManager mNotificationDataManager;
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final ScreenLifecycle mScreenLifecycle;
+ private final CarNotificationListener mCarNotificationListener;
private boolean mDeviceIsSetUpForUser = true;
private boolean mIsUserSetupInProgress = false;
private PowerManagerHelper mPowerManagerHelper;
private FlingAnimationUtils mFlingAnimationUtils;
- private NotificationDataManager mNotificationDataManager;
private NotificationClickHandlerFactory mNotificationClickHandlerFactory;
// The container for the notifications.
@@ -230,24 +228,8 @@
private boolean mIsNotificationCardSwiping;
// If notification shade is being swiped vertically to close.
private boolean mIsSwipingVerticallyToClose;
- // Whether heads-up notifications should be shown when shade is open.
- private boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
- private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
-
- private final CarPowerStateListener mCarPowerStateListener =
- (int state) -> {
- // When the car powers on, clear all notifications and mute/unread states.
- Log.d(TAG, "New car power state: " + state);
- if (state == CarPowerStateListener.ON) {
- if (mNotificationClickHandlerFactory != null) {
- mNotificationClickHandlerFactory.clearAllNotifications();
- }
- if (mNotificationDataManager != null) {
- mNotificationDataManager.clearAll();
- }
- }
- };
+ private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
public CarStatusBar(
Context context,
@@ -288,7 +270,7 @@
BubbleController bubbleController,
NotificationGroupManager groupManager,
VisualStabilityManager visualStabilityManager,
- DeviceProvisionedController deviceProvisionedController,
+ CarDeviceProvisionedController carDeviceProvisionedController,
NavigationBarController navigationBarController,
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
@@ -330,7 +312,10 @@
CarServiceProvider carServiceProvider,
Lazy<PowerManagerHelper> powerManagerHelperLazy,
CarNavigationBarController carNavigationBarController,
- FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
+ FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+ NotificationDataManager notificationDataManager,
+ CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
+ CarNotificationListener carNotificationListener) {
super(
context,
notificationsController,
@@ -370,7 +355,7 @@
bubbleController,
groupManager,
visualStabilityManager,
- deviceProvisionedController,
+ carDeviceProvisionedController,
navigationBarController,
assistManagerLazy,
configurationController,
@@ -411,14 +396,16 @@
mUserSwitcherController = userSwitcherController;
mScrimController = scrimController;
mLockscreenLockIconController = lockscreenLockIconController;
- mCarDeviceProvisionedController =
- (CarDeviceProvisionedController) deviceProvisionedController;
+ mCarDeviceProvisionedController = carDeviceProvisionedController;
mShadeController = shadeController;
mCarServiceProvider = carServiceProvider;
mPowerManagerHelperLazy = powerManagerHelperLazy;
mCarNavigationBarController = carNavigationBarController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
mScreenLifecycle = screenLifecycle;
+ mNotificationDataManager = notificationDataManager;
+ mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
+ mCarNotificationListener = carNotificationListener;
}
@Override
@@ -461,7 +448,17 @@
mCarBatteryController.startListening();
mPowerManagerHelper = mPowerManagerHelperLazy.get();
- mPowerManagerHelper.setCarPowerStateListener(mCarPowerStateListener);
+ mPowerManagerHelper.setCarPowerStateListener(
+ state -> {
+ // When the car powers on, clear all notifications and mute/unread states.
+ Log.d(TAG, "New car power state: " + state);
+ if (state == CarPowerStateListener.ON) {
+ if (mNotificationClickHandlerFactory != null) {
+ mNotificationClickHandlerFactory.clearAllNotifications();
+ }
+ mNotificationDataManager.clearAll();
+ }
+ });
mPowerManagerHelper.connectToCarService();
mCarDeviceProvisionedController.addCallback(
@@ -612,27 +609,13 @@
mShadeController.animateCollapsePanels();
}
});
- CarNotificationListener carNotificationListener = new CarNotificationListener();
- mCarUxRestrictionManagerWrapper = new CarUxRestrictionManagerWrapper();
-
- mNotificationDataManager = new NotificationDataManager();
mNotificationDataManager.setOnUnseenCountUpdateListener(() -> {
- if (mNotificationDataManager != null) {
- onUseenCountUpdate(mNotificationDataManager.getUnseenNotificationCount());
- }
+ onUseenCountUpdate(mNotificationDataManager.getUnseenNotificationCount());
});
- mEnableHeadsUpNotificationWhenNotificationShadeOpen = mContext.getResources().getBoolean(
- R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
- CarHeadsUpNotificationManager carHeadsUpNotificationManager =
- new CarSystemUIHeadsUpNotificationManager(mContext,
- mNotificationClickHandlerFactory, mNotificationDataManager);
mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
- carNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper,
- carHeadsUpNotificationManager, mNotificationDataManager);
-
final View glassPane = mNotificationShadeWindowView.findViewById(R.id.glass_pane);
mNotificationView = mNotificationShadeWindowView.findViewById(R.id.notification_view);
mHandleBar = mNotificationShadeWindowView.findViewById(R.id.handle_bar);
@@ -735,7 +718,7 @@
mNotificationViewController = new NotificationViewController(
mNotificationView,
PreprocessingManager.getInstance(mContext),
- carNotificationListener,
+ mCarNotificationListener,
mCarUxRestrictionManagerWrapper,
mNotificationDataManager);
mNotificationViewController.enable();
@@ -1221,61 +1204,4 @@
return true;
}
}
-
- /**
- * SystemUi version of the notification manager that overrides methods such that the
- * notifications end up in the status bar layouts instead of a standalone window.
- */
- private class CarSystemUIHeadsUpNotificationManager extends CarHeadsUpNotificationManager {
-
- CarSystemUIHeadsUpNotificationManager(Context context,
- NotificationClickHandlerFactory clickHandlerFactory,
- NotificationDataManager notificationDataManager) {
- super(context, clickHandlerFactory, notificationDataManager);
- }
-
- @Override
- protected View createHeadsUpPanel() {
- // In SystemUi the view is already in the window so just return a reference.
- return mNotificationShadeWindowView.findViewById(R.id.notification_headsup);
- }
-
- @Override
- protected void addHeadsUpPanelToDisplay() {
- // Set the panel initial state to invisible
- mHeadsUpPanel.setVisibility(View.INVISIBLE);
- }
-
- @Override
- protected void setInternalInsetsInfo(ViewTreeObserver.InternalInsetsInfo info,
- HeadsUpEntry currentNotification, boolean panelExpanded) {
- super.setInternalInsetsInfo(info, currentNotification, mPanelExpanded);
- }
-
- @Override
- protected void setHeadsUpVisible() {
- // if the Notifications panel is showing or SUW for user is in progress then don't show
- // heads up notifications
- if ((!mEnableHeadsUpNotificationWhenNotificationShadeOpen && mPanelExpanded)
- || !isDeviceSetupForUser()) {
- return;
- }
-
- super.setHeadsUpVisible();
- if (mHeadsUpPanel.getVisibility() == View.VISIBLE) {
- mNotificationShadeWindowController.setHeadsUpShowing(true);
- mStatusBarWindowController.setForceStatusBarVisible(true);
- }
- }
-
- @Override
- protected void removeNotificationFromPanel(HeadsUpEntry currentHeadsUpNotification) {
- super.removeNotificationFromPanel(currentHeadsUpNotification);
- // If the panel ended up empty and hidden we can remove it from SystemUi
- if (mHeadsUpPanel.getVisibility() != View.VISIBLE) {
- mNotificationShadeWindowController.setHeadsUpShowing(false);
- mStatusBarWindowController.setForceStatusBarVisible(false);
- }
- }
- }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 9798ee7..9a53584 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -23,6 +23,9 @@
import android.os.PowerManager;
import android.util.DisplayMetrics;
+import com.android.car.notification.CarNotificationListener;
+import com.android.car.notification.CarUxRestrictionManagerWrapper;
+import com.android.car.notification.NotificationDataManager;
import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
@@ -30,6 +33,7 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -92,7 +96,6 @@
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneDependenciesModule;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -162,7 +165,7 @@
BubbleController bubbleController,
NotificationGroupManager groupManager,
VisualStabilityManager visualStabilityManager,
- DeviceProvisionedController deviceProvisionedController,
+ CarDeviceProvisionedController carDeviceProvisionedController,
NavigationBarController navigationBarController,
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
@@ -203,7 +206,10 @@
CarServiceProvider carServiceProvider,
Lazy<PowerManagerHelper> powerManagerHelperLazy,
CarNavigationBarController carNavigationBarController,
- FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
+ FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+ NotificationDataManager notificationDataManager,
+ CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
+ CarNotificationListener carNotificationListener) {
return new CarStatusBar(
context,
notificationsController,
@@ -243,7 +249,7 @@
bubbleController,
groupManager,
visualStabilityManager,
- deviceProvisionedController,
+ carDeviceProvisionedController,
navigationBarController,
assistManagerLazy,
configurationController,
@@ -283,6 +289,9 @@
carServiceProvider,
powerManagerHelperLazy,
carNavigationBarController,
- flingAnimationUtilsBuilder);
+ flingAnimationUtilsBuilder,
+ notificationDataManager,
+ carUxRestrictionManagerWrapper,
+ carNotificationListener);
}
}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
new file mode 100644
index 0000000..05b8e6a
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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 com.android.systemui.car.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.statusbar.car.CarStatusBar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase {
+ private CarHeadsUpNotificationSystemContainer mDefaultController;
+ private CarHeadsUpNotificationSystemContainer mOverrideEnabledController;
+ @Mock
+ private CarDeviceProvisionedController mCarDeviceProvisionedController;
+ @Mock
+ private CarStatusBar mCarStatusBar;
+ @Mock
+ private WindowManager mWindowManager;
+
+ @Mock
+ private View mNotificationView;
+ @Mock
+ private View mNotificationView2;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mCarStatusBar.isPanelExpanded()).thenReturn(false);
+ when(mCarDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
+ when(mCarDeviceProvisionedController.isCurrentUserSetupInProgress()).thenReturn(false);
+
+ TestableResources testableResources = mContext.getOrCreateTestableResources();
+
+ testableResources.addOverride(
+ R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen, false);
+
+ mDefaultController = new CarHeadsUpNotificationSystemContainer(mContext,
+ testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager,
+ () -> mCarStatusBar);
+
+ testableResources.addOverride(
+ R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen, true);
+
+ mOverrideEnabledController = new CarHeadsUpNotificationSystemContainer(mContext,
+ testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager,
+ () -> mCarStatusBar);
+ }
+
+ @Test
+ public void testDisplayNotification_firstNotification_isVisible() {
+ mDefaultController.displayNotification(mNotificationView);
+ assertThat(mDefaultController.isVisible()).isTrue();
+ }
+
+ @Test
+ public void testRemoveNotification_lastNotification_isInvisible() {
+ mDefaultController.displayNotification(mNotificationView);
+ mDefaultController.removeNotification(mNotificationView);
+ assertThat(mDefaultController.isVisible()).isFalse();
+ }
+
+ @Test
+ public void testRemoveNotification_nonLastNotification_isVisible() {
+ mDefaultController.displayNotification(mNotificationView);
+ mDefaultController.displayNotification(mNotificationView2);
+ mDefaultController.removeNotification(mNotificationView);
+ assertThat(mDefaultController.isVisible()).isTrue();
+ }
+
+ @Test
+ public void testDisplayNotification_userSetupInProgress_isInvisible() {
+ when(mCarDeviceProvisionedController.isCurrentUserSetupInProgress()).thenReturn(true);
+ mDefaultController.displayNotification(mNotificationView);
+ assertThat(mDefaultController.isVisible()).isFalse();
+
+ }
+
+ @Test
+ public void testDisplayNotification_userSetupIncomplete_isInvisible() {
+ when(mCarDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
+ mDefaultController.displayNotification(mNotificationView);
+ assertThat(mDefaultController.isVisible()).isFalse();
+ }
+
+ @Test
+ public void testDisplayNotification_notificationPanelExpanded_isInvisible() {
+ when(mCarStatusBar.isPanelExpanded()).thenReturn(true);
+ mDefaultController.displayNotification(mNotificationView);
+ assertThat(mDefaultController.isVisible()).isFalse();
+ }
+
+ @Test
+ public void testDisplayNotification_notificationPanelExpandedEnabledHUNWhenOpen_isVisible() {
+ when(mCarStatusBar.isPanelExpanded()).thenReturn(true);
+ mOverrideEnabledController.displayNotification(mNotificationView);
+ assertThat(mOverrideEnabledController.isVisible()).isTrue();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 4ebb102..6a7000e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -43,10 +43,6 @@
R.attr.state_encrypted
};
- private static final int[] STATE_METERED = {
- R.attr.state_metered
- };
-
private static final int[] FRICTION_ATTRS = {
R.attr.wifi_friction
};
@@ -201,8 +197,6 @@
if ((mWifiEntry.getSecurity() != WifiEntry.SECURITY_NONE)
&& (mWifiEntry.getSecurity() != WifiEntry.SECURITY_OWE)) {
mFrictionSld.setState(STATE_SECURED);
- } else if (mWifiEntry.isMetered()) {
- mFrictionSld.setState(STATE_METERED);
}
frictionImageView.setImageDrawable(mFrictionSld.getCurrent());
}
diff --git a/packages/SystemUI/res/drawable/dismiss_circle_background.xml b/packages/SystemUI/res/drawable/dismiss_circle_background.xml
new file mode 100644
index 0000000..e311c52
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dismiss_circle_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <stroke
+ android:width="1dp"
+ android:color="#66FFFFFF" />
+
+ <solid android:color="#B3000000" />
+
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/dismiss_target_x.xml b/packages/SystemUI/res/drawable/dismiss_target_x.xml
new file mode 100644
index 0000000..3672eff
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dismiss_target_x.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<!-- 'X' icon. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
+ android:fillColor="#FFFFFFFF"
+ android:strokeColor="#FF000000"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
new file mode 100644
index 0000000..08209ab
--- /dev/null
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -0,0 +1,69 @@
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tag="row"
+ android:layout_width="@dimen/volume_dialog_row_width"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:theme="@style/qs_theme">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:orientation="horizontal" >
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/volume_row_icon"
+ style="@style/VolumeButtons"
+ android:layout_width="@dimen/volume_dialog_tap_target_size"
+ android:layout_height="@dimen/volume_dialog_tap_target_size"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:tint="@color/accent_tint_color_selector"
+ android:soundEffectsEnabled="false" />
+ <TextView
+ android:id="@+id/volume_row_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLength="10"
+ android:maxLines="1"
+ android:visibility="gone"
+ android:textColor="?android:attr/colorControlNormal"
+ android:textAppearance="@style/TextAppearance.Volume.Header" />
+ <FrameLayout
+ android:id="@+id/volume_row_slider_frame"
+ android:layout_height="match_parent"
+ android:layoutDirection="ltr"
+ android:layout_width="@dimen/volume_dialog_row_width">
+ <SeekBar
+ android:id="@+id/volume_row_slider"
+ android:clickable="false"
+ android:layout_width="@dimen/volume_dialog_row_width"
+ android:layout_height="match_parent"
+ android:layoutDirection="ltr"
+ android:layout_gravity="center"
+ android:rotation="0" />
+ </FrameLayout>
+ </LinearLayout>
+
+ <include layout="@layout/volume_dnd_icon"/>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml
index 22303dc..34bd703 100644
--- a/packages/SystemUI/res/layout/qs_media_panel.xml
+++ b/packages/SystemUI/res/layout/qs_media_panel.xml
@@ -32,7 +32,6 @@
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:id="@+id/header"
android:layout_marginBottom="16dp"
>
@@ -73,7 +72,7 @@
<!-- Song name -->
<TextView
- android:id="@+id/header_text"
+ android:id="@+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
diff --git a/packages/SystemUI/res/values-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml
new file mode 100644
index 0000000..499341c
--- /dev/null
+++ b/packages/SystemUI/res/values-land-television/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<resources>
+ <!-- Width of volume bar -->
+ <dimen name="volume_dialog_row_width">252dp</dimen>
+ <dimen name="volume_dialog_tap_target_size">36dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7aaf6f9..9437485 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -983,6 +983,9 @@
<!-- The touchable/draggable edge size for PIP resize. -->
<dimen name="pip_resize_edge_size">30dp</dimen>
+ <!-- The corner radius for PiP window. -->
+ <dimen name="pip_corner_radius">8dp</dimen>
+
<dimen name="default_gear_space">18dp</dimen>
<dimen name="cell_overlay_padding">18dp</dimen>
@@ -1183,6 +1186,9 @@
<dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
<dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
+ <dimen name="dismiss_circle_size">52dp</dimen>
+ <dimen name="dismiss_target_x_size">24dp</dimen>
+
<!-- Bubbles user education views -->
<dimen name="bubbles_manage_education_width">160dp</dimen>
<!-- The inset from the top bound of the manage button to place the user education. -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
index 8809d83..7cfa289 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
@@ -62,7 +62,7 @@
surfaceControl.getWidth(),
surfaceControl.getHeight(),
WindowManager.LayoutParams.TYPE_APPLICATION,
- 0,
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
mOpacity);
mSurfaceControlViewHost.addView(view, layoutParams);
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 24fa91b..284074e 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -26,7 +26,7 @@
import kotlin.math.roundToInt
-const val TAG = "CameraOpTransitionController"
+const val TAG = "CameraAvailabilityListener"
/**
* Listens for usage of the Camera and controls the ScreenDecorations transition to show extra
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 86aa640..cab9f18 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -724,9 +724,10 @@
private final List<Rect> mBounds = new ArrayList();
private final Rect mBoundingRect = new Rect();
private final Path mBoundingPath = new Path();
- // Don't initialize these because they are cached elsewhere and may not exist
+ // Don't initialize these yet because they may never exist
private Rect mProtectionRect;
private Path mProtectionPath;
+ private Path mProtectionPathOrig;
private Rect mTotalBounds = new Rect();
// Whether or not to show the cutout protection path
private boolean mShowProtection = false;
@@ -812,7 +813,11 @@
}
void setProtection(Path protectionPath, Rect pathBounds) {
- mProtectionPath = protectionPath;
+ if (mProtectionPathOrig == null) {
+ mProtectionPathOrig = new Path();
+ mProtectionPath = new Path();
+ }
+ mProtectionPathOrig.set(protectionPath);
mProtectionRect = pathBounds;
}
@@ -889,7 +894,9 @@
Matrix m = new Matrix();
transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
mBoundingPath.transform(m);
- if (mProtectionPath != null) {
+ if (mProtectionPathOrig != null) {
+ // Reset the protection path so we don't aggregate rotations
+ mProtectionPath.set(mProtectionPathOrig);
mProtectionPath.transform(m);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 0cf6d89..8cc10d9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -32,7 +32,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.annotation.NonNull;
import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
@@ -47,7 +46,6 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
-import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Log;
import android.view.Choreographer;
@@ -56,6 +54,7 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -66,6 +65,7 @@
import android.widget.TextView;
import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
@@ -81,7 +81,10 @@
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
import com.android.systemui.bubbles.animation.StackAnimationController;
import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.systemui.util.animation.PhysicsAnimator;
+import com.android.systemui.util.magnetictarget.MagnetizedObject;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -227,8 +230,6 @@
pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress);
pw.print(" showingDismiss: "); pw.println(mShowingDismiss);
pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating);
- pw.print(" draggingInDismiss: "); pw.println(mDraggingInDismissTarget);
- pw.print(" animatingMagnet: "); pw.println(mAnimatingMagnet);
mStackAnimationController.dump(fd, pw, args);
mExpandedAnimationController.dump(fd, pw, args);
}
@@ -240,16 +241,6 @@
private boolean mIsExpansionAnimating = false;
private boolean mShowingDismiss = false;
- /**
- * Whether the user is currently dragging their finger within the dismiss target. In this state
- * the stack will be magnetized to the center of the target, so we shouldn't move it until the
- * touch exits the dismiss target area.
- */
- private boolean mDraggingInDismissTarget = false;
-
- /** Whether the stack is magneting towards the dismiss target. */
- private boolean mAnimatingMagnet = false;
-
/** The view to desaturate/darken when magneted to the dismiss target. */
private View mDesaturateAndDarkenTargetView;
@@ -331,8 +322,100 @@
@NonNull
private final SurfaceSynchronizer mSurfaceSynchronizer;
- private BubbleDismissView mDismissContainer;
- private Runnable mAfterMagnet;
+ /**
+ * The currently magnetized object, which is being dragged and will be attracted to the magnetic
+ * dismiss target.
+ *
+ * This is either the stack itself, or an individual bubble.
+ */
+ private MagnetizedObject<?> mMagnetizedObject;
+
+ /**
+ * The action to run when the magnetized object is released in the dismiss target.
+ *
+ * This will actually perform the dismissal of either the stack or an individual bubble.
+ */
+ private Runnable mReleasedInDismissTargetAction;
+
+ /**
+ * The MagneticTarget instance for our circular dismiss view. This is added to the
+ * MagnetizedObject instances for the stack and any dragged-out bubbles.
+ */
+ private MagnetizedObject.MagneticTarget mMagneticTarget;
+
+ /** Magnet listener that handles animating and dismissing individual dragged-out bubbles. */
+ private final MagnetizedObject.MagnetListener mIndividualBubbleMagnetListener =
+ new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ animateDesaturateAndDarken(
+ mExpandedAnimationController.getDraggedOutBubble(), true);
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+ animateDesaturateAndDarken(
+ mExpandedAnimationController.getDraggedOutBubble(), false);
+
+ if (wasFlungOut) {
+ mExpandedAnimationController.snapBubbleBack(
+ mExpandedAnimationController.getDraggedOutBubble(), velX, velY);
+ hideDismissTarget();
+ } else {
+ mExpandedAnimationController.onUnstuckFromTarget();
+ }
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ mExpandedAnimationController.dismissDraggedOutBubble(
+ mExpandedAnimationController.getDraggedOutBubble(),
+ mReleasedInDismissTargetAction);
+ hideDismissTarget();
+ }
+ };
+
+ /** Magnet listener that handles animating and dismissing the entire stack. */
+ private final MagnetizedObject.MagnetListener mStackMagnetListener =
+ new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(
+ @NonNull MagnetizedObject.MagneticTarget target) {
+ animateDesaturateAndDarken(mBubbleContainer, true);
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+ animateDesaturateAndDarken(mBubbleContainer, false);
+
+ if (wasFlungOut) {
+ mStackAnimationController.flingStackThenSpringToEdge(
+ mStackAnimationController.getStackPosition().x, velX, velY);
+ hideDismissTarget();
+ } else {
+ mStackAnimationController.onUnstuckFromTarget();
+ }
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ mStackAnimationController.implodeStack(
+ () -> {
+ resetDesaturationAndDarken();
+ mReleasedInDismissTargetAction.run();
+ }
+ );
+
+ hideDismissTarget();
+ }
+ };
+
+ private ViewGroup mDismissTargetContainer;
+ private PhysicsAnimator<View> mDismissTargetAnimator;
+ private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -409,12 +492,31 @@
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
- mDismissContainer = new BubbleDismissView(mContext);
- mDismissContainer.setLayoutParams(new FrameLayout.LayoutParams(
+ final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+ final View targetView = new DismissCircleView(context);
+ final FrameLayout.LayoutParams newParams =
+ new FrameLayout.LayoutParams(targetSize, targetSize);
+ newParams.gravity = Gravity.CENTER;
+ targetView.setLayoutParams(newParams);
+ mDismissTargetAnimator = PhysicsAnimator.getInstance(targetView);
+
+ mDismissTargetContainer = new FrameLayout(context);
+ mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams(
MATCH_PARENT,
getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
Gravity.BOTTOM));
- addView(mDismissContainer);
+ mDismissTargetContainer.setClipChildren(false);
+ mDismissTargetContainer.addView(targetView);
+ mDismissTargetContainer.setVisibility(View.INVISIBLE);
+ addView(mDismissTargetContainer);
+
+ // Start translated down so the target springs up.
+ targetView.setTranslationY(
+ getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height));
+
+ // Save the MagneticTarget instance for the newly set up view - we'll add this to the
+ // MagnetizedObjects.
+ mMagneticTarget = new MagnetizedObject.MagneticTarget(targetView, mBubbleSize * 2);
mExpandedViewXAnim =
new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
@@ -1066,6 +1168,14 @@
}
}
+ /*
+ * Sets the action to run to dismiss the currently dragging object (either the stack or an
+ * individual bubble).
+ */
+ public void setReleasedInDismissTargetAction(Runnable action) {
+ mReleasedInDismissTargetAction = action;
+ }
+
/**
* Dismiss the stack of bubbles.
*
@@ -1262,7 +1372,12 @@
Log.d(TAG, "onBubbleDragStart: bubble=" + bubble);
}
maybeShowManageEducation(false);
- mExpandedAnimationController.prepareForBubbleDrag(bubble);
+ mExpandedAnimationController.prepareForBubbleDrag(bubble, mMagneticTarget);
+
+ // We're dragging an individual bubble, so set the magnetized object to the magnetized
+ // bubble.
+ mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
+ mMagnetizedObject.setMagnetListener(mIndividualBubbleMagnetListener);
}
/** Called with the coordinates to which an individual bubble has been dragged. */
@@ -1304,7 +1419,9 @@
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
- mDraggingInDismissTarget = false;
+ // Since we're dragging the stack, set the magnetized object to the magnetized stack.
+ mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
+ mMagnetizedObject.setMagnetListener(mStackMagnetListener);
}
void onDragged(float x, float y) {
@@ -1425,6 +1542,11 @@
}
}
+ /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
+ boolean passEventToMagnetizedObject(MotionEvent event) {
+ return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
/** Prepares and starts the desaturate/darken animation on the bubble stack. */
private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
mDesaturateAndDarkenTargetView = targetView;
@@ -1455,102 +1577,6 @@
mDesaturateAndDarkenTargetView.setLayerType(View.LAYER_TYPE_NONE, null);
}
- /**
- * Magnets the stack to the target, while also transforming the target to encircle the stack and
- * desaturating/darkening the bubbles.
- */
- void animateMagnetToDismissTarget(
- View magnetView, boolean toTarget, float x, float y, float velX, float velY) {
- mDraggingInDismissTarget = toTarget;
-
- if (toTarget) {
- // The Y-value for the bubble stack to be positioned in the center of the dismiss target
- final float destY = mDismissContainer.getDismissTargetCenterY() - mBubbleSize / 2f;
-
- mAnimatingMagnet = true;
-
- final Runnable afterMagnet = () -> {
- mAnimatingMagnet = false;
- if (mAfterMagnet != null) {
- mAfterMagnet.run();
- }
- };
-
- if (magnetView == this) {
- mStackAnimationController.magnetToDismiss(velX, velY, destY, afterMagnet);
- animateDesaturateAndDarken(mBubbleContainer, true);
- } else {
- mExpandedAnimationController.magnetBubbleToDismiss(
- magnetView, velX, velY, destY, afterMagnet);
-
- animateDesaturateAndDarken(magnetView, true);
- }
- } else {
- mAnimatingMagnet = false;
-
- if (magnetView == this) {
- mStackAnimationController.demagnetizeFromDismissToPoint(x, y, velX, velY);
- animateDesaturateAndDarken(mBubbleContainer, false);
- } else {
- mExpandedAnimationController.demagnetizeBubbleTo(x, y, velX, velY);
- animateDesaturateAndDarken(magnetView, false);
- }
- }
-
- mVibrator.vibrate(VibrationEffect.get(toTarget
- ? VibrationEffect.EFFECT_CLICK
- : VibrationEffect.EFFECT_TICK));
- }
-
- /**
- * Magnets the stack to the dismiss target if it's not already there. Then, dismiss the stack
- * using the 'implode' animation and animate out the target.
- */
- void magnetToStackIfNeededThenAnimateDismissal(
- View touchedView, float velX, float velY, Runnable after) {
- final View draggedOutBubble = mExpandedAnimationController.getDraggedOutBubble();
- final Runnable animateDismissal = () -> {
- mAfterMagnet = null;
-
- mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
- mDismissContainer.springOut();
-
- // 'Implode' the stack and then hide the dismiss target.
- if (touchedView == this) {
- mStackAnimationController.implodeStack(
- () -> {
- mAnimatingMagnet = false;
- mShowingDismiss = false;
- mDraggingInDismissTarget = false;
- after.run();
- resetDesaturationAndDarken();
- });
- } else {
- mExpandedAnimationController.dismissDraggedOutBubble(draggedOutBubble, () -> {
- mAnimatingMagnet = false;
- mShowingDismiss = false;
- mDraggingInDismissTarget = false;
- resetDesaturationAndDarken();
- after.run();
- });
- }
- };
-
- if (mAnimatingMagnet) {
- // If the magnet animation is currently playing, dismiss the stack after it's done. This
- // happens if the stack is flung towards the target.
- mAfterMagnet = animateDismissal;
- } else if (mDraggingInDismissTarget) {
- // If we're in the dismiss target, but not animating, we already magneted - dismiss
- // immediately.
- animateDismissal.run();
- } else {
- // Otherwise, we need to start the magnet animation and then dismiss afterward.
- animateMagnetToDismissTarget(touchedView, true, -1 /* x */, -1 /* y */, velX, velY);
- mAfterMagnet = animateDismissal;
- }
- }
-
/** Animates in the dismiss target. */
private void springInDismissTarget() {
if (mShowingDismiss) {
@@ -1559,10 +1585,14 @@
mShowingDismiss = true;
- // Show the dismiss container and bring it to the front so the bubbles will go behind it.
- mDismissContainer.springIn();
- mDismissContainer.bringToFront();
- mDismissContainer.setZ(Short.MAX_VALUE - 1);
+ mDismissTargetContainer.bringToFront();
+ mDismissTargetContainer.setZ(Short.MAX_VALUE - 1);
+ mDismissTargetContainer.setVisibility(VISIBLE);
+
+ mDismissTargetAnimator.cancel();
+ mDismissTargetAnimator
+ .spring(DynamicAnimation.TRANSLATION_Y, 0f, mDismissTargetSpring)
+ .start();
}
/**
@@ -1574,13 +1604,13 @@
return;
}
- mDismissContainer.springOut();
mShowingDismiss = false;
- }
- /** Whether the location of the given MotionEvent is within the dismiss target area. */
- boolean isInDismissTarget(MotionEvent ev) {
- return isIntersecting(mDismissContainer.getDismissTarget(), ev.getRawX(), ev.getRawY());
+ mDismissTargetAnimator
+ .spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(),
+ mDismissTargetSpring)
+ .withEndActions(() -> mDismissTargetContainer.setVisibility(View.INVISIBLE))
+ .start();
}
/** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 46d1e0d..0c5bef4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -30,28 +30,6 @@
* dismissing, and flings.
*/
class BubbleTouchHandler implements View.OnTouchListener {
- /** Velocity required to dismiss the stack without dragging it into the dismiss target. */
- private static final float STACK_DISMISS_MIN_VELOCITY = 4000f;
-
- /**
- * Velocity required to dismiss an individual bubble without dragging it into the dismiss
- * target.
- *
- * This is higher than the stack dismiss velocity since unlike the stack, a downward fling could
- * also be an attempted gesture to return the bubble to the row of expanded bubbles, which would
- * usually be below the dragged bubble. By increasing the required velocity, it's less likely
- * that the user is trying to drop it back into the row vs. fling it away.
- */
- private static final float INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY = 6000f;
-
- /**
- * When the stack is flung towards the bottom of the screen, it'll be dismissed if it's flung
- * towards the center of the screen (where the dismiss target is). This value is the width of
- * the target area to be considered 'towards the target'. For example 50% means that the stack
- * needs to be flung towards the middle 50%, and the 25% on the left and right sides won't
- * count.
- */
- private static final float DISMISS_FLING_TARGET_WIDTH_PERCENT = 0.5f;
private final PointF mTouchDown = new PointF();
private final PointF mViewPositionOnTouchDown = new PointF();
@@ -66,8 +44,6 @@
/** View that was initially touched, when we received the first ACTION_DOWN event. */
private View mTouchedView;
- /** Whether the current touched view is in the dismiss target. */
- private boolean mInDismissTarget;
BubbleTouchHandler(BubbleStackView stackView,
BubbleData bubbleData, Context context) {
@@ -124,13 +100,33 @@
if (isStack) {
mViewPositionOnTouchDown.set(mStack.getStackPosition());
+
+ // Dismiss the entire stack if it's released in the dismiss target.
+ mStack.setReleasedInDismissTargetAction(
+ () -> mController.dismissStack(BubbleController.DISMISS_USER_GESTURE));
mStack.onDragStart();
+ mStack.passEventToMagnetizedObject(event);
} else if (isFlyout) {
mStack.onFlyoutDragStart();
} else {
mViewPositionOnTouchDown.set(
mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
+
+ // Dismiss only the dragged-out bubble if it's released in the target.
+ final String individualBubbleKey = ((BadgedImageView) mTouchedView).getKey();
+ mStack.setReleasedInDismissTargetAction(() -> {
+ final Bubble bubble =
+ mBubbleData.getBubbleWithKey(individualBubbleKey);
+ // bubble can be null if the user is in the middle of
+ // dismissing the bubble, but the app also sent a cancel
+ if (bubble != null) {
+ mController.removeBubble(bubble.getEntry(),
+ BubbleController.DISMISS_USER_GESTURE);
+ }
+ });
+
mStack.onBubbleDragStart(mTouchedView);
+ mStack.passEventToMagnetizedObject(event);
}
break;
@@ -144,27 +140,16 @@
}
if (mMovedEnough) {
- if (isStack) {
- mStack.onDragged(viewX, viewY);
- } else if (isFlyout) {
+ if (isFlyout) {
mStack.onFlyoutDragged(deltaX);
- } else {
- mStack.onBubbleDragged(mTouchedView, viewX, viewY);
- }
- }
-
- final boolean currentlyInDismissTarget = mStack.isInDismissTarget(event);
- if (currentlyInDismissTarget != mInDismissTarget) {
- mInDismissTarget = currentlyInDismissTarget;
-
- mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
- final float velX = mVelocityTracker.getXVelocity();
- final float velY = mVelocityTracker.getYVelocity();
-
- // If the touch event is within the dismiss target, magnet the stack to it.
- if (!isFlyout) {
- mStack.animateMagnetToDismissTarget(
- mTouchedView, mInDismissTarget, viewX, viewY, velX, velY);
+ } else if (!mStack.passEventToMagnetizedObject(event)) {
+ // If the magnetic target doesn't consume the event, drag the stack or
+ // bubble.
+ if (isStack) {
+ mStack.onDragged(viewX, viewY);
+ } else {
+ mStack.onBubbleDragged(mTouchedView, viewX, viewY);
+ }
}
}
break;
@@ -179,42 +164,21 @@
final float velX = mVelocityTracker.getXVelocity();
final float velY = mVelocityTracker.getYVelocity();
- final boolean shouldDismiss =
- isStack
- ? mInDismissTarget
- || isFastFlingTowardsDismissTarget(rawX, rawY, velX, velY)
- : mInDismissTarget
- || velY > INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY;
-
if (isFlyout && mMovedEnough) {
mStack.onFlyoutDragFinished(rawX - mTouchDown.x /* deltaX */, velX);
- } else if (shouldDismiss) {
- final String individualBubbleKey =
- isStack ? null : ((BadgedImageView) mTouchedView).getKey();
- mStack.magnetToStackIfNeededThenAnimateDismissal(mTouchedView, velX, velY,
- () -> {
- if (isStack) {
- mController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
- } else {
- final Bubble bubble =
- mBubbleData.getBubbleWithKey(individualBubbleKey);
- // bubble can be null if the user is in the middle of
- // dismissing the bubble, but the app also sent a cancel
- if (bubble != null) {
- mController.removeBubble(bubble.getEntry(),
- BubbleController.DISMISS_USER_GESTURE);
- }
- }
- });
} else if (isFlyout) {
if (!mBubbleData.isExpanded() && !mMovedEnough) {
mStack.onFlyoutTapped();
}
} else if (mMovedEnough) {
- if (isStack) {
- mStack.onDragFinish(viewX, viewY, velX, velY);
- } else {
- mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY);
+ if (!mStack.passEventToMagnetizedObject(event)) {
+ // If the magnetic target didn't consume the event, tell the stack to finish
+ // the drag.
+ if (isStack) {
+ mStack.onDragFinish(viewX, viewY, velX, velY);
+ } else {
+ mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY);
+ }
}
} else if (mTouchedView == mStack.getExpandedBubbleView()) {
mBubbleData.setExpanded(false);
@@ -235,45 +199,15 @@
return true;
}
- /**
- * Whether the given touch data represents a powerful fling towards the bottom-center of the
- * screen (the dismiss target).
- */
- private boolean isFastFlingTowardsDismissTarget(
- float rawX, float rawY, float velX, float velY) {
- // Not a fling downward towards the target if velocity is zero or negative.
- if (velY <= 0) {
- return false;
- }
-
- float bottomOfScreenInterceptX = rawX;
-
- // Only do math if the X velocity is non-zero, otherwise X won't change.
- if (velX != 0) {
- // Rise over run...
- final float slope = velY / velX;
- // ...y = mx + b, b = y / mx...
- final float yIntercept = rawY - slope * rawX;
- // ...calculate the x value when y = bottom of the screen.
- bottomOfScreenInterceptX = (mStack.getHeight() - yIntercept) / slope;
- }
-
- final float dismissTargetWidth =
- mStack.getWidth() * DISMISS_FLING_TARGET_WIDTH_PERCENT;
- return velY > STACK_DISMISS_MIN_VELOCITY
- && bottomOfScreenInterceptX > dismissTargetWidth / 2f
- && bottomOfScreenInterceptX < mStack.getWidth() - dismissTargetWidth / 2f;
- }
-
/** Clears all touch-related state. */
private void resetForNextGesture() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+
mTouchedView = null;
mMovedEnough = false;
- mInDismissTarget = false;
mStack.onGestureFinished();
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 607b5ef..3eaa90c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -25,13 +25,14 @@
import android.view.View;
import android.view.WindowInsets;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleExperimentConfig;
+import com.android.systemui.util.magnetictarget.MagnetizedObject;
import com.google.android.collect.Sets;
@@ -62,6 +63,12 @@
/** What percentage of the screen to use when centering the bubbles in landscape. */
private static final float CENTER_BUBBLES_LANDSCAPE_PERCENT = 0.66f;
+ /**
+ * Velocity required to dismiss an individual bubble without dragging it into the dismiss
+ * target.
+ */
+ private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
+
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
private float mStackOffsetPx;
/** Space between status bar and bubbles in the expanded state. */
@@ -79,9 +86,6 @@
/** What the current screen orientation is. */
private int mScreenOrientation;
- /** Whether the dragged-out bubble is in the dismiss target. */
- private boolean mIndividualBubbleWithinDismissTarget = false;
-
private boolean mAnimatingExpand = false;
private boolean mAnimatingCollapse = false;
private @Nullable Runnable mAfterExpand;
@@ -99,6 +103,17 @@
*/
private boolean mSpringingBubbleToTouch = false;
+ /**
+ * Whether to spring the bubble to the next touch event coordinates. This is used to animate the
+ * bubble out of the magnetic dismiss target to the touch location.
+ *
+ * Once it 'catches up' and the animation ends, we'll revert to moving it directly.
+ */
+ private boolean mSpringToTouchOnNextMotionEvent = false;
+
+ /** The bubble currently being dragged out of the row (to potentially be dismissed). */
+ private MagnetizedObject<View> mMagnetizedBubbleDraggingOut;
+
private int mExpandedViewPadding;
public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
@@ -113,9 +128,6 @@
*/
private boolean mBubbleDraggedOutEnough = false;
- /** The bubble currently being dragged out of the row (to potentially be dismissed). */
- private View mBubbleDraggingOut;
-
/**
* Animates expanding the bubbles into a row along the top of the screen.
*/
@@ -235,12 +247,46 @@
}).startAll(after);
}
+ /** Notifies the controller that the dragged-out bubble was unstuck from the magnetic target. */
+ public void onUnstuckFromTarget() {
+ mSpringToTouchOnNextMotionEvent = true;
+ }
+
/** Prepares the given bubble to be dragged out. */
- public void prepareForBubbleDrag(View bubble) {
+ public void prepareForBubbleDrag(View bubble, MagnetizedObject.MagneticTarget target) {
mLayout.cancelAnimationsOnView(bubble);
- mBubbleDraggingOut = bubble;
- mBubbleDraggingOut.setTranslationZ(Short.MAX_VALUE);
+ bubble.setTranslationZ(Short.MAX_VALUE);
+ mMagnetizedBubbleDraggingOut = new MagnetizedObject<View>(
+ mLayout.getContext(), bubble,
+ DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
+ @Override
+ public float getWidth(@NonNull View underlyingObject) {
+ return mBubbleSizePx;
+ }
+
+ @Override
+ public float getHeight(@NonNull View underlyingObject) {
+ return mBubbleSizePx;
+ }
+
+ @Override
+ public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) {
+ loc[0] = (int) bubble.getTranslationX();
+ loc[1] = (int) bubble.getTranslationY();
+ }
+ };
+ mMagnetizedBubbleDraggingOut.addTarget(target);
+ mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
+ mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ }
+
+ private void springBubbleTo(View bubble, float x, float y) {
+ animationForChild(bubble)
+ .translationX(x)
+ .translationY(y)
+ .withStiffness(SpringForce.STIFFNESS_HIGH)
+ .start();
}
/**
@@ -249,20 +295,20 @@
* bubble is dragged back into the row.
*/
public void dragBubbleOut(View bubbleView, float x, float y) {
- if (mSpringingBubbleToTouch) {
+ if (mSpringToTouchOnNextMotionEvent) {
+ springBubbleTo(mMagnetizedBubbleDraggingOut.getUnderlyingObject(), x, y);
+ mSpringToTouchOnNextMotionEvent = false;
+ mSpringingBubbleToTouch = true;
+ } else if (mSpringingBubbleToTouch) {
if (mLayout.arePropertiesAnimatingOnView(
bubbleView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)) {
- animationForChild(mBubbleDraggingOut)
- .translationX(x)
- .translationY(y)
- .withStiffness(SpringForce.STIFFNESS_HIGH)
- .start();
+ springBubbleTo(mMagnetizedBubbleDraggingOut.getUnderlyingObject(), x, y);
} else {
mSpringingBubbleToTouch = false;
}
}
- if (!mSpringingBubbleToTouch && !mIndividualBubbleWithinDismissTarget) {
+ if (!mSpringingBubbleToTouch && !mMagnetizedBubbleDraggingOut.getObjectStuckToTarget()) {
bubbleView.setTranslationX(x);
bubbleView.setTranslationY(y);
}
@@ -277,8 +323,6 @@
/** Plays a dismiss animation on the dragged out bubble. */
public void dismissDraggedOutBubble(View bubble, Runnable after) {
- mIndividualBubbleWithinDismissTarget = false;
-
animationForChild(bubble)
.withStiffness(SpringForce.STIFFNESS_HIGH)
.scaleX(1.1f)
@@ -290,37 +334,14 @@
}
@Nullable public View getDraggedOutBubble() {
- return mBubbleDraggingOut;
+ return mMagnetizedBubbleDraggingOut == null
+ ? null
+ : mMagnetizedBubbleDraggingOut.getUnderlyingObject();
}
- /** Magnets the given bubble to the dismiss target. */
- public void magnetBubbleToDismiss(
- View bubbleView, float velX, float velY, float destY, Runnable after) {
- mIndividualBubbleWithinDismissTarget = true;
- mSpringingBubbleToTouch = false;
- animationForChild(bubbleView)
- .withStiffness(SpringForce.STIFFNESS_MEDIUM)
- .withDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
- .withPositionStartVelocities(velX, velY)
- .translationX(mLayout.getWidth() / 2f - mBubbleSizePx / 2f)
- .translationY(destY, after)
- .start();
- }
-
- /**
- * Springs the dragged-out bubble towards the given coordinates and sets flags to have touch
- * events update the spring's final position until it's settled.
- */
- public void demagnetizeBubbleTo(float x, float y, float velX, float velY) {
- mIndividualBubbleWithinDismissTarget = false;
- mSpringingBubbleToTouch = true;
-
- animationForChild(mBubbleDraggingOut)
- .translationX(x)
- .translationY(y)
- .withPositionStartVelocities(velX, velY)
- .withStiffness(SpringForce.STIFFNESS_HIGH)
- .start();
+ /** Returns the MagnetizedObject instance for the dragging-out bubble. */
+ public MagnetizedObject<View> getMagnetizedBubbleDraggingOut() {
+ return mMagnetizedBubbleDraggingOut;
}
/**
@@ -335,13 +356,14 @@
.withPositionStartVelocities(velX, velY)
.start(() -> bubbleView.setTranslationZ(0f) /* after */);
+ mMagnetizedBubbleDraggingOut = null;
+
updateBubblePositions();
}
/** Resets bubble drag out gesture flags. */
public void onGestureFinished() {
mBubbleDraggedOutEnough = false;
- mBubbleDraggingOut = null;
updateBubblePositions();
}
@@ -373,7 +395,6 @@
pw.print(" isActive: "); pw.println(isActiveController());
pw.print(" animatingExpand: "); pw.println(mAnimatingExpand);
pw.print(" animatingCollapse: "); pw.println(mAnimatingCollapse);
- pw.print(" bubbleInDismiss: "); pw.println(mIndividualBubbleWithinDismissTarget);
pw.print(" springingBubble: "); pw.println(mSpringingBubbleToTouch);
}
@@ -453,8 +474,8 @@
final PhysicsAnimationLayout.PhysicsPropertyAnimator animator = animationForChild(child);
// If we're removing the dragged-out bubble, that means it got dismissed.
- if (child.equals(mBubbleDraggingOut)) {
- mBubbleDraggingOut = null;
+ if (child.equals(getDraggedOutBubble())) {
+ mMagnetizedBubbleDraggingOut = null;
finishRemoval.run();
} else {
animator.alpha(0f, finishRemoval /* endAction */)
@@ -490,7 +511,7 @@
// Don't animate the dragging out bubble, or it'll jump around while being dragged. It
// will be snapped to the correct X value after the drag (if it's not dismissed).
- if (bubble.equals(mBubbleDraggingOut)) {
+ if (bubble.equals(getDraggedOutBubble())) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index f22c8fa..b81665c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -16,7 +16,6 @@
package com.android.systemui.bubbles.animation;
-import android.annotation.NonNull;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -25,6 +24,7 @@
import android.view.View;
import android.view.WindowInsets;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FlingAnimation;
@@ -35,6 +35,7 @@
import com.android.systemui.R;
import com.android.systemui.util.FloatingContentCoordinator;
import com.android.systemui.util.animation.PhysicsAnimator;
+import com.android.systemui.util.magnetictarget.MagnetizedObject;
import com.google.android.collect.Sets;
@@ -92,6 +93,9 @@
*/
private static final float ESCAPE_VELOCITY = 750f;
+ /** Velocity required to dismiss the stack without dragging it into the dismiss target. */
+ private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f;
+
/**
* The canonical position of the stack. This is typically the position of the first bubble, but
* we need to keep track of it separately from the first bubble's translation in case there are
@@ -100,6 +104,12 @@
private PointF mStackPosition = new PointF(-1, -1);
/**
+ * MagnetizedObject instance for the stack, which is used by the touch handler for the magnetic
+ * dismiss target.
+ */
+ private MagnetizedObject<StackAnimationController> mMagnetizedStack;
+
+ /**
* The area that Bubbles will occupy after all animations end. This is used to move other
* floating content out of the way proactively.
*/
@@ -136,11 +146,6 @@
private boolean mIsMovingFromFlinging = false;
/**
- * Whether the stack is within the dismiss target (either by being dragged, magnet'd, or flung).
- */
- private boolean mWithinDismissTarget = false;
-
- /**
* Whether the first bubble is springing towards the touch point, rather than using the default
* behavior of moving directly to the touch point with the rest of the stack following it.
*
@@ -154,6 +159,14 @@
*/
private boolean mFirstBubbleSpringingToTouch = false;
+ /**
+ * Whether to spring the stack to the next touch event coordinates. This is used to animate the
+ * stack (including the first bubble) out of the magnetic dismiss target to the touch location.
+ * Once it 'catches up' and the animation ends, we'll revert to moving the first bubble directly
+ * and only animating the following bubbles.
+ */
+ private boolean mSpringToTouchOnNextMotionEvent = false;
+
/** Horizontal offset of bubbles in the stack. */
private float mStackOffset;
/** Diameter of the bubble icon. */
@@ -273,7 +286,8 @@
* Note that we need new SpringForce instances per animation despite identical configs because
* SpringAnimation uses SpringForce's internal (changing) velocity while the animation runs.
*/
- public void springStack(float destinationX, float destinationY, float stiffness) {
+ public void springStack(
+ float destinationX, float destinationY, float stiffness) {
notifyFloatingCoordinatorStackAnimatingTo(destinationX, destinationY);
springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X,
@@ -404,7 +418,7 @@
pw.println(mRestingStackPosition != null ? mRestingStackPosition.toString() : "null");
pw.print(" currentStackPos: "); pw.println(mStackPosition.toString());
pw.print(" isMovingFromFlinging: "); pw.println(mIsMovingFromFlinging);
- pw.print(" withinDismiss: "); pw.println(mWithinDismissTarget);
+ pw.print(" withinDismiss: "); pw.println(isStackStuckToTarget());
pw.print(" firstBubbleSpringing: "); pw.println(mFirstBubbleSpringingToTouch);
}
@@ -580,14 +594,18 @@
/** Moves the stack in response to a touch event. */
public void moveStackFromTouch(float x, float y) {
-
- // If we're springing to the touch point to 'catch up' after dragging out of the dismiss
- // target, then update the stack position animations instead of moving the bubble directly.
- if (mFirstBubbleSpringingToTouch) {
+ // Begin the spring-to-touch catch up animation if needed.
+ if (mSpringToTouchOnNextMotionEvent) {
+ springStack(x, y, DEFAULT_STIFFNESS);
+ mSpringToTouchOnNextMotionEvent = false;
+ mFirstBubbleSpringingToTouch = true;
+ } else if (mFirstBubbleSpringingToTouch) {
final SpringAnimation springToTouchX =
- (SpringAnimation) mStackPositionAnimations.get(DynamicAnimation.TRANSLATION_X);
+ (SpringAnimation) mStackPositionAnimations.get(
+ DynamicAnimation.TRANSLATION_X);
final SpringAnimation springToTouchY =
- (SpringAnimation) mStackPositionAnimations.get(DynamicAnimation.TRANSLATION_Y);
+ (SpringAnimation) mStackPositionAnimations.get(
+ DynamicAnimation.TRANSLATION_Y);
// If either animation is still running, we haven't caught up. Update the animations.
if (springToTouchX.isRunning() || springToTouchY.isRunning()) {
@@ -600,56 +618,14 @@
}
}
- if (!mFirstBubbleSpringingToTouch && !mWithinDismissTarget) {
+ if (!mFirstBubbleSpringingToTouch && !isStackStuckToTarget()) {
moveFirstBubbleWithStackFollowing(x, y);
}
}
- /**
- * Demagnetizes the stack, springing it towards the given point. This also sets flags so that
- * subsequent touch events will update the final position of the demagnetization spring instead
- * of directly moving the bubbles, until demagnetization is complete.
- */
- public void demagnetizeFromDismissToPoint(float x, float y, float velX, float velY) {
- mWithinDismissTarget = false;
- mFirstBubbleSpringingToTouch = true;
-
- springFirstBubbleWithStackFollowing(
- DynamicAnimation.TRANSLATION_X,
- new SpringForce()
- .setDampingRatio(DEFAULT_BOUNCINESS)
- .setStiffness(DEFAULT_STIFFNESS),
- velX, x);
-
- springFirstBubbleWithStackFollowing(
- DynamicAnimation.TRANSLATION_Y,
- new SpringForce()
- .setDampingRatio(DEFAULT_BOUNCINESS)
- .setStiffness(DEFAULT_STIFFNESS),
- velY, y);
- }
-
- /**
- * Spring the stack towards the dismiss target, respecting existing velocity. This also sets
- * flags so that subsequent touch events will not move the stack until it's demagnetized.
- */
- public void magnetToDismiss(float velX, float velY, float destY, Runnable after) {
- mWithinDismissTarget = true;
- mFirstBubbleSpringingToTouch = false;
-
- springFirstBubbleWithStackFollowing(
- DynamicAnimation.TRANSLATION_X,
- new SpringForce()
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
- .setStiffness(SpringForce.STIFFNESS_MEDIUM),
- velX, mLayout.getWidth() / 2f - mBubbleBitmapSize / 2f);
-
- springFirstBubbleWithStackFollowing(
- DynamicAnimation.TRANSLATION_Y,
- new SpringForce()
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
- .setStiffness(SpringForce.STIFFNESS_MEDIUM),
- velY, destY, after);
+ /** Notify the controller that the stack has been unstuck from the dismiss target. */
+ public void onUnstuckFromTarget() {
+ mSpringToTouchOnNextMotionEvent = true;
}
/**
@@ -663,13 +639,7 @@
.alpha(0f)
.withDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
.withStiffness(SpringForce.STIFFNESS_HIGH)
- .start(() -> {
- // Run the callback and reset flags. The child translation animations might
- // still be running, but that's fine. Once the alpha is at 0f they're no longer
- // visible anyway.
- after.run();
- mWithinDismissTarget = false;
- });
+ .start(after);
}
/**
@@ -720,7 +690,7 @@
if (property.equals(DynamicAnimation.TRANSLATION_X)
|| property.equals(DynamicAnimation.TRANSLATION_Y)) {
return index + 1;
- } else if (mWithinDismissTarget) {
+ } else if (isStackStuckToTarget()) {
return index + 1; // Chain all animations in dismiss (scale, alpha, etc. are used).
} else {
return NONE;
@@ -733,7 +703,7 @@
if (property.equals(DynamicAnimation.TRANSLATION_X)) {
// If we're in the dismiss target, have the bubbles pile on top of each other with no
// offset.
- if (mWithinDismissTarget) {
+ if (isStackStuckToTarget()) {
return 0f;
} else {
// Offset to the left if we're on the left, or the right otherwise.
@@ -755,7 +725,7 @@
@Override
void onChildAdded(View child, int index) {
// Don't animate additions within the dismiss target.
- if (mWithinDismissTarget) {
+ if (isStackStuckToTarget()) {
return;
}
@@ -784,8 +754,6 @@
if (mLayout.getChildCount() > 0) {
animationForChildAtIndex(0).translationX(mStackPosition.x).start();
} else {
- // If there's no other bubbles, and we were in the dismiss target, reset the flag.
- mWithinDismissTarget = false;
// When all children are removed ensure stack position is sane
setStackPosition(mRestingStackPosition == null
? getDefaultStartPosition()
@@ -831,6 +799,9 @@
}
}
+ private boolean isStackStuckToTarget() {
+ return mMagnetizedStack != null && mMagnetizedStack.getObjectStuckToTarget();
+ }
/** Moves the stack, without any animation, to the starting position. */
private void moveStackToStartPosition() {
@@ -959,6 +930,44 @@
}
/**
+ * Returns the {@link MagnetizedObject} instance for the bubble stack, with the provided
+ * {@link MagnetizedObject.MagneticTarget} added as a target.
+ */
+ public MagnetizedObject<StackAnimationController> getMagnetizedStack(
+ MagnetizedObject.MagneticTarget target) {
+ if (mMagnetizedStack == null) {
+ mMagnetizedStack = new MagnetizedObject<StackAnimationController>(
+ mLayout.getContext(),
+ this,
+ new StackPositionProperty(DynamicAnimation.TRANSLATION_X),
+ new StackPositionProperty(DynamicAnimation.TRANSLATION_Y)
+ ) {
+ @Override
+ public float getWidth(@NonNull StackAnimationController underlyingObject) {
+ return mBubbleSize;
+ }
+
+ @Override
+ public float getHeight(@NonNull StackAnimationController underlyingObject) {
+ return mBubbleSize;
+ }
+
+ @Override
+ public void getLocationOnScreen(@NonNull StackAnimationController underlyingObject,
+ @NonNull int[] loc) {
+ loc[0] = (int) mStackPosition.x;
+ loc[1] = (int) mStackPosition.y;
+ }
+ };
+ mMagnetizedStack.addTarget(target);
+ mMagnetizedStack.setHapticsEnabled(true);
+ mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ }
+
+ return mMagnetizedStack;
+ }
+
+ /**
* FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's
* translation and animate the rest of the stack with it. A DynamicAnimation can animate this
* property directly to move the first bubble and cause the stack to 'follow' to the new
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index f818d19..50bd1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -125,14 +125,19 @@
@VisibleForTesting
internal val settingObserver = object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean, uri: Uri, userId: Int) {
+ override fun onChange(
+ selfChange: Boolean,
+ uris: MutableIterable<Uri>,
+ flags: Int,
+ userId: Int
+ ) {
// Do not listen to changes in the middle of user change, those will be read by the
// user-switch receiver.
if (userChanging || userId != currentUserId) {
return
}
available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE,
- DEFAULT_ENABLED, currentUserId) != 0
+ DEFAULT_ENABLED, currentUserId) != 0
resetFavorites(available)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 44e5d3d..c28a719 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -261,7 +261,7 @@
private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
+ public void onChange(boolean selfChange, Iterable<Uri> uris, int flags, int userId) {
if (userId != ActivityManager.getCurrentUser()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
new file mode 100644
index 0000000..a161d03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -0,0 +1,425 @@
+/*
+ * 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 com.android.systemui.media;
+
+import android.annotation.LayoutRes;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.RippleDrawable;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.settingslib.media.MediaDevice;
+import com.android.settingslib.media.MediaOutputSliceConstants;
+import com.android.settingslib.widget.AdaptiveIcon;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.NotificationMediaManager;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Base media control panel for System UI
+ */
+public class MediaControlPanel implements NotificationMediaManager.MediaListener {
+ private static final String TAG = "MediaControlPanel";
+ private final NotificationMediaManager mMediaManager;
+ private final Executor mBackgroundExecutor;
+
+ private Context mContext;
+ protected LinearLayout mMediaNotifView;
+ private View mSeamless;
+ private MediaSession.Token mToken;
+ private MediaController mController;
+ private int mForegroundColor;
+ private int mBackgroundColor;
+ protected ComponentName mRecvComponent;
+
+ private final int[] mActionIds;
+
+ // Button IDs used in notifications
+ protected static final int[] NOTIF_ACTION_IDS = {
+ com.android.internal.R.id.action0,
+ com.android.internal.R.id.action1,
+ com.android.internal.R.id.action2,
+ com.android.internal.R.id.action3,
+ com.android.internal.R.id.action4
+ };
+
+ private MediaController.Callback mSessionCallback = new MediaController.Callback() {
+ @Override
+ public void onSessionDestroyed() {
+ Log.d(TAG, "session destroyed");
+ mController.unregisterCallback(mSessionCallback);
+ clearControls();
+ }
+ };
+
+ /**
+ * Initialize a new control panel
+ * @param context
+ * @param parent
+ * @param manager
+ * @param layoutId layout resource to use for this control panel
+ * @param actionIds resource IDs for action buttons in the layout
+ * @param backgroundExecutor background executor, used for processing artwork
+ */
+ public MediaControlPanel(Context context, ViewGroup parent, NotificationMediaManager manager,
+ @LayoutRes int layoutId, int[] actionIds, Executor backgroundExecutor) {
+ mContext = context;
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ mMediaNotifView = (LinearLayout) inflater.inflate(layoutId, parent, false);
+ mMediaManager = manager;
+ mActionIds = actionIds;
+ mBackgroundExecutor = backgroundExecutor;
+ }
+
+ /**
+ * Get the view used to display media controls
+ * @return the view
+ */
+ public View getView() {
+ return mMediaNotifView;
+ }
+
+ /**
+ * Get the context
+ * @return context
+ */
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Update the media panel view for the given media session
+ * @param token
+ * @param icon
+ * @param iconColor
+ * @param bgColor
+ * @param contentIntent
+ * @param appNameString
+ * @param device
+ */
+ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
+ int bgColor, PendingIntent contentIntent, String appNameString, MediaDevice device) {
+ mToken = token;
+ mForegroundColor = iconColor;
+ mBackgroundColor = bgColor;
+ mController = new MediaController(mContext, mToken);
+
+ MediaMetadata mediaMetadata = mController.getMetadata();
+
+ // Try to find a receiver for the media button that matches this app
+ PackageManager pm = mContext.getPackageManager();
+ Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
+ if (info != null) {
+ for (ResolveInfo inf : info) {
+ if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
+ mRecvComponent = inf.getComponentInfo().getComponentName();
+ }
+ }
+ }
+
+ mController.registerCallback(mSessionCallback);
+
+ if (mediaMetadata == null) {
+ Log.e(TAG, "Media metadata was null");
+ return;
+ }
+
+ ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
+ if (albumView != null) {
+ // Resize art in a background thread
+ mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView));
+ }
+ mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
+
+ // Click action
+ mMediaNotifView.setOnClickListener(v -> {
+ try {
+ contentIntent.send();
+ // Also close shade
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Pending intent was canceled", e);
+ }
+ });
+
+ // App icon
+ ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
+ Drawable iconDrawable = icon.loadDrawable(mContext);
+ iconDrawable.setTint(mForegroundColor);
+ appIcon.setImageDrawable(iconDrawable);
+
+ // Song name
+ TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
+ String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+ titleText.setText(songName);
+ titleText.setTextColor(mForegroundColor);
+
+ // Not in mini player:
+ // App title
+ TextView appName = mMediaNotifView.findViewById(R.id.app_name);
+ if (appName != null) {
+ appName.setText(appNameString);
+ appName.setTextColor(mForegroundColor);
+ }
+
+ // Artist name
+ TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
+ if (artistText != null) {
+ String artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+ artistText.setText(artistName);
+ artistText.setTextColor(mForegroundColor);
+ }
+
+ // Transfer chip
+ mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
+ if (mSeamless != null) {
+ mSeamless.setVisibility(View.VISIBLE);
+ updateDevice(device);
+ ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
+ mSeamless.setOnClickListener(v -> {
+ final Intent intent = new Intent()
+ .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
+ .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
+ mController.getPackageName())
+ .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
+ mActivityStarter.startActivity(intent, false, true /* dismissShade */,
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ });
+ }
+
+ // Ensure is only added once
+ mMediaManager.removeCallback(this);
+ mMediaManager.addCallback(this);
+ }
+
+ /**
+ * Return the token for the current media session
+ * @return the token
+ */
+ public MediaSession.Token getMediaSessionToken() {
+ return mToken;
+ }
+
+ /**
+ * Get the current media controller
+ * @return the controller
+ */
+ public MediaController getController() {
+ return mController;
+ }
+
+ /**
+ * Get the name of the package associated with the current media controller
+ * @return the package name
+ */
+ public String getMediaPlayerPackage() {
+ return mController.getPackageName();
+ }
+
+ /**
+ * Check whether this player has an attached media session.
+ * @return whether there is a controller with a current media session.
+ */
+ public boolean hasMediaSession() {
+ return mController != null && mController.getPlaybackState() != null;
+ }
+
+ /**
+ * Check whether the media controlled by this player is currently playing
+ * @return whether it is playing, or false if no controller information
+ */
+ public boolean isPlaying() {
+ return isPlaying(mController);
+ }
+
+ /**
+ * Check whether the given controller is currently playing
+ * @param controller media controller to check
+ * @return whether it is playing, or false if no controller information
+ */
+ protected boolean isPlaying(MediaController controller) {
+ if (controller == null) {
+ return false;
+ }
+
+ PlaybackState state = controller.getPlaybackState();
+ if (state == null) {
+ return false;
+ }
+
+ return (state.getState() == PlaybackState.STATE_PLAYING);
+ }
+
+ /**
+ * Process album art for layout
+ * @param metadata media metadata
+ * @param albumView view to hold the album art
+ */
+ private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
+ Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
+ RoundedBitmapDrawable roundedDrawable = null;
+ if (albumArt != null) {
+ Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
+ int albumSize = (int) mContext.getResources().getDimension(
+ R.dimen.qs_media_album_size);
+ Bitmap scaled = Bitmap.createScaledBitmap(original, albumSize, albumSize, false);
+ roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
+ roundedDrawable.setCornerRadius(radius);
+ } else {
+ Log.e(TAG, "No album art available");
+ }
+
+ // Now that it's resized, update the UI
+ final RoundedBitmapDrawable result = roundedDrawable;
+ albumView.getHandler().post(() -> {
+ if (result != null) {
+ albumView.setImageDrawable(result);
+ albumView.setVisibility(View.VISIBLE);
+ } else {
+ albumView.setImageDrawable(null);
+ albumView.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ /**
+ * Update the current device information
+ * @param device device information to display
+ */
+ public void updateDevice(MediaDevice device) {
+ if (mSeamless == null) {
+ return;
+ }
+ Handler handler = mSeamless.getHandler();
+ handler.post(() -> {
+ updateChipInternal(device);
+ });
+ }
+
+ private void updateChipInternal(MediaDevice device) {
+ ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor);
+
+ // Update the outline color
+ LinearLayout viewLayout = (LinearLayout) mSeamless;
+ RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
+ GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
+ rect.setStroke(2, mForegroundColor);
+ rect.setColor(mBackgroundColor);
+
+ ImageView iconView = mSeamless.findViewById(R.id.media_seamless_image);
+ TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text);
+ deviceName.setTextColor(fgTintList);
+
+ if (device != null) {
+ Drawable icon = device.getIcon();
+ iconView.setVisibility(View.VISIBLE);
+ iconView.setImageTintList(fgTintList);
+
+ if (icon instanceof AdaptiveIcon) {
+ AdaptiveIcon aIcon = (AdaptiveIcon) icon;
+ aIcon.setBackgroundColor(mBackgroundColor);
+ iconView.setImageDrawable(aIcon);
+ } else {
+ iconView.setImageDrawable(icon);
+ }
+ deviceName.setText(device.getName());
+ } else {
+ // Reset to default
+ iconView.setVisibility(View.GONE);
+ deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
+ }
+ }
+
+ /**
+ * Put controls into a resumption state
+ */
+ public void clearControls() {
+ // Hide all the old buttons
+ for (int i = 0; i < mActionIds.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
+ if (thisBtn != null) {
+ thisBtn.setVisibility(View.GONE);
+ }
+ }
+
+ // Add a restart button
+ ImageButton btn = mMediaNotifView.findViewById(mActionIds[0]);
+ btn.setOnClickListener(v -> {
+ Log.d(TAG, "Attempting to restart session");
+ // Send a media button event to previously found receiver
+ if (mRecvComponent != null) {
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ intent.setComponent(mRecvComponent);
+ int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
+ intent.putExtra(
+ Intent.EXTRA_KEY_EVENT,
+ new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+ mContext.sendBroadcast(intent);
+ } else {
+ Log.d(TAG, "No receiver to restart");
+ // If we don't have a receiver, try relaunching the activity instead
+ try {
+ mController.getSessionActivity().send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Pending intent was canceled", e);
+ }
+ }
+ });
+ btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
+ btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
+ btn.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onMetadataOrStateChanged(MediaMetadata metadata, int state) {
+ if (state == PlaybackState.STATE_NONE) {
+ clearControls();
+ mMediaManager.removeCallback(this);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index 36b5fad..67802bc 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -37,7 +37,6 @@
private static final float FRACTION_START = 0f;
private static final float FRACTION_END = 1f;
- public static final int DURATION_NONE = 0;
public static final int DURATION_DEFAULT_MS = 425;
public static final int ANIM_TYPE_BOUNDS = 0;
public static final int ANIM_TYPE_ALPHA = 1;
@@ -49,6 +48,20 @@
@Retention(RetentionPolicy.SOURCE)
public @interface AnimationType {}
+ static final int TRANSITION_DIRECTION_NONE = 0;
+ static final int TRANSITION_DIRECTION_SAME = 1;
+ static final int TRANSITION_DIRECTION_TO_PIP = 2;
+ static final int TRANSITION_DIRECTION_TO_FULLSCREEN = 3;
+
+ @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
+ TRANSITION_DIRECTION_NONE,
+ TRANSITION_DIRECTION_SAME,
+ TRANSITION_DIRECTION_TO_PIP,
+ TRANSITION_DIRECTION_TO_FULLSCREEN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface TransitionDirection {}
+
private final Interpolator mFastOutSlowInInterpolator;
private PipTransitionAnimator mCurrentAnimator;
@@ -58,30 +71,28 @@
com.android.internal.R.interpolator.fast_out_slow_in);
}
- PipTransitionAnimator getAnimator(SurfaceControl leash, boolean scheduleFinishPip,
+ @SuppressWarnings("unchecked")
+ PipTransitionAnimator getAnimator(SurfaceControl leash,
Rect destinationBounds, float alphaStart, float alphaEnd) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofAlpha(leash, scheduleFinishPip, destinationBounds,
- alphaStart, alphaEnd));
+ PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
&& mCurrentAnimator.isRunning()) {
mCurrentAnimator.updateEndValue(alphaEnd);
} else {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofAlpha(leash, scheduleFinishPip, destinationBounds,
- alphaStart, alphaEnd));
+ PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
}
return mCurrentAnimator;
}
- PipTransitionAnimator getAnimator(SurfaceControl leash, boolean scheduleFinishPip,
- Rect startBounds, Rect endBounds) {
+ @SuppressWarnings("unchecked")
+ PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, scheduleFinishPip,
- startBounds, endBounds));
+ PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_BOUNDS
&& mCurrentAnimator.isRunning()) {
mCurrentAnimator.setDestinationBounds(endBounds);
@@ -90,8 +101,7 @@
} else {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, scheduleFinishPip,
- startBounds, endBounds));
+ PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
}
return mCurrentAnimator;
}
@@ -134,7 +144,6 @@
public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
ValueAnimator.AnimatorUpdateListener,
ValueAnimator.AnimatorListener {
- private final boolean mScheduleFinishPip;
private final SurfaceControl mLeash;
private final @AnimationType int mAnimationType;
private final Rect mDestinationBounds = new Rect();
@@ -144,11 +153,11 @@
private T mCurrentValue;
private PipAnimationCallback mPipAnimationCallback;
private SurfaceControlTransactionFactory mSurfaceControlTransactionFactory;
+ private @TransitionDirection int mTransitionDirection;
+ private int mCornerRadius;
- private PipTransitionAnimator(SurfaceControl leash, boolean scheduleFinishPip,
- @AnimationType int animationType, Rect destinationBounds,
- T startValue, T endValue) {
- mScheduleFinishPip = scheduleFinishPip;
+ private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType,
+ Rect destinationBounds, T startValue, T endValue) {
mLeash = leash;
mAnimationType = animationType;
mDestinationBounds.set(destinationBounds);
@@ -157,6 +166,7 @@
addListener(this);
addUpdateListener(this);
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
+ mTransitionDirection = TRANSITION_DIRECTION_NONE;
}
@Override
@@ -202,8 +212,15 @@
return this;
}
- boolean shouldScheduleFinishPip() {
- return mScheduleFinishPip;
+ @TransitionDirection int getTransitionDirection() {
+ return mTransitionDirection;
+ }
+
+ PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) {
+ if (direction != TRANSITION_DIRECTION_SAME) {
+ mTransitionDirection = direction;
+ }
+ return this;
}
T getStartValue() {
@@ -226,6 +243,19 @@
mCurrentValue = value;
}
+ int getCornerRadius() {
+ return mCornerRadius;
+ }
+
+ PipTransitionAnimator<T> setCornerRadius(int cornerRadius) {
+ mCornerRadius = cornerRadius;
+ return this;
+ }
+
+ boolean shouldApplyCornerRadius() {
+ return mTransitionDirection != TRANSITION_DIRECTION_TO_FULLSCREEN;
+ }
+
/**
* Updates the {@link #mEndValue}.
*
@@ -251,9 +281,9 @@
abstract void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction);
- static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash, boolean scheduleFinishPip,
+ static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash,
Rect destinationBounds, float startValue, float endValue) {
- return new PipTransitionAnimator<Float>(leash, scheduleFinishPip, ANIM_TYPE_ALPHA,
+ return new PipTransitionAnimator<Float>(leash, ANIM_TYPE_ALPHA,
destinationBounds, startValue, endValue) {
@Override
void applySurfaceControlTransaction(SurfaceControl leash,
@@ -266,16 +296,18 @@
final Rect bounds = getDestinationBounds();
tx.setPosition(leash, bounds.left, bounds.top)
.setWindowCrop(leash, bounds.width(), bounds.height());
+ tx.setCornerRadius(leash,
+ shouldApplyCornerRadius() ? getCornerRadius() : 0);
}
tx.apply();
}
};
}
- static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash, boolean scheduleFinishPip,
+ static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
Rect startValue, Rect endValue) {
// construct new Rect instances in case they are recycled
- return new PipTransitionAnimator<Rect>(leash, scheduleFinishPip, ANIM_TYPE_BOUNDS,
+ return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
endValue, new Rect(startValue), new Rect(endValue)) {
private final Rect mTmpRect = new Rect();
@@ -299,6 +331,8 @@
if (Float.compare(fraction, FRACTION_START) == 0) {
// Ensure the start condition
tx.setAlpha(leash, 1f);
+ tx.setCornerRadius(leash,
+ shouldApplyCornerRadius() ? getCornerRadius() : 0);
}
tx.apply();
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 4766ebc..3933af0 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -19,6 +19,10 @@
import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
import static com.android.systemui.pip.PipAnimationController.DURATION_DEFAULT_MS;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,7 +34,6 @@
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.DisplayInfo;
@@ -40,6 +43,7 @@
import android.view.WindowContainerTransaction;
import com.android.internal.os.SomeArgs;
+import com.android.systemui.R;
import com.android.systemui.pip.phone.PipUpdateThread;
import java.util.ArrayList;
@@ -74,6 +78,7 @@
private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
private final Rect mDisplayBounds = new Rect();
private final Rect mLastReportedBounds = new Rect();
+ private final int mCornerRadius;
// These callbacks are called on the update thread
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
@@ -97,7 +102,7 @@
callback.onPipTransitionFinished();
}
});
- finishResize(animator.getDestinationBounds(), tx, animator.shouldScheduleFinishPip());
+ finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection());
}
@Override
@@ -111,57 +116,53 @@
}
};
- private Handler.Callback mUpdateCallbacks = new Handler.Callback() {
- @Override
- public boolean handleMessage(Message msg) {
- SomeArgs args = (SomeArgs) msg.obj;
- Consumer<Rect> updateBoundsCallback = (Consumer<Rect>) args.arg1;
- switch (msg.what) {
- case MSG_RESIZE_IMMEDIATE: {
- Rect toBounds = (Rect) args.arg2;
- resizePip(toBounds);
- if (updateBoundsCallback != null) {
- updateBoundsCallback.accept(toBounds);
- }
- break;
+ @SuppressWarnings("unchecked")
+ private Handler.Callback mUpdateCallbacks = (msg) -> {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Consumer<Rect> updateBoundsCallback = (Consumer<Rect>) args.arg1;
+ switch (msg.what) {
+ case MSG_RESIZE_IMMEDIATE: {
+ Rect toBounds = (Rect) args.arg2;
+ resizePip(toBounds);
+ if (updateBoundsCallback != null) {
+ updateBoundsCallback.accept(toBounds);
}
- case MSG_RESIZE_ANIMATE: {
- Rect currentBounds = (Rect) args.arg2;
- Rect toBounds = (Rect) args.arg3;
- boolean scheduleFinishPip = args.argi1 != 0;
- int duration = args.argi2;
- animateResizePip(scheduleFinishPip, currentBounds, toBounds, duration);
- if (updateBoundsCallback != null) {
- updateBoundsCallback.accept(toBounds);
- }
- break;
- }
- case MSG_OFFSET_ANIMATE: {
- Rect originalBounds = (Rect) args.arg2;
- final int offset = args.argi1;
- final int duration = args.argi2;
- offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
- Rect toBounds = new Rect(originalBounds);
- toBounds.offset(0, offset);
- if (updateBoundsCallback != null) {
- updateBoundsCallback.accept(toBounds);
- }
- break;
- }
- case MSG_FINISH_RESIZE: {
- SurfaceControl.Transaction tx = (SurfaceControl.Transaction) args.arg2;
- Rect toBounds = (Rect) args.arg3;
- boolean scheduleFinishPip = args.argi1 != 0;
- finishResize(toBounds, tx, scheduleFinishPip);
- if (updateBoundsCallback != null) {
- updateBoundsCallback.accept(toBounds);
- }
- break;
- }
+ break;
}
- args.recycle();
- return true;
+ case MSG_RESIZE_ANIMATE: {
+ Rect currentBounds = (Rect) args.arg2;
+ Rect toBounds = (Rect) args.arg3;
+ int duration = args.argi2;
+ animateResizePip(currentBounds, toBounds, args.argi1 /* direction */, duration);
+ if (updateBoundsCallback != null) {
+ updateBoundsCallback.accept(toBounds);
+ }
+ break;
+ }
+ case MSG_OFFSET_ANIMATE: {
+ Rect originalBounds = (Rect) args.arg2;
+ final int offset = args.argi1;
+ final int duration = args.argi2;
+ offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
+ Rect toBounds = new Rect(originalBounds);
+ toBounds.offset(0, offset);
+ if (updateBoundsCallback != null) {
+ updateBoundsCallback.accept(toBounds);
+ }
+ break;
+ }
+ case MSG_FINISH_RESIZE: {
+ SurfaceControl.Transaction tx = (SurfaceControl.Transaction) args.arg2;
+ Rect toBounds = (Rect) args.arg3;
+ finishResize(tx, toBounds, args.argi1 /* direction */);
+ if (updateBoundsCallback != null) {
+ updateBoundsCallback.accept(toBounds);
+ }
+ break;
+ }
}
+ args.recycle();
+ return true;
};
private ActivityManager.RunningTaskInfo mTaskInfo;
@@ -176,6 +177,7 @@
mTaskOrganizerController = ActivityTaskManager.getTaskOrganizerController();
mPipBoundsHandler = boundsHandler;
mPipAnimationController = new PipAnimationController(context);
+ mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
}
public Handler getUpdateHandler() {
@@ -191,7 +193,8 @@
/**
* Sets the preferred animation type for one time.
- * This is typically used to set the animation type to {@link #ANIM_TYPE_ALPHA}.
+ * This is typically used to set the animation type to
+ * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
*/
public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
mOneShotAnimationType = animationType;
@@ -200,13 +203,14 @@
/**
* Updates the display dimension with given {@link DisplayInfo}
*/
+ @SuppressWarnings("unchecked")
public void onDisplayInfoChanged(DisplayInfo displayInfo) {
final Rect newDisplayBounds = new Rect(0, 0,
displayInfo.logicalWidth, displayInfo.logicalHeight);
if (!mDisplayBounds.equals(newDisplayBounds)) {
// Updates the exiting PiP animation in case the screen rotation changes in the middle.
// It's a legit case that PiP window is in portrait mode on home screen and
- // the application requests landscape onces back to fullscreen mode.
+ // the application requests landscape once back to fullscreen mode.
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
if (animator != null
@@ -250,12 +254,13 @@
}
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
- scheduleAnimateResizePip(true /* scheduleFinishPip */,
- currentBounds, destinationBounds, DURATION_DEFAULT_MS, null);
+ scheduleAnimateResizePip(currentBounds, destinationBounds,
+ TRANSITION_DIRECTION_TO_PIP, DURATION_DEFAULT_MS, null);
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
mUpdateHandler.post(() -> mPipAnimationController
- .getAnimator(mLeash, true /* scheduleFinishPip */,
- destinationBounds, 0f, 1f)
+ .getAnimator(mLeash, destinationBounds, 0f, 1f)
+ .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
+ .setCornerRadius(mCornerRadius)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(DURATION_DEFAULT_MS)
.start());
@@ -272,7 +277,8 @@
Log.wtf(TAG, "Unrecognized token: " + token);
return;
}
- scheduleAnimateResizePip(mDisplayBounds, DURATION_DEFAULT_MS, null);
+ scheduleAnimateResizePip(mLastReportedBounds, mDisplayBounds,
+ TRANSITION_DIRECTION_TO_FULLSCREEN, DURATION_DEFAULT_MS, null);
mInPip = false;
}
@@ -310,12 +316,12 @@
*/
public void scheduleAnimateResizePip(Rect toBounds, int duration,
Consumer<Rect> updateBoundsCallback) {
- scheduleAnimateResizePip(false /* scheduleFinishPip */,
- mLastReportedBounds, toBounds, duration, updateBoundsCallback);
+ scheduleAnimateResizePip(mLastReportedBounds, toBounds,
+ TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback);
}
- private void scheduleAnimateResizePip(boolean scheduleFinishPip,
- Rect currentBounds, Rect destinationBounds, int durationMs,
+ private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds,
+ @PipAnimationController.TransitionDirection int direction, int durationMs,
Consumer<Rect> updateBoundsCallback) {
Objects.requireNonNull(mToken, "Requires valid IWindowContainer");
if (!mInPip) {
@@ -326,7 +332,7 @@
args.arg1 = updateBoundsCallback;
args.arg2 = currentBounds;
args.arg3 = destinationBounds;
- args.argi1 = scheduleFinishPip ? 1 : 0;
+ args.argi1 = direction;
args.argi2 = durationMs;
mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args));
}
@@ -351,25 +357,25 @@
Objects.requireNonNull(mToken, "Requires valid IWindowContainer");
SurfaceControl.Transaction tx = new SurfaceControl.Transaction()
.setPosition(mLeash, destinationBounds.left, destinationBounds.top)
- .setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
- scheduleFinishResizePip(tx, destinationBounds, false /* scheduleFinishPip */,
- null);
+ .setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height())
+ .setCornerRadius(mLeash, mInPip ? mCornerRadius : 0);
+ scheduleFinishResizePip(tx, destinationBounds, TRANSITION_DIRECTION_NONE, null);
}
private void scheduleFinishResizePip(SurfaceControl.Transaction tx,
- Rect destinationBounds, boolean scheduleFinishPip,
+ Rect destinationBounds, @PipAnimationController.TransitionDirection int direction,
Consumer<Rect> updateBoundsCallback) {
Objects.requireNonNull(mToken, "Requires valid IWindowContainer");
SomeArgs args = SomeArgs.obtain();
args.arg1 = updateBoundsCallback;
args.arg2 = tx;
args.arg3 = destinationBounds;
- args.argi1 = scheduleFinishPip ? 1 : 0;
+ args.argi1 = direction;
mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_FINISH_RESIZE, args));
}
/**
- * Offset the PiP window, animate if the given duration is not {@link #DURATION_NONE}
+ * Offset the PiP window by a given offset on Y-axis, triggered also from screen rotation.
*/
public void scheduleOffsetPip(Rect originalBounds, int offset, int duration,
Consumer<Rect> updateBoundsCallback) {
@@ -398,8 +404,7 @@
}
final Rect destinationBounds = new Rect(originalBounds);
destinationBounds.offset(xOffset, yOffset);
- animateResizePip(false /* scheduleFinishPip*/, originalBounds, destinationBounds,
- durationMs);
+ animateResizePip(originalBounds, destinationBounds, TRANSITION_DIRECTION_SAME, durationMs);
}
private void resizePip(Rect destinationBounds) {
@@ -416,11 +421,12 @@
new SurfaceControl.Transaction()
.setPosition(mLeash, destinationBounds.left, destinationBounds.top)
.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height())
+ .setCornerRadius(mLeash, mInPip ? mCornerRadius : 0)
.apply();
}
- private void finishResize(Rect destinationBounds, SurfaceControl.Transaction tx,
- boolean shouldScheduleFinishPip) {
+ private void finishResize(SurfaceControl.Transaction tx, Rect destinationBounds,
+ @PipAnimationController.TransitionDirection int direction) {
if (Looper.myLooper() != mUpdateHandler.getLooper()) {
throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
+ "directly");
@@ -428,7 +434,7 @@
mLastReportedBounds.set(destinationBounds);
try {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (shouldScheduleFinishPip) {
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
wct.scheduleFinishEnterPip(mToken, destinationBounds);
} else {
wct.setBounds(mToken, destinationBounds);
@@ -440,8 +446,8 @@
}
}
- private void animateResizePip(boolean scheduleFinishPip, Rect currentBounds,
- Rect destinationBounds, int durationMs) {
+ private void animateResizePip(Rect currentBounds, Rect destinationBounds,
+ @PipAnimationController.TransitionDirection int direction, int durationMs) {
if (Looper.myLooper() != mUpdateHandler.getLooper()) {
throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of "
+ "this directly");
@@ -452,7 +458,9 @@
return;
}
mUpdateHandler.post(() -> mPipAnimationController
- .getAnimator(mLeash, scheduleFinishPip, currentBounds, destinationBounds)
+ .getAnimator(mLeash, currentBounds, destinationBounds)
+ .setTransitionDirection(direction)
+ .setCornerRadius(mCornerRadius)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(durationMs)
.start());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index 011893d..837256b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -17,158 +17,53 @@
package com.android.systemui.qs;
import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
-import android.graphics.drawable.RippleDrawable;
-import android.media.MediaMetadata;
-import android.media.session.MediaController;
import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.os.Handler;
import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.android.settingslib.media.MediaDevice;
-import com.android.settingslib.media.MediaOutputSliceConstants;
-import com.android.settingslib.widget.AdaptiveIcon;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.media.MediaControlPanel;
+import com.android.systemui.statusbar.NotificationMediaManager;
-import java.util.List;
+import java.util.concurrent.Executor;
/**
* Single media player for carousel in QSPanel
*/
-public class QSMediaPlayer {
+public class QSMediaPlayer extends MediaControlPanel {
private static final String TAG = "QSMediaPlayer";
- private Context mContext;
- private LinearLayout mMediaNotifView;
- private View mSeamless;
- private MediaSession.Token mToken;
- private MediaController mController;
- private int mForegroundColor;
- private int mBackgroundColor;
- private ComponentName mRecvComponent;
- private QSPanel mParent;
-
- private MediaController.Callback mSessionCallback = new MediaController.Callback() {
- @Override
- public void onSessionDestroyed() {
- Log.d(TAG, "session destroyed");
- mController.unregisterCallback(mSessionCallback);
-
- // Hide all the old buttons
- final int[] actionIds = {
- R.id.action0,
- R.id.action1,
- R.id.action2,
- R.id.action3,
- R.id.action4
- };
- for (int i = 0; i < actionIds.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
- if (thisBtn != null) {
- thisBtn.setVisibility(View.GONE);
- }
- }
-
- // Add a restart button
- ImageButton btn = mMediaNotifView.findViewById(actionIds[0]);
- btn.setOnClickListener(v -> {
- Log.d(TAG, "Attempting to restart session");
- // Send a media button event to previously found receiver
- if (mRecvComponent != null) {
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- intent.setComponent(mRecvComponent);
- int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
- intent.putExtra(
- Intent.EXTRA_KEY_EVENT,
- new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
- mContext.sendBroadcast(intent);
- } else {
- Log.d(TAG, "No receiver to restart");
- // If we don't have a receiver, try relaunching the activity instead
- try {
- mController.getSessionActivity().send();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent was canceled");
- e.printStackTrace();
- }
- }
- });
- btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
- btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
- btn.setVisibility(View.VISIBLE);
-
- // Add long-click option to remove the player
- ViewGroup mMediaCarousel = (ViewGroup) mMediaNotifView.getParent();
- mMediaNotifView.setOnLongClickListener(v -> {
- // Replace player view with delete/cancel view
- v.setVisibility(View.GONE);
-
- View options = LayoutInflater.from(mContext).inflate(
- R.layout.qs_media_panel_options, null, false);
- ImageButton btnDelete = options.findViewById(R.id.remove);
- btnDelete.setOnClickListener(b -> {
- mMediaCarousel.removeView(options);
- mParent.removeMediaPlayer(QSMediaPlayer.this);
- });
- ImageButton btnCancel = options.findViewById(R.id.cancel);
- btnCancel.setOnClickListener(b -> {
- mMediaCarousel.removeView(options);
- v.setVisibility(View.VISIBLE);
- });
-
- int pos = mMediaCarousel.indexOfChild(v);
- mMediaCarousel.addView(options, pos, v.getLayoutParams());
- return true; // consumed click
- });
- }
+ // Button IDs for QS controls
+ static final int[] QS_ACTION_IDS = {
+ R.id.action0,
+ R.id.action1,
+ R.id.action2,
+ R.id.action3,
+ R.id.action4
};
/**
- *
+ * Initialize quick shade version of player
* @param context
* @param parent
+ * @param manager
+ * @param backgroundExecutor
*/
- public QSMediaPlayer(Context context, ViewGroup parent) {
- mContext = context;
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mMediaNotifView = (LinearLayout) inflater.inflate(R.layout.qs_media_panel, parent, false);
- }
-
- public View getView() {
- return mMediaNotifView;
+ public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager,
+ Executor backgroundExecutor) {
+ super(context, parent, manager, R.layout.qs_media_panel, QS_ACTION_IDS, backgroundExecutor);
}
/**
- * Create or update the player view for the given media session
- * @param parent the parent QSPanel
+ * Update media panel view for the given media session
* @param token token for this media session
* @param icon app notification icon
* @param iconColor foreground color (for text, icons)
@@ -177,114 +72,20 @@
* @param notif reference to original notification
* @param device current playback device
*/
- public void setMediaSession(QSPanel parent, MediaSession.Token token, Icon icon, int iconColor,
+ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
int bgColor, View actionsContainer, Notification notif, MediaDevice device) {
- mParent = parent;
- mToken = token;
- mForegroundColor = iconColor;
- mBackgroundColor = bgColor;
- mController = new MediaController(mContext, token);
- // Try to find a receiver for the media button that matches this app
- PackageManager pm = mContext.getPackageManager();
- Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
- List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
- if (info != null) {
- for (ResolveInfo inf : info) {
- if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
- mRecvComponent = inf.getComponentInfo().getComponentName();
- }
- }
- }
-
- // reset in case we had previously restarted the stream
- mMediaNotifView.setOnLongClickListener(null);
- mController.registerCallback(mSessionCallback);
- MediaMetadata mMediaMetadata = mController.getMetadata();
- if (mMediaMetadata == null) {
- Log.e(TAG, "Media metadata was null");
- return;
- }
-
- Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif);
-
- // Album art
- addAlbumArt(mMediaMetadata, bgColor);
-
- LinearLayout headerView = mMediaNotifView.findViewById(R.id.header);
-
- // App icon
- ImageView appIcon = headerView.findViewById(R.id.icon);
- Drawable iconDrawable = icon.loadDrawable(mContext);
- iconDrawable.setTint(iconColor);
- appIcon.setImageDrawable(iconDrawable);
-
- // App title
- TextView appName = headerView.findViewById(R.id.app_name);
- String appNameString = builder.loadHeaderAppName();
- appName.setText(appNameString);
- appName.setTextColor(iconColor);
-
- // Action
- mMediaNotifView.setOnClickListener(v -> {
- try {
- notif.contentIntent.send();
- // Also close shade
- mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent was canceled");
- e.printStackTrace();
- }
- });
-
- // Transfer chip
- mSeamless = headerView.findViewById(R.id.media_seamless);
- mSeamless.setVisibility(View.VISIBLE);
- updateChip(device);
- ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
- mSeamless.setOnClickListener(v -> {
- final Intent intent = new Intent()
- .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
- .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
- mController.getPackageName())
- .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, token);
- mActivityStarter.startActivity(intent, false, true /* dismissShade */,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- });
-
- // Artist name
- TextView artistText = headerView.findViewById(R.id.header_artist);
- String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
- artistText.setText(artistName);
- artistText.setTextColor(iconColor);
-
- // Song name
- TextView titleText = headerView.findViewById(R.id.header_text);
- String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
- titleText.setText(songName);
- titleText.setTextColor(iconColor);
+ String appName = Notification.Builder.recoverBuilder(getContext(), notif)
+ .loadHeaderAppName();
+ super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent,
+ appName, device);
// Media controls
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
- final int[] actionIds = {
- R.id.action0,
- R.id.action1,
- R.id.action2,
- R.id.action3,
- R.id.action4
- };
- final int[] notifActionIds = {
- com.android.internal.R.id.action0,
- com.android.internal.R.id.action1,
- com.android.internal.R.id.action2,
- com.android.internal.R.id.action3,
- com.android.internal.R.id.action4
- };
-
int i = 0;
- for (; i < parentActionsLayout.getChildCount() && i < actionIds.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
- ImageButton thatBtn = parentActionsLayout.findViewById(notifActionIds[i]);
+ for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
+ ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
if (thatBtn == null || thatBtn.getDrawable() == null
|| thatBtn.getVisibility() != View.VISIBLE) {
thisBtn.setVisibility(View.GONE);
@@ -301,116 +102,9 @@
}
// Hide any unused buttons
- for (; i < actionIds.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
+ for (; i < QS_ACTION_IDS.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
thisBtn.setVisibility(View.GONE);
}
}
-
- public MediaSession.Token getMediaSessionToken() {
- return mToken;
- }
-
- public String getMediaPlayerPackage() {
- return mController.getPackageName();
- }
-
- /**
- * Check whether the media controlled by this player is currently playing
- * @return whether it is playing, or false if no controller information
- */
- public boolean isPlaying() {
- if (mController == null) {
- return false;
- }
-
- PlaybackState state = mController.getPlaybackState();
- if (state == null) {
- return false;
- }
-
- return (state.getState() == PlaybackState.STATE_PLAYING);
- }
-
- private void addAlbumArt(MediaMetadata metadata, int bgColor) {
- Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
- ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
- if (albumArt != null) {
- Log.d(TAG, "updating album art");
- Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
- int albumSize = (int) mContext.getResources().getDimension(R.dimen.qs_media_album_size);
- Bitmap scaled = scaleBitmap(original, albumSize, albumSize);
- RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
- mContext.getResources(), scaled);
- roundedDrawable.setCornerRadius(radius);
- albumView.setImageDrawable(roundedDrawable);
- } else {
- Log.e(TAG, "No album art available");
- albumView.setImageDrawable(null);
- }
-
- mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(bgColor));
- }
-
- private Bitmap scaleBitmap(Bitmap original, int width, int height) {
- Bitmap cropped = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(cropped);
-
- float scale = (float) cropped.getWidth() / (float) original.getWidth();
- float dy = (cropped.getHeight() - original.getHeight() * scale) / 2.0f;
- Matrix transformation = new Matrix();
- transformation.postTranslate(0, dy);
- transformation.preScale(scale, scale);
-
- Paint paint = new Paint();
- paint.setFilterBitmap(true);
- canvas.drawBitmap(original, transformation, paint);
-
- return cropped;
- }
-
- protected void updateChip(MediaDevice device) {
- if (mSeamless == null) {
- return;
- }
- Handler handler = mSeamless.getHandler();
- handler.post(() -> {
- updateChipInternal(device);
- });
- }
-
- private void updateChipInternal(MediaDevice device) {
- ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor);
-
- // Update the outline color
- LinearLayout viewLayout = (LinearLayout) mSeamless;
- RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
- GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
- rect.setStroke(2, mForegroundColor);
- rect.setColor(mBackgroundColor);
-
- ImageView iconView = mSeamless.findViewById(R.id.media_seamless_image);
- TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text);
- deviceName.setTextColor(fgTintList);
-
- if (device != null) {
- Drawable icon = device.getIcon();
- iconView.setVisibility(View.VISIBLE);
- iconView.setImageTintList(fgTintList);
-
- if (icon instanceof AdaptiveIcon) {
- AdaptiveIcon aIcon = (AdaptiveIcon) icon;
- aIcon.setBackgroundColor(mBackgroundColor);
- iconView.setImageDrawable(aIcon);
- } else {
- iconView.setImageDrawable(icon);
- }
- deviceName.setText(device.getName());
- } else {
- // Reset to default
- iconView.setVisibility(View.GONE);
- deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index d2d9092..9ab4714 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -50,6 +50,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
@@ -60,6 +61,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.BrightnessController;
import com.android.systemui.settings.ToggleSliderView;
+import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
import com.android.systemui.tuner.TunerService;
@@ -70,6 +72,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -94,6 +97,8 @@
private final LinearLayout mMediaCarousel;
private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>();
+ private final NotificationMediaManager mNotificationMediaManager;
+ private final Executor mBackgroundExecutor;
private LocalMediaManager mLocalMediaManager;
private MediaDevice mDevice;
private boolean mUpdateCarousel = false;
@@ -128,7 +133,7 @@
if (mDevice == null || !mDevice.equals(currentDevice)) {
mDevice = currentDevice;
for (QSMediaPlayer p : mMediaPlayers) {
- p.updateChip(mDevice);
+ p.updateDevice(mDevice);
}
}
}
@@ -138,7 +143,7 @@
if (mDevice == null || !mDevice.equals(device)) {
mDevice = device;
for (QSMediaPlayer p : mMediaPlayers) {
- p.updateChip(mDevice);
+ p.updateDevice(mDevice);
}
}
}
@@ -150,12 +155,16 @@
AttributeSet attrs,
DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher,
- QSLogger qsLogger
+ QSLogger qsLogger,
+ NotificationMediaManager notificationMediaManager,
+ @Background Executor backgroundExecutor
) {
super(context, attrs);
mContext = context;
mQSLogger = qsLogger;
mDumpManager = dumpManager;
+ mNotificationMediaManager = notificationMediaManager;
+ mBackgroundExecutor = backgroundExecutor;
setOrientation(VERTICAL);
@@ -255,7 +264,8 @@
if (player == null) {
Log.d(TAG, "creating new player");
- player = new QSMediaPlayer(mContext, this);
+ player = new QSMediaPlayer(mContext, this, mNotificationMediaManager,
+ mBackgroundExecutor);
if (player.isPlaying()) {
mMediaCarousel.addView(player.getView(), 0, lp); // add in front
@@ -268,7 +278,7 @@
}
Log.d(TAG, "setting player session");
- player.setMediaSession(this, token, icon, iconColor, bgColor, actionsContainer,
+ player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
notif.getNotification(), mDevice);
if (mMediaPlayers.size() > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index 9018a37..4512afb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -17,108 +17,47 @@
package com.android.systemui.qs;
import android.app.PendingIntent;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import com.android.systemui.R;
+import com.android.systemui.media.MediaControlPanel;
+import com.android.systemui.statusbar.NotificationMediaManager;
-import java.util.List;
+import java.util.concurrent.Executor;
/**
* QQS mini media player
*/
-public class QuickQSMediaPlayer {
+public class QuickQSMediaPlayer extends MediaControlPanel {
private static final String TAG = "QQSMediaPlayer";
- private Context mContext;
- private LinearLayout mMediaNotifView;
- private MediaSession.Token mToken;
- private MediaController mController;
- private int mForegroundColor;
- private ComponentName mRecvComponent;
-
- private MediaController.Callback mSessionCallback = new MediaController.Callback() {
- @Override
- public void onSessionDestroyed() {
- Log.d(TAG, "session destroyed");
- mController.unregisterCallback(mSessionCallback);
-
- // Hide all the old buttons
- final int[] actionIds = {R.id.action0, R.id.action1, R.id.action2};
- for (int i = 0; i < actionIds.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
- if (thisBtn != null) {
- thisBtn.setVisibility(View.GONE);
- }
- }
-
- // Add a restart button
- ImageButton btn = mMediaNotifView.findViewById(actionIds[0]);
- btn.setOnClickListener(v -> {
- Log.d(TAG, "Attempting to restart session");
- // Send a media button event to previously found receiver
- if (mRecvComponent != null) {
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- intent.setComponent(mRecvComponent);
- int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
- intent.putExtra(
- Intent.EXTRA_KEY_EVENT,
- new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
- mContext.sendBroadcast(intent);
- } else {
- Log.d(TAG, "No receiver to restart");
- // If we don't have a receiver, try relaunching the activity instead
- try {
- mController.getSessionActivity().send();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent was canceled");
- e.printStackTrace();
- }
- }
- });
- btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
- btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
- btn.setVisibility(View.VISIBLE);
- }
- };
+ // Button IDs for QS controls
+ private static final int[] QQS_ACTION_IDS = {R.id.action0, R.id.action1, R.id.action2};
/**
- *
+ * Initialize mini media player for QQS
* @param context
* @param parent
+ * @param manager
+ * @param backgroundExecutor
*/
- public QuickQSMediaPlayer(Context context, ViewGroup parent) {
- mContext = context;
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mMediaNotifView = (LinearLayout) inflater.inflate(R.layout.qqs_media_panel, parent, false);
- }
-
- public View getView() {
- return mMediaNotifView;
+ public QuickQSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager,
+ Executor backgroundExecutor) {
+ super(context, parent, manager, R.layout.qqs_media_panel, QQS_ACTION_IDS,
+ backgroundExecutor);
}
/**
- *
+ * Update media panel view for the given media session
* @param token token for this media session
* @param icon app notification icon
* @param iconColor foreground color (for text, icons)
@@ -130,84 +69,30 @@
*/
public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) {
- mToken = token;
- mForegroundColor = iconColor;
-
+ // Only update if this is a different session and currently playing
String oldPackage = "";
- if (mController != null) {
- oldPackage = mController.getPackageName();
+ if (getController() != null) {
+ oldPackage = getController().getPackageName();
}
- MediaController controller = new MediaController(mContext, token);
- boolean samePlayer = mToken.equals(token) && oldPackage.equals(controller.getPackageName());
- if (mController != null && !samePlayer && !isPlaying(controller)) {
- // Only update if this is a different session and currently playing
- return;
- }
- mController = controller;
- MediaMetadata mMediaMetadata = mController.getMetadata();
-
- // Try to find a receiver for the media button that matches this app
- PackageManager pm = mContext.getPackageManager();
- Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
- List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
- if (info != null) {
- for (ResolveInfo inf : info) {
- if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
- mRecvComponent = inf.getComponentInfo().getComponentName();
- }
- }
- }
- mController.registerCallback(mSessionCallback);
-
- if (mMediaMetadata == null) {
- Log.e(TAG, "Media metadata was null");
+ MediaController controller = new MediaController(getContext(), token);
+ MediaSession.Token currentToken = getMediaSessionToken();
+ boolean samePlayer = currentToken != null
+ && currentToken.equals(token)
+ && oldPackage.equals(controller.getPackageName());
+ if (getController() != null && !samePlayer && !isPlaying(controller)) {
return;
}
- // Action
- mMediaNotifView.setOnClickListener(v -> {
- try {
- contentIntent.send();
- mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent was canceled: " + e.getMessage());
- }
- });
+ super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null, null);
- mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(bgColor));
-
- // App icon
- ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
- Drawable iconDrawable = icon.loadDrawable(mContext);
- iconDrawable.setTint(mForegroundColor);
- appIcon.setImageDrawable(iconDrawable);
-
- // Song name
- TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
- String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
- titleText.setText(songName);
- titleText.setTextColor(mForegroundColor);
-
- // Buttons we can display
- final int[] actionIds = {R.id.action0, R.id.action1, R.id.action2};
-
- // Existing buttons in the notification
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
- final int[] notifActionIds = {
- com.android.internal.R.id.action0,
- com.android.internal.R.id.action1,
- com.android.internal.R.id.action2,
- com.android.internal.R.id.action3,
- com.android.internal.R.id.action4
- };
-
int i = 0;
if (actionsToShow != null) {
int maxButtons = Math.min(actionsToShow.length, parentActionsLayout.getChildCount());
- maxButtons = Math.min(maxButtons, actionIds.length);
+ maxButtons = Math.min(maxButtons, QQS_ACTION_IDS.length);
for (; i < maxButtons; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
- int thatId = notifActionIds[actionsToShow[i]];
+ ImageButton thisBtn = mMediaNotifView.findViewById(QQS_ACTION_IDS[i]);
+ int thatId = NOTIF_ACTION_IDS[actionsToShow[i]];
ImageButton thatBtn = parentActionsLayout.findViewById(thatId);
if (thatBtn == null || thatBtn.getDrawable() == null
|| thatBtn.getVisibility() != View.VISIBLE) {
@@ -225,38 +110,9 @@
}
// Hide any unused buttons
- for (; i < actionIds.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
+ for (; i < QQS_ACTION_IDS.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(QQS_ACTION_IDS[i]);
thisBtn.setVisibility(View.GONE);
}
}
-
- public MediaSession.Token getMediaSessionToken() {
- return mToken;
- }
-
- /**
- * Check whether the media controlled by this player is currently playing
- * @return whether it is playing, or false if no controller information
- */
- public boolean isPlaying(MediaController controller) {
- if (controller == null) {
- return false;
- }
-
- PlaybackState state = controller.getPlaybackState();
- if (state == null) {
- return false;
- }
-
- return (state.getState() == PlaybackState.STATE_PLAYING);
- }
-
- /**
- * Check whether this player has an attached media session.
- * @return whether there is a controller with a current media session.
- */
- public boolean hasMediaSession() {
- return mController != null && mController.getPlaybackState() != null;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 20efbcb..3da767e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -29,18 +29,21 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.Utils;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -72,9 +75,12 @@
AttributeSet attrs,
DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher,
- QSLogger qsLogger
+ QSLogger qsLogger,
+ NotificationMediaManager notificationMediaManager,
+ @Background Executor backgroundExecutor
) {
- super(context, attrs, dumpManager, broadcastDispatcher, qsLogger);
+ super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, notificationMediaManager,
+ backgroundExecutor);
if (mFooter != null) {
removeView(mFooter.getView());
}
@@ -93,7 +99,8 @@
mHorizontalLinearLayout.setClipToPadding(false);
int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
- mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout);
+ mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout,
+ notificationMediaManager, backgroundExecutor);
LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
lp2.setMarginEnd(marginSize);
lp2.setMarginStart(0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index ebac4b2..1b752452 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -165,6 +165,7 @@
private void removeNavigationBar(int displayId) {
NavigationBarFragment navBar = mNavigationBars.get(displayId);
if (navBar != null) {
+ navBar.setAutoHideController(/* autoHideController */ null);
View navigationWindow = navBar.getView().getRootView();
WindowManagerGlobal.getInstance()
.removeView(navigationWindow, true /* immediate */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index 04f1c32..7f30009 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static java.lang.Float.isNaN;
+
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Canvas;
@@ -179,6 +181,9 @@
* @param alpha Gradient alpha from 0 to 1.
*/
public void setViewAlpha(float alpha) {
+ if (isNaN(alpha)) {
+ throw new IllegalArgumentException("alpha cannot be NaN: " + alpha);
+ }
if (alpha != mViewAlpha) {
mViewAlpha = alpha;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
index 62d3612..7f42fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
@@ -164,8 +164,8 @@
// Immediately report current value of setting
updateListener(listener)
val observer = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean, uri: Uri?, userId: Int) {
- super.onChange(selfChange, uri, userId)
+ override fun onChange(selfChange: Boolean, uri: Uri?, flags: Int) {
+ super.onChange(selfChange, uri, flags)
updateListener(listener)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 02cf8cc..b119f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -205,6 +205,28 @@
private final Handler mHandler;
+ private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
+ @Override
+ public void synchronizeState() {
+ checkNavBarModes();
+ }
+
+ @Override
+ public boolean shouldHideOnTouch() {
+ return !mNotificationRemoteInputManager.getController().isRemoteInputActive();
+ }
+
+ @Override
+ public boolean isVisible() {
+ return isTransientShown();
+ }
+
+ @Override
+ public void hide() {
+ clearTransient();
+ }
+ };
+
private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
@Override
public void onConnectionChanged(boolean isConnected) {
@@ -1052,28 +1074,13 @@
/** Sets {@link AutoHideController} to the navigation bar. */
public void setAutoHideController(AutoHideController autoHideController) {
+ if (mAutoHideController != null) {
+ mAutoHideController.removeAutoHideUiElement(mAutoHideUiElement);
+ }
mAutoHideController = autoHideController;
- mAutoHideController.addAutoHideUiElement(new AutoHideUiElement() {
- @Override
- public void synchronizeState() {
- checkNavBarModes();
- }
-
- @Override
- public boolean shouldHideOnTouch() {
- return !mNotificationRemoteInputManager.getController().isRemoteInputActive();
- }
-
- @Override
- public boolean isVisible() {
- return isTransientShown();
- }
-
- @Override
- public void hide() {
- clearTransient();
- }
- });
+ if (mAutoHideController != null) {
+ mAutoHideController.addAutoHideUiElement(mAutoHideUiElement);
+ }
}
private boolean isTransientShown() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 945a9db..d1ff32d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static java.lang.Float.isNaN;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -289,6 +291,10 @@
mInFrontAlpha = state.getFrontAlpha();
mBehindAlpha = state.getBehindAlpha();
mBubbleAlpha = state.getBubbleAlpha();
+ if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) {
+ throw new IllegalStateException("Scrim opacity is NaN for state: " + state + ", front: "
+ + mInFrontAlpha + ", back: " + mBehindAlpha);
+ }
applyExpansionToAlpha();
// Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
@@ -416,6 +422,9 @@
* @param fraction From 0 to 1 where 0 means collapsed and 1 expanded.
*/
public void setPanelExpansion(float fraction) {
+ if (isNaN(fraction)) {
+ throw new IllegalArgumentException("Fraction should not be NaN");
+ }
if (mExpansionFraction != fraction) {
mExpansionFraction = fraction;
@@ -493,6 +502,10 @@
mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
mState.getBehindTint(), interpolatedFract);
}
+ if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) {
+ throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
+ + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha);
+ }
}
/**
@@ -548,6 +561,10 @@
float newBehindAlpha = mState.getBehindAlpha();
if (mBehindAlpha != newBehindAlpha) {
mBehindAlpha = newBehindAlpha;
+ if (isNaN(mBehindAlpha)) {
+ throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
+ + ", back: " + mBehindAlpha);
+ }
updateScrims();
}
}
@@ -948,8 +965,11 @@
pw.print(" tint=0x");
pw.println(Integer.toHexString(mScrimForBubble.getTint()));
- pw.print(" mTracking=");
+ pw.print(" mTracking=");
pw.println(mTracking);
+
+ pw.print(" mExpansionFraction=");
+ pw.println(mExpansionFraction);
}
public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
@@ -996,6 +1016,10 @@
// in this case, back-scrim needs to be re-evaluated
if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
float newBehindAlpha = mState.getBehindAlpha();
+ if (isNaN(newBehindAlpha)) {
+ throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
+ + ", back: " + mBehindAlpha);
+ }
if (mBehindAlpha != newBehindAlpha) {
mBehindAlpha = newBehindAlpha;
updateScrims();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
index a3e2e76..7280a88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -59,7 +59,7 @@
mUserSetupUri = Secure.getUriFor(Secure.USER_SETUP_COMPLETE);
mSettingsObserver = new ContentObserver(mainHandler) {
@Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
+ public void onChange(boolean selfChange, Uri uri, int flags) {
Log.d(TAG, "Setting change: " + uri);
if (mUserSetupUri.equals(uri)) {
notifySetupChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 31b9952..30db37c 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -99,8 +99,10 @@
Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
false,
new ContentObserver(mBgHandler) {
+
@Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
+ public void onChange(boolean selfChange, Iterable<Uri> uris, int flags,
+ int userId) {
if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId);
if (ActivityManager.getCurrentUser() == userId) {
updateThemeOverlays();
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 142fdc2..b2a5f5b 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -262,10 +262,13 @@
}
@Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
+ public void onChange(boolean selfChange, Iterable<Uri> uris, int flags, int userId) {
if (userId == ActivityManager.getCurrentUser()) {
- reloadSetting(uri);
+ for (Uri u : uris) {
+ reloadSetting(u);
+ }
}
}
+
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java
new file mode 100644
index 0000000..6c3538c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java
@@ -0,0 +1,62 @@
+/*
+ * 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 com.android.systemui.util;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.view.Gravity;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+/**
+ * Circular view with a semitransparent, circular background with an 'X' inside it.
+ *
+ * This is used by both Bubbles and PIP as the dismiss target.
+ */
+public class DismissCircleView extends FrameLayout {
+
+ private final ImageView mIconView = new ImageView(getContext());
+
+ public DismissCircleView(Context context) {
+ super(context);
+ final Resources res = getResources();
+
+ setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
+
+ mIconView.setImageDrawable(res.getDrawable(R.drawable.dismiss_target_x));
+ addView(mIconView);
+
+ setViewSizes();
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ setViewSizes();
+ }
+
+ /** Retrieves the current dimensions for the icon and circle and applies them. */
+ private void setViewSizes() {
+ final Resources res = getResources();
+ final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size);
+ mIconView.setLayoutParams(
+ new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index ae4581a..ec6d3e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.bubbles.animation;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.mockito.Mockito.verify;
import android.content.res.Configuration;
@@ -118,118 +117,6 @@
testBubblesInCorrectExpandedPositions();
}
- @Test
- @Ignore
- public void testBubbleDraggedNotDismissedSnapsBack() throws InterruptedException {
- expand();
-
- final View draggedBubble = mViews.get(0);
- mExpandedController.prepareForBubbleDrag(draggedBubble);
- mExpandedController.dragBubbleOut(draggedBubble, 500f, 500f);
-
- assertEquals(500f, draggedBubble.getTranslationX(), 1f);
- assertEquals(500f, draggedBubble.getTranslationY(), 1f);
-
- // Snap it back and make sure it made it back correctly.
- mExpandedController.snapBubbleBack(draggedBubble, 0f, 0f);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
- testBubblesInCorrectExpandedPositions();
- }
-
- @Test
- @Ignore
- public void testBubbleDismissed() throws InterruptedException {
- expand();
-
- final View draggedBubble = mViews.get(0);
- mExpandedController.prepareForBubbleDrag(draggedBubble);
- mExpandedController.dragBubbleOut(draggedBubble, 500f, 500f);
-
- assertEquals(500f, draggedBubble.getTranslationX(), 1f);
- assertEquals(500f, draggedBubble.getTranslationY(), 1f);
-
- mLayout.removeView(draggedBubble);
- waitForLayoutMessageQueue();
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
- assertEquals(-1, mLayout.indexOfChild(draggedBubble));
- testBubblesInCorrectExpandedPositions();
- }
-
- @Test
- @Ignore("Flaky")
- public void testMagnetToDismiss_dismiss() throws InterruptedException {
- expand();
-
- final View draggedOutView = mViews.get(0);
- final Runnable after = Mockito.mock(Runnable.class);
-
- mExpandedController.prepareForBubbleDrag(draggedOutView);
- mExpandedController.dragBubbleOut(draggedOutView, 25, 25);
-
- // Magnet to dismiss, verify the bubble is at the dismiss target and the callback was
- // called.
- mExpandedController.magnetBubbleToDismiss(
- mViews.get(0), 100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
- verify(after).run();
- assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
-
- // Dismiss the now-magneted bubble, verify that the callback was called.
- final Runnable afterDismiss = Mockito.mock(Runnable.class);
- mExpandedController.dismissDraggedOutBubble(draggedOutView, afterDismiss);
- waitForPropertyAnimations(DynamicAnimation.ALPHA);
- verify(after).run();
-
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
- assertEquals(mBubblePaddingTop, mViews.get(1).getTranslationX(), 1f);
- }
-
- @Test
- @Ignore("Flaky")
- public void testMagnetToDismiss_demagnetizeThenDrag() throws InterruptedException {
- expand();
-
- final View draggedOutView = mViews.get(0);
- final Runnable after = Mockito.mock(Runnable.class);
-
- mExpandedController.prepareForBubbleDrag(draggedOutView);
- mExpandedController.dragBubbleOut(draggedOutView, 25, 25);
-
- // Magnet to dismiss, verify the bubble is at the dismiss target and the callback was
- // called.
- mExpandedController.magnetBubbleToDismiss(
- draggedOutView, 100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
- verify(after).run();
- assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
-
- // Demagnetize the bubble towards (25, 25).
- mExpandedController.demagnetizeBubbleTo(25 /* x */, 25 /* y */, 100, 100);
-
- // Start dragging towards (20, 20).
- mExpandedController.dragBubbleOut(draggedOutView, 20, 20);
-
- // Since we just demagnetized, the bubble shouldn't be at (20, 20), it should be animating
- // towards it.
- assertNotEquals(20, draggedOutView.getTranslationX());
- assertNotEquals(20, draggedOutView.getTranslationY());
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
- // Waiting for the animations should result in the bubble ending at (20, 20) since the
- // animation end value was updated.
- assertEquals(20, draggedOutView.getTranslationX(), 1f);
- assertEquals(20, draggedOutView.getTranslationY(), 1f);
-
- // Drag to (30, 30).
- mExpandedController.dragBubbleOut(draggedOutView, 30, 30);
-
- // It should go there instantly since the animations finished.
- assertEquals(30, draggedOutView.getTranslationX(), 1f);
- assertEquals(30, draggedOutView.getTranslationY(), 1f);
- }
-
/** Expand the stack and wait for animations to finish. */
private void expand() throws InterruptedException {
mExpandedController.expandFromStack(Mockito.mock(Runnable.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index 9cc0349..e3187cb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.bubbles.animation;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -41,7 +40,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -242,61 +240,6 @@
}
@Test
- @Ignore("Flaky")
- public void testMagnetToDismiss_dismiss() throws InterruptedException {
- final Runnable after = Mockito.mock(Runnable.class);
-
- // Magnet to dismiss, verify the stack is at the dismiss target and the callback was
- // called.
- mStackController.magnetToDismiss(100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
- verify(after).run();
- assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
-
- // Dismiss the stack, verify that the callback was called.
- final Runnable afterImplode = Mockito.mock(Runnable.class);
- mStackController.implodeStack(afterImplode);
- waitForPropertyAnimations(
- DynamicAnimation.ALPHA, DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y);
- verify(after).run();
- }
-
- @Test
- @Ignore("Flaking")
- public void testMagnetToDismiss_demagnetizeThenDrag() throws InterruptedException {
- final Runnable after = Mockito.mock(Runnable.class);
-
- // Magnet to dismiss, verify the stack is at the dismiss target and the callback was
- // called.
- mStackController.magnetToDismiss(100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
- verify(after).run();
-
- assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
-
- // Demagnetize towards (25, 25) and then send a touch event.
- mStackController.demagnetizeFromDismissToPoint(25, 25, 0, 0);
- waitForLayoutMessageQueue();
- mStackController.moveStackFromTouch(20, 20);
-
- // Since the stack is demagnetizing, it shouldn't be at the stack position yet.
- assertNotEquals(20, mStackController.getStackPosition().x, 1f);
- assertNotEquals(20, mStackController.getStackPosition().y, 1f);
-
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
- // Once the animation is done it should end at the touch position coordinates.
- assertEquals(20, mStackController.getStackPosition().x, 1f);
- assertEquals(20, mStackController.getStackPosition().y, 1f);
-
- mStackController.moveStackFromTouch(30, 30);
-
- // Touches after the animation are done should change the stack position instantly.
- assertEquals(30, mStackController.getStackPosition().x, 1f);
- assertEquals(30, mStackController.getStackPosition().y, 1f);
- }
-
- @Test
public void testFloatingCoordinator() {
// We should have called onContentAdded only once while adding all of the bubbles in
// setup().
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 45ea3c9..183dde8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -408,7 +408,7 @@
fun testDisableFeature_notAvailable() {
Settings.Secure.putIntForUser(mContext.contentResolver,
ControlsControllerImpl.CONTROLS_AVAILABLE, 0, user)
- controller.settingObserver.onChange(false, ControlsControllerImpl.URI, 0)
+ controller.settingObserver.onChange(false, listOf(ControlsControllerImpl.URI), 0, 0)
assertFalse(controller.available)
}
@@ -421,7 +421,7 @@
Settings.Secure.putIntForUser(mContext.contentResolver,
ControlsControllerImpl.CONTROLS_AVAILABLE, 0, user)
- controller.settingObserver.onChange(false, ControlsControllerImpl.URI, user)
+ controller.settingObserver.onChange(false, listOf(ControlsControllerImpl.URI), 0, user)
assertTrue(controller.getFavorites().isEmpty())
}
@@ -432,7 +432,7 @@
Settings.Secure.putIntForUser(mContext.contentResolver,
ControlsControllerImpl.CONTROLS_AVAILABLE, 0, otherUser)
- controller.settingObserver.onChange(false, ControlsControllerImpl.URI, otherUser)
+ controller.settingObserver.onChange(false, listOf(ControlsControllerImpl.URI), 0, otherUser)
assertTrue(controller.available)
assertFalse(controller.getFavorites().isEmpty())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index cd46110..a2ab784 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -16,9 +16,10 @@
package com.android.systemui.pip;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -64,7 +65,7 @@
@Test
public void getAnimator_withAlpha_returnFloatAnimator() {
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, true /* scheduleFinishPip */, new Rect(), 0f, 1f);
+ .getAnimator(mLeash, new Rect(), 0f, 1f);
assertEquals("Expect ANIM_TYPE_ALPHA animation",
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_ALPHA);
@@ -73,7 +74,7 @@
@Test
public void getAnimator_withBounds_returnBoundsAnimator() {
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, true /* scheduleFinishPip */, new Rect(), new Rect());
+ .getAnimator(mLeash, new Rect(), new Rect());
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -85,12 +86,12 @@
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
- .getAnimator(mLeash, true /* scheduleFinishPip */, startValue, endValue1);
+ .getAnimator(mLeash, startValue, endValue1);
oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
- .getAnimator(mLeash, true /* scheduleFinishPip */, startValue, endValue2);
+ .getAnimator(mLeash, startValue, endValue2);
assertEquals("getAnimator with same type returns same animator",
oldAnimator, newAnimator);
@@ -99,23 +100,28 @@
}
@Test
- public void getAnimator_scheduleFinishPip() {
+ public void getAnimator_setTransitionDirection() {
PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, true /* scheduleFinishPip */, new Rect(), 0f, 1f);
- assertTrue("scheduleFinishPip is true", animator.shouldScheduleFinishPip());
+ .getAnimator(mLeash, new Rect(), 0f, 1f)
+ .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
+ assertEquals("Transition to PiP mode",
+ animator.getTransitionDirection(), TRANSITION_DIRECTION_TO_PIP);
animator = mPipAnimationController
- .getAnimator(mLeash, false /* scheduleFinishPip */, new Rect(), 0f, 1f);
- assertFalse("scheduleFinishPip is false", animator.shouldScheduleFinishPip());
+ .getAnimator(mLeash, new Rect(), 0f, 1f)
+ .setTransitionDirection(TRANSITION_DIRECTION_TO_FULLSCREEN);
+ assertEquals("Transition to fullscreen mode",
+ animator.getTransitionDirection(), TRANSITION_DIRECTION_TO_FULLSCREEN);
}
@Test
+ @SuppressWarnings("unchecked")
public void pipTransitionAnimator_updateEndValue() {
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, true /* scheduleFinishPip */, startValue, endValue1);
+ .getAnimator(mLeash, startValue, endValue1);
animator.updateEndValue(endValue2);
@@ -127,7 +133,7 @@
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, true /* scheduleFinishPip */, startValue, endValue);
+ .getAnimator(mLeash, startValue, endValue);
animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
animator.setPipAnimationCallback(mPipAnimationCallback);
@@ -167,6 +173,11 @@
}
@Override
+ public SurfaceControl.Transaction setCornerRadius(SurfaceControl leash, float radius) {
+ return this;
+ }
+
+ @Override
public void apply() {}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index 45c0cdd..616399a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.NotificationMediaManager;
import org.junit.Before;
import org.junit.Test;
@@ -53,6 +54,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
+import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -79,6 +81,10 @@
private QSDetail.Callback mCallback;
@Mock
private QSTileView mQSTileView;
+ @Mock
+ private NotificationMediaManager mNotificationMediaManager;
+ @Mock
+ private Executor mBackgroundExecutor;
@Before
public void setup() throws Exception {
@@ -88,7 +94,7 @@
mTestableLooper.runWithLooper(() -> {
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher,
- mQSLogger);
+ mQSLogger, mNotificationMediaManager, mBackgroundExecutor);
// Provides a parent with non-zero size for QSPanel
mParentView = new FrameLayout(mContext);
mParentView.addView(mQsPanel);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 84ce34b..4cc6590 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -94,6 +94,7 @@
"services-stubs",
"services.net",
"android.hardware.light-V2.0-java",
+ "android.hardware.power-java",
"android.hardware.power-V1.0-java",
"android.hardware.tv.cec-V1.0-java",
"android.hardware.vibrator-java",
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 45c3aeb..7774633 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -138,6 +138,7 @@
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.Immutable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsAsyncNotedCallback;
@@ -155,11 +156,14 @@
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
+import com.android.server.SystemServiceManager;
import com.android.server.pm.PackageList;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import libcore.util.EmptyArray;
+import org.json.JSONException;
+import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -169,6 +173,7 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
@@ -184,6 +189,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Scanner;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
@@ -191,6 +197,11 @@
static final String TAG = "AppOps";
static final boolean DEBUG = false;
+ /**
+ * Used for data access validation collection, we wish to only log a specific access once
+ */
+ private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
+
private static final int NO_VERSION = -1;
/** Increment by one every time and add the corresponding upgrade logic in
* {@link #upgradeLocked(int)} below. The first version was 1 */
@@ -241,6 +252,7 @@
final Context mContext;
final AtomicFile mFile;
+ private final @Nullable File mNoteOpCallerStacktracesFile;
final Handler mHandler;
/** Pool for {@link OpEventProxyInfoPool} to avoid to constantly reallocate new objects */
@@ -278,6 +290,8 @@
private final ArrayMap<Pair<String, Integer>, ArrayList<AsyncNotedAppOp>>
mUnforwardedAsyncNotedOps = new ArrayMap<>();
+ boolean mWriteNoteOpsScheduled;
+
boolean mWriteScheduled;
boolean mFastWriteScheduled;
final Runnable mWriteRunner = new Runnable() {
@@ -1397,11 +1411,42 @@
featureOp.onClientDeath(clientId);
}
+
+ /**
+ * Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces}
+ * so that we do not log the same operation twice between instances
+ */
+ private void readNoteOpCallerStackTraces() {
+ try {
+ if (!mNoteOpCallerStacktracesFile.exists()) {
+ mNoteOpCallerStacktracesFile.createNewFile();
+ return;
+ }
+
+ try (Scanner read = new Scanner(mNoteOpCallerStacktracesFile)) {
+ read.useDelimiter("\\},");
+ while (read.hasNext()) {
+ String jsonOps = read.next();
+ mNoteOpCallerStacktraces.add(NoteOpTrace.fromJson(jsonOps));
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Cannot parse traces noteOps", e);
+ }
+ }
+
public AppOpsService(File storagePath, Handler handler, Context context) {
mContext = context;
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mFile = new AtomicFile(storagePath, "appops");
+ if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
+ mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
+ "noteOpStackTraces.json");
+ readNoteOpCallerStackTraces();
+ } else {
+ mNoteOpCallerStacktracesFile = null;
+ }
mHandler = handler;
mConstants = new Constants(mHandler);
readState();
@@ -1802,6 +1847,9 @@
if (doWrite) {
writeState();
}
+ if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
+ writeNoteOps();
+ }
}
private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
@@ -6051,4 +6099,142 @@
setMode(code, uid, packageName, mode, callback);
}
}
+
+
+ /**
+ * Async task for writing note op stack trace, op code, package name and version to file
+ * More specifically, writes all the collected ops from {@link #mNoteOpCallerStacktraces}
+ */
+ private void writeNoteOps() {
+ synchronized (this) {
+ mWriteNoteOpsScheduled = false;
+ }
+ synchronized (mNoteOpCallerStacktracesFile) {
+ try (FileWriter writer = new FileWriter(mNoteOpCallerStacktracesFile)) {
+ int numTraces = mNoteOpCallerStacktraces.size();
+ for (int i = 0; i < numTraces; i++) {
+ // Writing json formatted string into file
+ writer.write(mNoteOpCallerStacktraces.valueAt(i).asJson());
+ // Comma separation, so we can wrap the entire log as a JSON object
+ // when all results are collected
+ writer.write(",");
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to load opsValidation file for FileWriter", e);
+ }
+ }
+ }
+
+ /**
+ * This class represents a NoteOp Trace object amd contains the necessary fields that will
+ * be written to file to use for permissions data validation in JSON format
+ */
+ @Immutable
+ static class NoteOpTrace {
+ static final String STACKTRACE = "stackTrace";
+ static final String OP = "op";
+ static final String PACKAGENAME = "packageName";
+ static final String VERSION = "version";
+
+ private final @NonNull String mStackTrace;
+ private final int mOp;
+ private final @Nullable String mPackageName;
+ private final long mVersion;
+
+ /**
+ * Initialize a NoteOp object using a JSON object containing the necessary fields
+ *
+ * @param jsonTrace JSON object represented as a string
+ *
+ * @return NoteOpTrace object initialized with JSON fields
+ */
+ static NoteOpTrace fromJson(String jsonTrace) {
+ try {
+ // Re-add closing bracket which acted as a delimiter by the reader
+ JSONObject obj = new JSONObject(jsonTrace.concat("}"));
+ return new NoteOpTrace(obj.getString(STACKTRACE), obj.getInt(OP),
+ obj.getString(PACKAGENAME), obj.getLong(VERSION));
+ } catch (JSONException e) {
+ // Swallow error, only meant for logging ops, should not affect flow of the code
+ Slog.e(TAG, "Error constructing NoteOpTrace object "
+ + "JSON trace format incorrect", e);
+ return null;
+ }
+ }
+
+ NoteOpTrace(String stackTrace, int op, String packageName, long version) {
+ mStackTrace = stackTrace;
+ mOp = op;
+ mPackageName = packageName;
+ mVersion = version;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NoteOpTrace that = (NoteOpTrace) o;
+ return mOp == that.mOp
+ && mVersion == that.mVersion
+ && mStackTrace.equals(that.mStackTrace)
+ && Objects.equals(mPackageName, that.mPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStackTrace, mOp, mPackageName, mVersion);
+ }
+
+ /**
+ * The object is formatted as a JSON object and returned as a String
+ *
+ * @return JSON formatted string
+ */
+ public String asJson() {
+ return "{"
+ + "\"" + STACKTRACE + "\":\"" + mStackTrace.replace("\n", "\\n")
+ + '\"' + ",\"" + OP + "\":" + mOp
+ + ",\"" + PACKAGENAME + "\":\"" + mPackageName + '\"'
+ + ",\"" + VERSION + "\":" + mVersion
+ + '}';
+ }
+ }
+
+ /**
+ * Collects noteOps, noteProxyOps and startOps from AppOpsManager and writes it into a file
+ * which will be used for permissions data validation, the given parameters to this method
+ * will be logged in json format
+ *
+ * @param stackTrace stacktrace from the most recent call in AppOpsManager
+ * @param op op code
+ * @param packageName package making call
+ * @param version android version for this call
+ */
+ @Override
+ public void collectNoteOpCallsForValidation(String stackTrace, int op, String packageName,
+ long version) {
+ if (!AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
+ return;
+ }
+
+ Objects.requireNonNull(stackTrace);
+ Preconditions.checkArgument(op >= 0);
+ Preconditions.checkArgument(op < AppOpsManager._NUM_OP);
+ Objects.requireNonNull(version);
+
+ NoteOpTrace noteOpTrace = new NoteOpTrace(stackTrace, op, packageName, version);
+
+ boolean noteOpSetWasChanged;
+ synchronized (this) {
+ noteOpSetWasChanged = mNoteOpCallerStacktraces.add(noteOpTrace);
+ if (noteOpSetWasChanged && !mWriteNoteOpsScheduled) {
+ mWriteNoteOpsScheduled = true;
+ mHandler.postDelayed(PooledLambda.obtainRunnable((that) -> {
+ AsyncTask.execute(() -> {
+ that.writeNoteOps();
+ });
+ }, this), 2500);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 858c157..ecdafb0 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1138,9 +1138,10 @@
biometricStatus = result.second;
- Slog.d(TAG, "Authenticator ID: " + authenticator.id
+ Slog.d(TAG, "Package: " + opPackageName
+ + " Authenticator ID: " + authenticator.id
+ " Modality: " + authenticator.modality
- + " ReportedModality: " + result.first
+ + " Reported Modality: " + result.first
+ " Status: " + biometricStatus);
if (firstBiometricModality == TYPE_NONE) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index f04be0b..294deba 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -42,6 +42,8 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.hardware.power.Boost;
+import android.hardware.power.Mode;
import android.hardware.power.V1_0.PowerHint;
import android.net.Uri;
import android.os.BatteryManager;
@@ -732,6 +734,16 @@
PowerManagerService.nativeSendPowerHint(hintId, data);
}
+ /** Wrapper for PowerManager.nativeSetPowerBoost */
+ public void nativeSetPowerBoost(int boost, int durationMs) {
+ PowerManagerService.nativeSetPowerBoost(boost, durationMs);
+ }
+
+ /** Wrapper for PowerManager.nativeSetPowerMode */
+ public void nativeSetPowerMode(int mode, boolean enabled) {
+ PowerManagerService.nativeSetPowerMode(mode, enabled);
+ }
+
/** Wrapper for PowerManager.nativeSetFeature */
public void nativeSetFeature(int featureId, int data) {
PowerManagerService.nativeSetFeature(featureId, data);
@@ -817,6 +829,8 @@
private static native void nativeSetInteractive(boolean enable);
private static native void nativeSetAutoSuspend(boolean enable);
private static native void nativeSendPowerHint(int hintId, int data);
+ private static native void nativeSetPowerBoost(int boost, int durationMs);
+ private static native void nativeSetPowerMode(int mode, boolean enabled);
private static native void nativeSetFeature(int featureId, int data);
private static native boolean nativeForceSuspend();
@@ -3608,6 +3622,16 @@
mNativeWrapper.nativeSendPowerHint(hintId, data);
}
+ private void setPowerBoostInternal(int boost, int durationMs) {
+ // Maybe filter the event.
+ mNativeWrapper.nativeSetPowerBoost(boost, durationMs);
+ }
+
+ private void setPowerModeInternal(int mode, boolean enabled) {
+ // Maybe filter the event.
+ mNativeWrapper.nativeSetPowerMode(mode, enabled);
+ }
+
@VisibleForTesting
boolean wasDeviceIdleForInternal(long ms) {
synchronized (mLock) {
@@ -4664,6 +4688,26 @@
}
@Override // Binder call
+ public void setPowerBoost(int boost, int durationMs) {
+ if (!mSystemReady) {
+ // Service not ready yet, so who the heck cares about power hints, bah.
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+ setPowerBoostInternal(boost, durationMs);
+ }
+
+ @Override // Binder call
+ public void setPowerMode(int mode, boolean enabled) {
+ if (!mSystemReady) {
+ // Service not ready yet, so who the heck cares about power hints, bah.
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+ setPowerModeInternal(mode, enabled);
+ }
+
+ @Override // Binder call
public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
WorkSource ws, String historyTag) {
if (lock == null) {
@@ -5457,6 +5501,15 @@
}
@Override
+ public void setPowerBoost(int boost, int durationMs) {
+ setPowerBoostInternal(boost, durationMs);
+ }
+
+ @Override
+ public void setPowerMode(int mode, boolean enabled) {
+ setPowerModeInternal(mode, enabled);
+ }
+ @Override
public boolean wasDeviceIdleFor(long ms) {
return wasDeviceIdleForInternal(ms);
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 4eff954f..e100ff8 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -15,8 +15,8 @@
*/
package com.android.server.tv.tunerresourcemanager;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
/**
* A client profile object used by the Tuner Resource Manager to record the registered clients'
@@ -65,7 +65,7 @@
/**
* List of the frontend ids that are used by the current client.
*/
- private List<Integer> mUsingFrontendIds = new ArrayList<>();
+ private Set<Integer> mUsingFrontendIds = new HashSet<>();
/**
* Optional arbitrary priority value given by the client.
@@ -131,7 +131,7 @@
mUsingFrontendIds.add(frontendId);
}
- public List<Integer> getInUseFrontendIds() {
+ public Iterable<Integer> getInUseFrontendIds() {
return mUsingFrontendIds;
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
index a109265..56f6159 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java
@@ -15,12 +15,11 @@
*/
package com.android.server.tv.tunerresourcemanager;
-import android.annotation.Nullable;
import android.media.tv.tuner.frontend.FrontendSettings.Type;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
/**
* A frontend resource object used by the Tuner Resource Manager to record the tuner frontend
@@ -50,7 +49,7 @@
/**
* An array to save all the FE ids under the same exclisive group.
*/
- private List<Integer> mExclusiveGroupMemberFeIds = new ArrayList<>();
+ private Set<Integer> mExclusiveGroupMemberFeIds = new HashSet<>();
/**
* If the current resource is in use. Once resources under the same exclusive group id is in use
@@ -82,12 +81,12 @@
return mExclusiveGroupId;
}
- public List<Integer> getExclusiveGroupMemberFeIds() {
+ public Set<Integer> getExclusiveGroupMemberFeIds() {
return mExclusiveGroupMemberFeIds;
}
/**
- * Add one id into the exclusive group member id list.
+ * Add one id into the exclusive group member id collection.
*
* @param id the id to be added.
*/
@@ -96,21 +95,21 @@
}
/**
- * Add one id list to the exclusive group member id list.
+ * Add one id collection to the exclusive group member id collection.
*
- * @param ids the id list to be added.
+ * @param ids the id collection to be added.
*/
- public void addExclusiveGroupMemberFeId(List<Integer> ids) {
+ public void addExclusiveGroupMemberFeIds(Collection<Integer> ids) {
mExclusiveGroupMemberFeIds.addAll(ids);
}
/**
- * Remove one id from the exclusive group member id list.
+ * Remove one id from the exclusive group member id collection.
*
* @param id the id to be removed.
*/
public void removeExclusiveGroupMemberFeId(int id) {
- mExclusiveGroupMemberFeIds.remove(new Integer(id));
+ mExclusiveGroupMemberFeIds.remove(id);
}
public boolean isInUse() {
@@ -143,22 +142,10 @@
public String toString() {
return "FrontendResource[id=" + this.mId + ", type=" + this.mType
+ ", exclusiveGId=" + this.mExclusiveGroupId + ", exclusiveGMemeberIds="
- + Arrays.toString(this.mExclusiveGroupMemberFeIds.toArray())
+ + this.mExclusiveGroupMemberFeIds
+ ", isInUse=" + this.mIsInUse + ", ownerClientId=" + this.mOwnerClientId + "]";
}
- @Override
- public boolean equals(@Nullable Object o) {
- if (o instanceof FrontendResource) {
- FrontendResource fe = (FrontendResource) o;
- return mId == fe.getId() && mType == fe.getType()
- && mExclusiveGroupId == fe.getExclusiveGroupId()
- && mExclusiveGroupMemberFeIds.equals(fe.getExclusiveGroupMemberFeIds())
- && mIsInUse == fe.isInUse() && mOwnerClientId == fe.getOwnerClientId();
- }
- return false;
- }
-
/**
* Builder class for {@link FrontendResource}.
*/
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index cb31a50..04d551d 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -29,16 +29,19 @@
import android.media.tv.tunerresourcemanager.TunerLnbRequest;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
/**
* This class provides a system service that manages the TV tuner resources.
@@ -52,18 +55,15 @@
public static final int INVALID_CLIENT_ID = -1;
private static final int MAX_CLIENT_PRIORITY = 1000;
- // Array of the registered client profiles
- @VisibleForTesting private SparseArray<ClientProfile> mClientProfiles = new SparseArray<>();
+ // Map of the registered client profiles
+ private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
private int mNextUnusedClientId = 0;
- private List<Integer> mRegisteredClientIds = new ArrayList<Integer>();
- // Array of the current available frontend resources
- @VisibleForTesting
- private SparseArray<FrontendResource> mFrontendResources = new SparseArray<>();
- // Array of the current available frontend ids
- private List<Integer> mAvailableFrontendIds = new ArrayList<Integer>();
+ // Map of the current available frontend resources
+ private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
- private SparseArray<IResourcesReclaimListener> mListeners = new SparseArray<>();
+ @GuardedBy("mLock")
+ private Map<Integer, ResourcesReclaimListenerRecord> mListeners = new HashMap<>();
private TvInputManager mManager;
private UseCasePriorityHints mPriorityCongfig = new UseCasePriorityHints();
@@ -103,6 +103,10 @@
throw new RemoteException("clientId can't be null!");
}
+ if (listener == null) {
+ throw new RemoteException("IResourcesReclaimListener can't be null!");
+ }
+
if (!mPriorityCongfig.isDefinedUseCase(profile.getUseCase())) {
throw new RemoteException("Use undefined client use case:" + profile.getUseCase());
}
@@ -261,9 +265,7 @@
.build();
clientProfile.setPriority(getClientPriority(profile.getUseCase(), pid));
- mClientProfiles.append(clientId[0], clientProfile);
- mListeners.append(clientId[0], listener);
- mRegisteredClientIds.add(clientId[0]);
+ addClientProfile(clientId[0], clientProfile, listener);
}
@VisibleForTesting
@@ -271,15 +273,7 @@
if (DEBUG) {
Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")");
}
- for (int id : getClientProfile(clientId).getInUseFrontendIds()) {
- getFrontendResource(id).removeOwner();
- for (int groupMemberId : getFrontendResource(id).getExclusiveGroupMemberFeIds()) {
- getFrontendResource(groupMemberId).removeOwner();
- }
- }
- mClientProfiles.remove(clientId);
- mListeners.remove(clientId);
- mRegisteredClientIds.remove(clientId);
+ removeClientProfile(clientId);
}
@VisibleForTesting
@@ -313,56 +307,32 @@
}
}
- // An arrayList to record the frontends pending on updating. Ids will be removed
- // from this list once its updating finished. Any frontend left in this list when all
- // the updates are done will be removed from mAvailableFrontendIds and
- // mFrontendResources.
- List<Integer> updatingFrontendIds = new ArrayList<>(mAvailableFrontendIds);
+ // A set to record the frontends pending on updating. Ids will be removed
+ // from this set once its updating finished. Any frontend left in this set when all
+ // the updates are done will be removed from mFrontendResources.
+ Set<Integer> updatingFrontendIds = new HashSet<>(getFrontendResources().keySet());
- // Update frontendResources sparse array and other mappings accordingly
+ // Update frontendResources map and other mappings accordingly
for (int i = 0; i < infos.length; i++) {
if (getFrontendResource(infos[i].getId()) != null) {
if (DEBUG) {
Slog.d(TAG, "Frontend id=" + infos[i].getId() + "exists.");
}
- updatingFrontendIds.remove(new Integer(infos[i].getId()));
+ updatingFrontendIds.remove(infos[i].getId());
} else {
// Add a new fe resource
FrontendResource newFe = new FrontendResource.Builder(infos[i].getId())
.type(infos[i].getFrontendType())
.exclusiveGroupId(infos[i].getExclusiveGroupId())
.build();
- // Update the exclusive group member list in all the existing Frontend resource
- for (Integer feId : mAvailableFrontendIds) {
- FrontendResource fe = getFrontendResource(feId.intValue());
- if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) {
- newFe.addExclusiveGroupMemberFeId(fe.getId());
- newFe.addExclusiveGroupMemberFeId(fe.getExclusiveGroupMemberFeIds());
- for (Integer excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) {
- getFrontendResource(excGroupmemberFeId.intValue())
- .addExclusiveGroupMemberFeId(newFe.getId());
- }
- fe.addExclusiveGroupMemberFeId(newFe.getId());
- break;
- }
- }
- // Update resource list and available id list
- mFrontendResources.append(newFe.getId(), newFe);
- mAvailableFrontendIds.add(newFe.getId());
+ addFrontendResource(newFe);
}
}
// TODO check if the removing resource is in use or not. Handle the conflict.
- for (Integer removingId : updatingFrontendIds) {
- // update the exclusive group id memver list
- FrontendResource fe = getFrontendResource(removingId.intValue());
- fe.removeExclusiveGroupMemberFeId(new Integer(fe.getId()));
- for (Integer excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) {
- getFrontendResource(excGroupmemberFeId.intValue())
- .removeExclusiveGroupMemberFeId(new Integer(fe.getId()));
- }
- mFrontendResources.remove(removingId.intValue());
- mAvailableFrontendIds.remove(removingId);
+ for (int removingId : updatingFrontendIds) {
+ // update the exclusive group id member list
+ removeFrontendResource(removingId);
}
}
@@ -383,25 +353,24 @@
int inUseLowestPriorityFrId = -1;
// Priority max value is 1000
int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
- for (int id : mAvailableFrontendIds) {
- FrontendResource fr = getFrontendResource(id);
+ for (FrontendResource fr : getFrontendResources().values()) {
if (fr.getType() == request.getFrontendType()) {
if (!fr.isInUse()) {
// Grant unused frontend with no exclusive group members first.
- if (fr.getExclusiveGroupMemberFeIds().size() == 0) {
- grantingFrontendId = id;
+ if (fr.getExclusiveGroupMemberFeIds().isEmpty()) {
+ grantingFrontendId = fr.getId();
break;
} else if (grantingFrontendId < 0) {
// Grant the unused frontend with lower id first if all the unused
// frontends have exclusive group members.
- grantingFrontendId = id;
+ grantingFrontendId = fr.getId();
}
} else if (grantingFrontendId < 0) {
// Record the frontend id with the lowest client priority among all the
// in use frontends when no available frontend has been found.
- int priority = getOwnerClientPriority(id);
+ int priority = getOwnerClientPriority(fr);
if (currentLowestPriority > priority) {
- inUseLowestPriorityFrId = id;
+ inUseLowestPriorityFrId = fr.getId();
currentLowestPriority = priority;
}
}
@@ -428,6 +397,62 @@
}
@VisibleForTesting
+ protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
+ private final IResourcesReclaimListener mListener;
+ private final int mClientId;
+
+ public ResourcesReclaimListenerRecord(IResourcesReclaimListener listener, int clientId) {
+ mListener = listener;
+ mClientId = clientId;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ removeClientProfile(mClientId);
+ }
+ }
+
+ public int getId() {
+ return mClientId;
+ }
+
+ public IResourcesReclaimListener getListener() {
+ return mListener;
+ }
+ }
+
+ private void addResourcesReclaimListener(int clientId, IResourcesReclaimListener listener) {
+ if (listener == null) {
+ if (DEBUG) {
+ Slog.w(TAG, "Listener is null when client " + clientId + " registered!");
+ }
+ return;
+ }
+
+ ResourcesReclaimListenerRecord record =
+ new ResourcesReclaimListenerRecord(listener, clientId);
+
+ try {
+ listener.asBinder().linkToDeath(record, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Listener already died.");
+ return;
+ }
+
+ mListeners.put(clientId, record);
+ }
+
+ @VisibleForTesting
+ protected void reclaimFrontendResource(int reclaimingId) {
+ try {
+ mListeners.get(reclaimingId).getListener().onReclaimResources();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingId, e);
+ }
+ }
+
+ @VisibleForTesting
protected int getClientPriority(int useCase, int pid) {
if (DEBUG) {
Slog.d(TAG, "getClientPriority useCase=" + useCase
@@ -446,17 +471,6 @@
return true;
}
- @VisibleForTesting
- protected void reclaimFrontendResource(int reclaimingId) throws RemoteException {
- if (mListeners.get(reclaimingId) != null) {
- try {
- mListeners.get(reclaimingId).onReclaimResources();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
-
private void updateFrontendClientMappingOnNewGrant(int grantingId, int ownerClientId) {
FrontendResource grantingFrontend = getFrontendResource(grantingId);
ClientProfile ownerProfile = getClientProfile(ownerClientId);
@@ -471,33 +485,77 @@
/**
* Get the owner client's priority from the frontend id.
*
- * @param frontendId an in use frontend id.
+ * @param frontend an in use frontend.
* @return the priority of the owner client of the frontend.
*/
- private int getOwnerClientPriority(int frontendId) {
- return getClientProfile(getFrontendResource(frontendId).getOwnerClientId()).getPriority();
+ private int getOwnerClientPriority(FrontendResource frontend) {
+ return getClientProfile(frontend.getOwnerClientId()).getPriority();
}
- private ClientProfile getClientProfile(int clientId) {
- return mClientProfiles.get(clientId);
- }
-
+ @VisibleForTesting
+ @Nullable
protected FrontendResource getFrontendResource(int frontendId) {
return mFrontendResources.get(frontendId);
}
@VisibleForTesting
- protected SparseArray<ClientProfile> getClientProfiles() {
- return mClientProfiles;
- }
-
- @VisibleForTesting
- protected SparseArray<FrontendResource> getFrontendResources() {
+ protected Map<Integer, FrontendResource> getFrontendResources() {
return mFrontendResources;
}
- private boolean checkClientExists(int clientId) {
- return mRegisteredClientIds.contains(clientId);
+ private void addFrontendResource(FrontendResource newFe) {
+ // Update the exclusive group member list in all the existing Frontend resource
+ for (FrontendResource fe : getFrontendResources().values()) {
+ if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) {
+ newFe.addExclusiveGroupMemberFeId(fe.getId());
+ newFe.addExclusiveGroupMemberFeIds(fe.getExclusiveGroupMemberFeIds());
+ for (int excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) {
+ getFrontendResource(excGroupmemberFeId)
+ .addExclusiveGroupMemberFeId(newFe.getId());
+ }
+ fe.addExclusiveGroupMemberFeId(newFe.getId());
+ break;
+ }
+ }
+ // Update resource list and available id list
+ mFrontendResources.put(newFe.getId(), newFe);
+ }
+
+ private void removeFrontendResource(int removingId) {
+ FrontendResource fe = getFrontendResource(removingId);
+ for (int excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) {
+ getFrontendResource(excGroupmemberFeId)
+ .removeExclusiveGroupMemberFeId(fe.getId());
+ }
+ mFrontendResources.remove(removingId);
+ }
+
+ @VisibleForTesting
+ @Nullable
+ protected ClientProfile getClientProfile(int clientId) {
+ return mClientProfiles.get(clientId);
+ }
+
+ private void addClientProfile(int clientId, ClientProfile profile,
+ IResourcesReclaimListener listener) {
+ mClientProfiles.put(clientId, profile);
+ addResourcesReclaimListener(clientId, listener);
+ }
+
+ private void removeClientProfile(int clientId) {
+ for (int id : getClientProfile(clientId).getInUseFrontendIds()) {
+ getFrontendResource(id).removeOwner();
+ for (int groupMemberId : getFrontendResource(id).getExclusiveGroupMemberFeIds()) {
+ getFrontendResource(groupMemberId).removeOwner();
+ }
+ }
+ mClientProfiles.remove(clientId);
+ mListeners.remove(clientId);
+ }
+
+ @VisibleForTesting
+ protected boolean checkClientExists(int clientId) {
+ return mClientProfiles.keySet().contains(clientId);
}
private void enforceAccessPermission() {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java b/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java
index 8c2de47..367b966 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java
@@ -31,8 +31,8 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
/**
* This class provides the Tuner Resource Manager use case priority hints config info including a
@@ -56,7 +56,7 @@
*/
SparseArray<int[]> mPriorityHints = new SparseArray<>();
- List<Integer> mVendorDefinedUseCase = new ArrayList<>();
+ Set<Integer> mVendorDefinedUseCase = new HashSet<>();
private int mDefaultForeground = 150;
private int mDefaultBackground = 50;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index c781a5a..74982c6 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -146,6 +146,7 @@
"android.hardware.light@2.0",
"android.hardware.power@1.0",
"android.hardware.power@1.1",
+ "android.hardware.power-cpp",
"android.hardware.power.stats@1.0",
"android.hardware.thermal@1.0",
"android.hardware.tv.cec@1.0",
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 9cbb58d..b08868e 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -67,9 +67,9 @@
static bool wakeup_init = false;
static sem_t wakeup_sem;
-extern sp<IPowerV1_0> getPowerHalV1_0();
-extern sp<IPowerV1_1> getPowerHalV1_1();
-extern bool processPowerHalReturn(const Return<void> &ret, const char* functionName);
+extern sp<IPowerV1_0> getPowerHalHidlV1_0();
+extern sp<IPowerV1_1> getPowerHalHidlV1_1();
+extern bool processPowerHalReturn(bool isOk, const char* functionName);
extern sp<ISuspendControlService> getSuspendControl();
// Java methods used in getLowPowerStats
@@ -596,7 +596,7 @@
// The caller must be holding powerHalMutex.
static void getPowerHalLowPowerData(JNIEnv* env, jobject jrpmStats) {
- sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0();
+ sp<IPowerV1_0> powerHalV1_0 = getPowerHalHidlV1_0();
if (powerHalV1_0 == nullptr) {
ALOGE("Power Hal not loaded");
return;
@@ -629,12 +629,12 @@
}
}
});
- if (!processPowerHalReturn(ret, "getPlatformLowPowerStats")) {
+ if (!processPowerHalReturn(ret.isOk(), "getPlatformLowPowerStats")) {
return;
}
// Trying to get IPower 1.1, this will succeed only for devices supporting 1.1
- sp<IPowerV1_1> powerHal_1_1 = getPowerHalV1_1();
+ sp<IPowerV1_1> powerHal_1_1 = getPowerHalHidlV1_1();
if (powerHal_1_1 == nullptr) {
// This device does not support IPower@1.1, exiting gracefully
return;
@@ -665,7 +665,7 @@
}
}
});
- processPowerHalReturn(ret, "getSubsystemLowPowerStats");
+ processPowerHalReturn(ret.isOk(), "getSubsystemLowPowerStats");
}
static jint getPowerHalPlatformData(JNIEnv* env, jobject outBuf) {
@@ -675,7 +675,7 @@
int total_added = -1;
{
- sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0();
+ sp<IPowerV1_0> powerHalV1_0 = getPowerHalHidlV1_0();
if (powerHalV1_0 == nullptr) {
ALOGE("Power Hal not loaded");
return -1;
@@ -733,7 +733,7 @@
}
);
- if (!processPowerHalReturn(ret, "getPlatformLowPowerStats")) {
+ if (!processPowerHalReturn(ret.isOk(), "getPlatformLowPowerStats")) {
return -1;
}
}
@@ -753,7 +753,7 @@
{
// Trying to get 1.1, this will succeed only for devices supporting 1.1
- powerHal_1_1 = getPowerHalV1_1();
+ powerHal_1_1 = getPowerHalHidlV1_1();
if (powerHal_1_1 == nullptr) {
//This device does not support IPower@1.1, exiting gracefully
return 0;
@@ -820,7 +820,7 @@
}
);
- if (!processPowerHalReturn(ret, "getSubsystemLowPowerStats")) {
+ if (!processPowerHalReturn(ret.isOk(), "getSubsystemLowPowerStats")) {
return -1;
}
}
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 4e04348..239a101 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -19,6 +19,9 @@
//#define LOG_NDEBUG 0
#include <android/hardware/power/1.1/IPower.h>
+#include <android/hardware/power/Boost.h>
+#include <android/hardware/power/IPower.h>
+#include <android/hardware/power/Mode.h>
#include <android/system/suspend/1.0/ISystemSuspend.h>
#include <android/system/suspend/ISuspendControlService.h>
#include <nativehelper/JNIHelp.h>
@@ -45,6 +48,8 @@
using android::hardware::Return;
using android::hardware::Void;
+using android::hardware::power::Boost;
+using android::hardware::power::Mode;
using android::hardware::power::V1_0::PowerHint;
using android::hardware::power::V1_0::Feature;
using android::String8;
@@ -54,6 +59,7 @@
using android::system::suspend::ISuspendControlService;
using IPowerV1_1 = android::hardware::power::V1_1::IPower;
using IPowerV1_0 = android::hardware::power::V1_0::IPower;
+using IPowerAidl = android::hardware::power::IPower;
namespace android {
@@ -66,11 +72,18 @@
// ----------------------------------------------------------------------------
static jobject gPowerManagerServiceObj;
-// Use getPowerHal* to retrieve a copy
-static sp<IPowerV1_0> gPowerHalV1_0_ = nullptr;
-static sp<IPowerV1_1> gPowerHalV1_1_ = nullptr;
-static bool gPowerHalExists = true;
+static sp<IPowerV1_0> gPowerHalHidlV1_0_ = nullptr;
+static sp<IPowerV1_1> gPowerHalHidlV1_1_ = nullptr;
+static sp<IPowerAidl> gPowerHalAidl_ = nullptr;
static std::mutex gPowerHalMutex;
+
+enum class HalVersion {
+ NONE,
+ HIDL_1_0,
+ HIDL_1_1,
+ AIDL,
+};
+
static nsecs_t gLastEventTime[USER_ACTIVITY_EVENT_LAST + 1];
// Throttling interval for user activity calls.
@@ -88,68 +101,207 @@
return false;
}
-// Check validity of current handle to the power HAL service, and call getService() if necessary.
+// Check validity of current handle to the power HAL service, and connect to it if necessary.
// The caller must be holding gPowerHalMutex.
-static void connectPowerHalLocked() {
- if (gPowerHalExists && gPowerHalV1_0_ == nullptr) {
- gPowerHalV1_0_ = IPowerV1_0::getService();
- if (gPowerHalV1_0_ != nullptr) {
- ALOGI("Loaded power HAL 1.0 service");
- // Try cast to powerHAL V1_1
- gPowerHalV1_1_ = IPowerV1_1::castFrom(gPowerHalV1_0_);
- if (gPowerHalV1_1_ == nullptr) {
- } else {
- ALOGI("Loaded power HAL 1.1 service");
- }
+static HalVersion connectPowerHalLocked() {
+ static bool gPowerHalHidlExists = true;
+ static bool gPowerHalAidlExists = true;
+ if (!gPowerHalHidlExists && !gPowerHalAidlExists) {
+ return HalVersion::NONE;
+ }
+ if (gPowerHalAidlExists) {
+ if (!gPowerHalAidl_) {
+ gPowerHalAidl_ = waitForVintfService<IPowerAidl>();
+ }
+ if (gPowerHalAidl_) {
+ ALOGI("Successfully connected to Power HAL AIDL service.");
+ return HalVersion::AIDL;
} else {
- ALOGI("Couldn't load power HAL service");
- gPowerHalExists = false;
+ gPowerHalAidlExists = false;
}
}
+ if (gPowerHalHidlExists && gPowerHalHidlV1_0_ == nullptr) {
+ gPowerHalHidlV1_0_ = IPowerV1_0::getService();
+ if (gPowerHalHidlV1_0_) {
+ ALOGI("Successfully connected to Power HAL HIDL 1.0 service.");
+ // Try cast to powerHAL HIDL V1_1
+ gPowerHalHidlV1_1_ = IPowerV1_1::castFrom(gPowerHalHidlV1_0_);
+ if (gPowerHalHidlV1_1_) {
+ ALOGI("Successfully connected to Power HAL HIDL 1.1 service.");
+ }
+ } else {
+ ALOGI("Couldn't load power HAL HIDL service");
+ gPowerHalHidlExists = false;
+ return HalVersion::NONE;
+ }
+ }
+ if (gPowerHalHidlV1_1_) {
+ return HalVersion::HIDL_1_1;
+ } else if (gPowerHalHidlV1_0_) {
+ return HalVersion::HIDL_1_0;
+ }
+ return HalVersion::NONE;
}
-// Retrieve a copy of PowerHAL V1_0
-sp<IPowerV1_0> getPowerHalV1_0() {
+// Retrieve a copy of PowerHAL HIDL V1_0
+sp<IPowerV1_0> getPowerHalHidlV1_0() {
std::lock_guard<std::mutex> lock(gPowerHalMutex);
- connectPowerHalLocked();
- return gPowerHalV1_0_;
+ HalVersion halVersion = connectPowerHalLocked();
+ if (halVersion == HalVersion::HIDL_1_0 || halVersion == HalVersion::HIDL_1_1) {
+ return gPowerHalHidlV1_0_;
+ }
+
+ return nullptr;
}
-// Retrieve a copy of PowerHAL V1_1
-sp<IPowerV1_1> getPowerHalV1_1() {
+// Retrieve a copy of PowerHAL HIDL V1_1
+sp<IPowerV1_1> getPowerHalHidlV1_1() {
std::lock_guard<std::mutex> lock(gPowerHalMutex);
- connectPowerHalLocked();
- return gPowerHalV1_1_;
+ if (connectPowerHalLocked() == HalVersion::HIDL_1_1) {
+ return gPowerHalHidlV1_1_;
+ }
+
+ return nullptr;
}
// Check if a call to a power HAL function failed; if so, log the failure and invalidate the
// current handle to the power HAL service.
-bool processPowerHalReturn(const Return<void> &ret, const char* functionName) {
- if (!ret.isOk()) {
+bool processPowerHalReturn(bool isOk, const char* functionName) {
+ if (!isOk) {
ALOGE("%s() failed: power HAL service not available.", functionName);
gPowerHalMutex.lock();
- gPowerHalV1_0_ = nullptr;
- gPowerHalV1_1_ = nullptr;
+ gPowerHalHidlV1_0_ = nullptr;
+ gPowerHalHidlV1_1_ = nullptr;
+ gPowerHalAidl_ = nullptr;
gPowerHalMutex.unlock();
}
- return ret.isOk();
+ return isOk;
}
static void sendPowerHint(PowerHint hintId, uint32_t data) {
- sp<IPowerV1_1> powerHalV1_1 = getPowerHalV1_1();
- Return<void> ret;
- if (powerHalV1_1 != nullptr) {
- ret = powerHalV1_1->powerHintAsync(hintId, data);
- processPowerHalReturn(ret, "powerHintAsync");
- } else {
- sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0();
- if (powerHalV1_0 != nullptr) {
- ret = powerHalV1_0->powerHint(hintId, data);
- processPowerHalReturn(ret, "powerHint");
+ std::unique_lock<std::mutex> lock(gPowerHalMutex);
+ switch (connectPowerHalLocked()) {
+ case HalVersion::NONE:
+ return;
+ case HalVersion::HIDL_1_0: {
+ sp<IPowerV1_0> handle = gPowerHalHidlV1_0_;
+ lock.unlock();
+ auto ret = handle->powerHint(hintId, data);
+ processPowerHalReturn(ret.isOk(), "powerHint");
+ break;
+ }
+ case HalVersion::HIDL_1_1: {
+ sp<IPowerV1_1> handle = gPowerHalHidlV1_1_;
+ lock.unlock();
+ auto ret = handle->powerHintAsync(hintId, data);
+ processPowerHalReturn(ret.isOk(), "powerHintAsync");
+ break;
+ }
+ case HalVersion::AIDL: {
+ if (hintId == PowerHint::INTERACTION) {
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ auto ret = handle->setBoost(Boost::INTERACTION, data);
+ processPowerHalReturn(ret.isOk(), "setBoost");
+ break;
+ } else if (hintId == PowerHint::LAUNCH) {
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ auto ret = handle->setMode(Mode::LAUNCH, static_cast<bool>(data));
+ processPowerHalReturn(ret.isOk(), "setMode");
+ break;
+ } else {
+ ALOGE("Unsupported power hint: %s.", toString(hintId).c_str());
+ return;
+ }
+ }
+ default: {
+ ALOGE("Unknown power HAL state");
+ return;
+ }
+ }
+ SurfaceComposerClient::notifyPowerHint(static_cast<int32_t>(hintId));
+}
+
+enum class HalSupport {
+ UNKNOWN = 0,
+ ON,
+ OFF,
+};
+
+static void setPowerBoost(Boost boost, int32_t durationMs) {
+ // Android framework only sends boost upto DISPLAY_UPDATE_IMMINENT.
+ // Need to increase the array size if more boost supported.
+ static std::array<std::atomic<HalSupport>,
+ static_cast<int32_t>(Boost::DISPLAY_UPDATE_IMMINENT) + 1>
+ boostSupportedArray = {HalSupport::UNKNOWN};
+
+ // Quick return if boost is not supported by HAL
+ if (boost > Boost::DISPLAY_UPDATE_IMMINENT ||
+ boostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::OFF) {
+ ALOGV("Skipped setPowerBoost %s because HAL doesn't support it", toString(boost).c_str());
+ return;
+ }
+
+ std::unique_lock<std::mutex> lock(gPowerHalMutex);
+ if (connectPowerHalLocked() != HalVersion::AIDL) {
+ ALOGV("Power HAL AIDL not available");
+ return;
+ }
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+
+ if (boostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::UNKNOWN) {
+ bool isSupported = false;
+ handle->isBoostSupported(boost, &isSupported);
+ boostSupportedArray[static_cast<int32_t>(boost)] =
+ isSupported ? HalSupport::ON : HalSupport::OFF;
+ if (!isSupported) {
+ ALOGV("Skipped setPowerBoost %s because HAL doesn't support it",
+ toString(boost).c_str());
+ return;
}
}
- SurfaceComposerClient::notifyPowerHint(static_cast<int32_t>(hintId));
+ auto ret = handle->setBoost(boost, durationMs);
+ processPowerHalReturn(ret.isOk(), "setPowerBoost");
+}
+
+static void setPowerMode(Mode mode, bool enabled) {
+ // Android framework only sends mode upto DISPLAY_INACTIVE.
+ // Need to increase the array if more mode supported.
+ static std::array<std::atomic<HalSupport>,
+ static_cast<int32_t>(Mode::DISPLAY_INACTIVE) + 1>
+ modeSupportedArray = {HalSupport::UNKNOWN};
+
+ // Quick return if mode is not supported by HAL
+ if (mode > Mode::DISPLAY_INACTIVE ||
+ modeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::OFF) {
+ ALOGV("Skipped setPowerMode %s because HAL doesn't support it", toString(mode).c_str());
+ return;
+ }
+
+ std::unique_lock<std::mutex> lock(gPowerHalMutex);
+ if (connectPowerHalLocked() != HalVersion::AIDL) {
+ ALOGV("Power HAL AIDL not available");
+ return;
+ }
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+
+ if (modeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::UNKNOWN) {
+ bool isSupported = false;
+ handle->isModeSupported(mode, &isSupported);
+ modeSupportedArray[static_cast<int32_t>(mode)] =
+ isSupported ? HalSupport::ON : HalSupport::OFF;
+ if (!isSupported) {
+ ALOGV("Skipped setPowerMode %s because HAL doesn't support it", toString(mode).c_str());
+ return;
+ }
+ }
+
+ auto ret = handle->setMode(mode, enabled);
+ processPowerHalReturn(ret.isOk(), "setPowerMode");
}
void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType) {
@@ -253,14 +405,34 @@
}
static void nativeSetInteractive(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) {
- sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0();
- if (powerHalV1_0 != nullptr) {
- android::base::Timer t;
- Return<void> ret = powerHalV1_0->setInteractive(enable);
- processPowerHalReturn(ret, "setInteractive");
- if (t.duration() > 20ms) {
- ALOGD("Excessive delay in setInteractive(%s) while turning screen %s",
- enable ? "true" : "false", enable ? "on" : "off");
+ std::unique_lock<std::mutex> lock(gPowerHalMutex);
+ switch (connectPowerHalLocked()) {
+ case HalVersion::NONE:
+ return;
+ case HalVersion::HIDL_1_0:
+ FALLTHROUGH_INTENDED;
+ case HalVersion::HIDL_1_1: {
+ android::base::Timer t;
+ sp<IPowerV1_0> handle = gPowerHalHidlV1_0_;
+ lock.unlock();
+ auto ret = handle->setInteractive(enable);
+ processPowerHalReturn(ret.isOk(), "setInteractive");
+ if (t.duration() > 20ms) {
+ ALOGD("Excessive delay in setInteractive(%s) while turning screen %s",
+ enable ? "true" : "false", enable ? "on" : "off");
+ }
+ return;
+ }
+ case HalVersion::AIDL: {
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ auto ret = handle->setMode(Mode::INTERACTIVE, enable);
+ processPowerHalReturn(ret.isOk(), "setMode");
+ return;
+ }
+ default: {
+ ALOGE("Unknown power HAL state");
+ return;
}
}
}
@@ -285,11 +457,38 @@
sendPowerHint(static_cast<PowerHint>(hintId), data);
}
+static void nativeSetPowerBoost(JNIEnv* /* env */, jclass /* clazz */, jint boost,
+ jint durationMs) {
+ setPowerBoost(static_cast<Boost>(boost), durationMs);
+}
+
+static void nativeSetPowerMode(JNIEnv* /* env */, jclass /* clazz */, jint mode, jboolean enabled) {
+ setPowerMode(static_cast<Mode>(mode), enabled);
+}
+
static void nativeSetFeature(JNIEnv* /* env */, jclass /* clazz */, jint featureId, jint data) {
- sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0();
- if (powerHalV1_0 != nullptr) {
- Return<void> ret = powerHalV1_0->setFeature((Feature)featureId, static_cast<bool>(data));
- processPowerHalReturn(ret, "setFeature");
+ std::unique_lock<std::mutex> lock(gPowerHalMutex);
+ switch (connectPowerHalLocked()) {
+ case HalVersion::NONE:
+ return;
+ case HalVersion::HIDL_1_0:
+ FALLTHROUGH_INTENDED;
+ case HalVersion::HIDL_1_1: {
+ sp<IPowerV1_0> handle = gPowerHalHidlV1_0_;
+ lock.unlock();
+ auto ret = handle->setFeature(static_cast<Feature>(featureId), static_cast<bool>(data));
+ processPowerHalReturn(ret.isOk(), "setFeature");
+ return;
+ }
+ case HalVersion::AIDL: {
+ auto ret = gPowerHalAidl_->setMode(Mode::DOUBLE_TAP_TO_WAKE, static_cast<bool>(data));
+ processPowerHalReturn(ret.isOk(), "setMode");
+ return;
+ }
+ default: {
+ ALOGE("Unknown power HAL state");
+ return;
+ }
}
}
@@ -317,6 +516,10 @@
(void*) nativeSetAutoSuspend },
{ "nativeSendPowerHint", "(II)V",
(void*) nativeSendPowerHint },
+ { "nativeSetPowerBoost", "(II)V",
+ (void*) nativeSetPowerBoost },
+ { "nativeSetPowerMode", "(IZ)V",
+ (void*) nativeSetPowerMode },
{ "nativeSetFeature", "(II)V",
(void*) nativeSetFeature },
};
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index bd63f3c..8da3bdf 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -26,12 +26,12 @@
import android.media.tv.TvInputManager;
import android.media.tv.TvInputService;
import android.media.tv.tuner.frontend.FrontendSettings;
+import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
import android.media.tv.tunerresourcemanager.ResourceClientProfile;
import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
import android.os.RemoteException;
-import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -45,9 +45,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
+import java.util.Map;
/**
* Tests for {@link TunerResourceManagerService} class.
@@ -59,7 +58,19 @@
private Context mContextSpy;
@Mock private ITvInputManager mITvInputManagerMock;
private TunerResourceManagerService mTunerResourceManagerService;
- private int mReclaimingId;
+
+ private static final class TestResourcesReclaimListener extends IResourcesReclaimListener.Stub {
+ boolean mReclaimed;
+
+ @Override
+ public void onReclaimResources() {
+ mReclaimed = true;
+ }
+
+ public boolean isRelaimed() {
+ return mReclaimed;
+ }
+ }
// A correspondence to compare a FrontendResource and a TunerFrontendInfo.
private static final Correspondence<FrontendResource, TunerFrontendInfo> FR_TFI_COMPARE =
@@ -81,31 +92,14 @@
}
};
- private static <T> List<T> sparseArrayToList(SparseArray<T> sparseArray) {
- if (sparseArray == null) {
- return null;
- }
- List<T> arrayList = new ArrayList<T>(sparseArray.size());
- for (int i = 0; i < sparseArray.size(); i++) {
- arrayList.add(sparseArray.valueAt(i));
- }
- return arrayList;
- }
-
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
TvInputManager tvInputManager = new TvInputManager(mITvInputManagerMock, 0);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
when(mContextSpy.getSystemService(Context.TV_INPUT_SERVICE)).thenReturn(tvInputManager);
- mTunerResourceManagerService = new TunerResourceManagerService(mContextSpy) {
- @Override
- protected void reclaimFrontendResource(int reclaimingId) {
- mReclaimingId = reclaimingId;
- }
- };
+ mTunerResourceManagerService = new TunerResourceManagerService(mContextSpy);
mTunerResourceManagerService.onStart(true /*isForTesting*/);
- mReclaimingId = -1;
}
@Test
@@ -118,7 +112,7 @@
new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
- SparseArray<FrontendResource> resources =
+ Map<Integer, FrontendResource> resources =
mTunerResourceManagerService.getFrontendResources();
for (int id = 0; id < infos.length; id++) {
assertThat(resources.get(infos[id].getId())
@@ -128,7 +122,7 @@
assertThat(resources.get(infos[id].getId())
.getExclusiveGroupMemberFeIds().size()).isEqualTo(0);
}
- assertThat(sparseArrayToList(resources)).comparingElementsUsing(FR_TFI_COMPARE)
+ assertThat(resources.values()).comparingElementsUsing(FR_TFI_COMPARE)
.containsExactlyElementsIn(Arrays.asList(infos));
}
@@ -146,19 +140,15 @@
new TunerFrontendInfo(3 /*id*/, FrontendSettings.TYPE_ATSC, 1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
- SparseArray<FrontendResource> resources =
+ Map<Integer, FrontendResource> resources =
mTunerResourceManagerService.getFrontendResources();
- assertThat(sparseArrayToList(resources)).comparingElementsUsing(FR_TFI_COMPARE)
+ assertThat(resources.values()).comparingElementsUsing(FR_TFI_COMPARE)
.containsExactlyElementsIn(Arrays.asList(infos));
- assertThat(resources.get(0).getExclusiveGroupMemberFeIds())
- .isEqualTo(new ArrayList<Integer>());
- assertThat(resources.get(1).getExclusiveGroupMemberFeIds())
- .isEqualTo(new ArrayList<Integer>(Arrays.asList(2, 3)));
- assertThat(resources.get(2).getExclusiveGroupMemberFeIds())
- .isEqualTo(new ArrayList<Integer>(Arrays.asList(1, 3)));
- assertThat(resources.get(3).getExclusiveGroupMemberFeIds())
- .isEqualTo(new ArrayList<Integer>(Arrays.asList(1, 2)));
+ assertThat(resources.get(0).getExclusiveGroupMemberFeIds()).isEmpty();
+ assertThat(resources.get(1).getExclusiveGroupMemberFeIds()).containsExactly(2, 3);
+ assertThat(resources.get(2).getExclusiveGroupMemberFeIds()).containsExactly(1, 3);
+ assertThat(resources.get(3).getExclusiveGroupMemberFeIds()).containsExactly(1, 2);
}
@Test
@@ -171,11 +161,11 @@
new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
- SparseArray<FrontendResource> resources0 =
+ Map<Integer, FrontendResource> resources0 =
mTunerResourceManagerService.getFrontendResources();
mTunerResourceManagerService.setFrontendInfoListInternal(infos);
- SparseArray<FrontendResource> resources1 =
+ Map<Integer, FrontendResource> resources1 =
mTunerResourceManagerService.getFrontendResources();
assertThat(resources0).isEqualTo(resources1);
@@ -198,13 +188,13 @@
new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos1);
- SparseArray<FrontendResource> resources =
+ Map<Integer, FrontendResource> resources =
mTunerResourceManagerService.getFrontendResources();
for (int id = 0; id < infos1.length; id++) {
assertThat(resources.get(infos1[id].getId())
.getExclusiveGroupMemberFeIds().size()).isEqualTo(0);
}
- assertThat(sparseArrayToList(resources)).comparingElementsUsing(FR_TFI_COMPARE)
+ assertThat(resources.values()).comparingElementsUsing(FR_TFI_COMPARE)
.containsExactlyElementsIn(Arrays.asList(infos1));
}
@@ -225,13 +215,13 @@
new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
mTunerResourceManagerService.setFrontendInfoListInternal(infos1);
- SparseArray<FrontendResource> resources =
+ Map<Integer, FrontendResource> resources =
mTunerResourceManagerService.getFrontendResources();
for (int id = 0; id < infos1.length; id++) {
assertThat(resources.get(infos1[id].getId())
.getExclusiveGroupMemberFeIds().size()).isEqualTo(0);
}
- assertThat(sparseArrayToList(resources)).comparingElementsUsing(FR_TFI_COMPARE)
+ assertThat(resources.values()).comparingElementsUsing(FR_TFI_COMPARE)
.containsExactlyElementsIn(Arrays.asList(infos1));
}
@@ -352,9 +342,9 @@
throw e.rethrowFromSystemServer();
}
assertThat(frontendId[0]).isEqualTo(infos[1].getId());
- assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[1].getId())
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
.isInUse()).isTrue();
- assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[2].getId())
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[2].getId())
.isInUse()).isTrue();
}
@@ -369,15 +359,17 @@
int[] clientPriorities = {100, 50};
int[] clientId0 = new int[1];
int[] clientId1 = new int[1];
+ TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
+
mTunerResourceManagerService.registerClientProfileInternal(
- profiles[0], null /*listener*/, clientId0);
+ profiles[0], listener, clientId0);
assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfiles().get(clientId0[0])
+ mTunerResourceManagerService.getClientProfile(clientId0[0])
.setPriority(clientPriorities[0]);
mTunerResourceManagerService.registerClientProfileInternal(
- profiles[1], null /*listener*/, clientId1);
+ profiles[1], new TestResourcesReclaimListener(), clientId1);
assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfiles().get(clientId1[0])
+ mTunerResourceManagerService.getClientProfile(clientId1[0])
.setPriority(clientPriorities[1]);
// Init frontend resources.
@@ -403,17 +395,17 @@
try {
assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
.isFalse();
+ assertThat(listener.isRelaimed()).isFalse();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- assertThat(mReclaimingId).isEqualTo(-1);
request =
new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
try {
assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
.isFalse();
- assertThat(mReclaimingId).isEqualTo(-1);
+ assertThat(listener.isRelaimed()).isFalse();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -430,15 +422,16 @@
int[] clientPriorities = {100, 500};
int[] clientId0 = new int[1];
int[] clientId1 = new int[1];
+ TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
mTunerResourceManagerService.registerClientProfileInternal(
- profiles[0], null /*listener*/, clientId0);
+ profiles[0], listener, clientId0);
assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfiles().get(clientId0[0])
+ mTunerResourceManagerService.getClientProfile(clientId0[0])
.setPriority(clientPriorities[0]);
mTunerResourceManagerService.registerClientProfileInternal(
- profiles[1], null /*listener*/, clientId1);
+ profiles[1], new TestResourcesReclaimListener(), clientId1);
assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfiles().get(clientId1[0])
+ mTunerResourceManagerService.getClientProfile(clientId1[0])
.setPriority(clientPriorities[1]);
// Init frontend resources.
@@ -469,15 +462,15 @@
throw e.rethrowFromSystemServer();
}
assertThat(frontendId[0]).isEqualTo(infos[1].getId());
- assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[0].getId())
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
.isInUse()).isTrue();
- assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[1].getId())
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
.isInUse()).isTrue();
- assertThat(mTunerResourceManagerService.getFrontendResources()
- .get(infos[0].getId()).getOwnerClientId()).isEqualTo(clientId1[0]);
- assertThat(mTunerResourceManagerService.getFrontendResources()
- .get(infos[1].getId()).getOwnerClientId()).isEqualTo(clientId1[0]);
- assertThat(mReclaimingId).isEqualTo(clientId0[0]);
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+ .getOwnerClientId()).isEqualTo(clientId1[0]);
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+ .getOwnerClientId()).isEqualTo(clientId1[0]);
+ assertThat(listener.isRelaimed()).isTrue();
}
@Test
@@ -508,17 +501,18 @@
throw e.rethrowFromSystemServer();
}
assertThat(frontendId[0]).isEqualTo(infos[0].getId());
- assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[0].getId())
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
.isInUse()).isTrue();
- assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[1].getId())
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
.isInUse()).isTrue();
// Unregister client when using frontend
mTunerResourceManagerService.unregisterClientProfileInternal(clientId[0]);
- assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[0].getId())
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
.isInUse()).isFalse();
- assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[1].getId())
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
.isInUse()).isFalse();
+ assertThat(mTunerResourceManagerService.checkClientExists(clientId[0])).isFalse();
}
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 5f33a3d..9dfa3ac 100755
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -2331,7 +2331,6 @@
* See {@link TelecomManager} for valid values.
*/
public final void setAddress(Uri address, int presentation) {
- checkImmutable();
Log.d(this, "setAddress %s", address);
mAddress = address;
mAddressPresentation = presentation;
@@ -3358,6 +3357,7 @@
private boolean mImmutable = false;
public FailureSignalingConnection(DisconnectCause disconnectCause) {
setDisconnected(disconnectCause);
+ mImmutable = true;
}
public void checkImmutable() {